mirror of
https://github.com/veekun/pokedex.git
synced 2024-08-20 18:16:34 +00:00
Added semi-magical reST parsing for move effects.
This commit is contained in:
parent
c545fbf764
commit
585cb31c0a
2 changed files with 168 additions and 2 deletions
160
pokedex/db/rst.py
Normal file
160
pokedex/db/rst.py
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
# encoding: utf8
|
||||||
|
r"""Functionality for handling reStructuredText fields in the database.
|
||||||
|
|
||||||
|
This module defines the following extra text roles. By default, they merely
|
||||||
|
bold the contents of the tag. Calling code may redefine them with
|
||||||
|
`docutils.parsers.rst.roles.register_local_role`. Docutils role extensions
|
||||||
|
are, apparently, global.
|
||||||
|
|
||||||
|
`ability`
|
||||||
|
`item`
|
||||||
|
`move`
|
||||||
|
`pokemon`
|
||||||
|
These all wrap objects of the corresponding type. They're intended to be
|
||||||
|
used to link to these items.
|
||||||
|
|
||||||
|
`mechanic`
|
||||||
|
This is a general-purpose reference role. The Web Pokédex uses these to
|
||||||
|
link to pages on mechanics. Amongst the things tagged with this are:
|
||||||
|
* Stats, e.g., Attack, Speed
|
||||||
|
* Major status effects, e.g., paralysis, freezing
|
||||||
|
* Minor status effects not unique to a single move, e.g., confusion
|
||||||
|
* Battle mechanics, e.g., "regular damage", "lowers/raises" a stat
|
||||||
|
|
||||||
|
`data`
|
||||||
|
Depends on context. Created for move effect chances; some effects contain
|
||||||
|
text like "Has a \:data\:\`move.effect_chance\` chance to...". Here, the
|
||||||
|
enclosed text is taken as a reference to a column on the associated move.
|
||||||
|
Other contexts may someday invent their own constructs.
|
||||||
|
|
||||||
|
This is actually implemented by adding a `_pokedex_handle_data` attribute
|
||||||
|
to the reST document itself, which the `data` role handler attempts to
|
||||||
|
call. This function takes `rawtext` and `text` as arguments and should
|
||||||
|
return a reST node.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from docutils.frontend import OptionParser
|
||||||
|
from docutils.io import Output
|
||||||
|
import docutils.nodes
|
||||||
|
from docutils.parsers.rst import Parser, roles
|
||||||
|
import docutils.utils
|
||||||
|
from docutils.writers.html4css1 import Writer as HTMLWriter
|
||||||
|
|
||||||
|
### Subclasses of bits of docutils, to munge it into doing what I want
|
||||||
|
class HTMLFragmentWriter(HTMLWriter):
|
||||||
|
"""Translates reST to HTML, but only as a fragment. Enclosing <body>,
|
||||||
|
<head>, and <html> tags are omitted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def apply_template(self):
|
||||||
|
subs = self.interpolation_dict()
|
||||||
|
return subs['body']
|
||||||
|
|
||||||
|
class UnicodeOutput(Output):
|
||||||
|
"""reST Unicode output. The distribution only has a StringOutput, and I
|
||||||
|
want me some Unicode.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
"""Returns data (a Unicode string) unaltered."""
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
### Text roles
|
||||||
|
|
||||||
|
def generic_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||||
|
node = docutils.nodes.strong(text, rawtext, **options)
|
||||||
|
return [node], []
|
||||||
|
|
||||||
|
roles.register_local_role('ability', generic_role)
|
||||||
|
roles.register_local_role('item', generic_role)
|
||||||
|
roles.register_local_role('move', generic_role)
|
||||||
|
roles.register_local_role('pokemon', generic_role)
|
||||||
|
roles.register_local_role('mechanic', generic_role)
|
||||||
|
|
||||||
|
def data_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||||
|
document = inliner.document
|
||||||
|
node = document._pokedex_handle_data(rawtext, text)
|
||||||
|
return [node], []
|
||||||
|
|
||||||
|
roles.register_local_role('data', data_role)
|
||||||
|
|
||||||
|
|
||||||
|
### Public classes
|
||||||
|
|
||||||
|
class RstString(object):
|
||||||
|
"""Wraps a reStructuredText string. Stringifies to the original text, but
|
||||||
|
may be translated to HTML with .to_html().
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, source_text, document_properties={}):
|
||||||
|
"""
|
||||||
|
`document_properties`
|
||||||
|
List of extra properties to attach to the reST document object.
|
||||||
|
"""
|
||||||
|
self.source_text = source_text
|
||||||
|
self.document_properties = document_properties
|
||||||
|
self._rest_document = None
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.source_text
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rest_document(self):
|
||||||
|
"""reST parse tree of the source text.
|
||||||
|
|
||||||
|
This property is lazy-loaded.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Return it if we have it
|
||||||
|
if self._rest_document:
|
||||||
|
return self._rest_document
|
||||||
|
|
||||||
|
parser = Parser()
|
||||||
|
settings = OptionParser(components=(Parser,HTMLWriter)).get_default_values()
|
||||||
|
document = docutils.utils.new_document('pokedex', settings)
|
||||||
|
|
||||||
|
# Add properties (in this case, probably just the data role handler)
|
||||||
|
document.__dict__.update(self.document_properties)
|
||||||
|
|
||||||
|
# PARSE
|
||||||
|
parser.parse(self.source_text, document)
|
||||||
|
|
||||||
|
self._rest_document = document
|
||||||
|
return document
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_html(self):
|
||||||
|
"""Returns the string as HTML4."""
|
||||||
|
|
||||||
|
document = self.rest_document
|
||||||
|
destination = UnicodeOutput()
|
||||||
|
|
||||||
|
writer = HTMLFragmentWriter()
|
||||||
|
return writer.write(document, destination)
|
||||||
|
|
||||||
|
|
||||||
|
class MoveEffectProperty(object):
|
||||||
|
"""Property that wraps a move effect. Used like this:
|
||||||
|
|
||||||
|
MoveClass.effect = MoveEffectProperty()
|
||||||
|
|
||||||
|
some_move.effect # returns an RstString
|
||||||
|
some_move.effect.as_html # returns a chunk of HTML
|
||||||
|
|
||||||
|
This class also performs `%` substitution on the effect, replacing
|
||||||
|
`%(effect_chance)d` with the move's actual effect chance. Also this is a
|
||||||
|
lie and it doesn't yet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __get__(self, move, move_class):
|
||||||
|
# Attach a function for handling the `data` role
|
||||||
|
# XXX make this a little more fault-tolerant.. maybe..
|
||||||
|
def data_role_func(rawtext, text):
|
||||||
|
assert text[0:5] == 'move.'
|
||||||
|
newtext = getattr(move, text[5:])
|
||||||
|
return docutils.nodes.Text(newtext, rawtext)
|
||||||
|
|
||||||
|
return RstString(move.move_effect.effect,
|
||||||
|
document_properties=dict(
|
||||||
|
_pokedex_handle_data=data_role_func))
|
|
@ -7,6 +7,8 @@ from sqlalchemy.orm import backref, relation
|
||||||
from sqlalchemy.types import *
|
from sqlalchemy.types import *
|
||||||
from sqlalchemy.databases.mysql import *
|
from sqlalchemy.databases.mysql import *
|
||||||
|
|
||||||
|
from pokedex.db import rst
|
||||||
|
|
||||||
metadata = MetaData()
|
metadata = MetaData()
|
||||||
TableBase = declarative_base(metadata=metadata)
|
TableBase = declarative_base(metadata=metadata)
|
||||||
|
|
||||||
|
@ -174,7 +176,7 @@ class MoveEffect(TableBase):
|
||||||
id = Column(Integer, primary_key=True, nullable=False)
|
id = Column(Integer, primary_key=True, nullable=False)
|
||||||
priority = Column(SmallInteger, nullable=False)
|
priority = Column(SmallInteger, nullable=False)
|
||||||
short_effect = Column(Unicode(128), nullable=False)
|
short_effect = Column(Unicode(128), nullable=False)
|
||||||
effect = Column(Unicode(255), nullable=False)
|
effect = Column(Unicode(5120), nullable=False)
|
||||||
|
|
||||||
class MoveTarget(TableBase):
|
class MoveTarget(TableBase):
|
||||||
__tablename__ = 'move_targets'
|
__tablename__ = 'move_targets'
|
||||||
|
@ -395,12 +397,16 @@ LocationArea.location = relation(Location, backref='areas')
|
||||||
Machine.generation = relation(Generation)
|
Machine.generation = relation(Generation)
|
||||||
|
|
||||||
Move.damage_class = relation(MoveDamageClass, backref='moves')
|
Move.damage_class = relation(MoveDamageClass, backref='moves')
|
||||||
Move.effect = relation(MoveEffect, backref='moves')
|
Move.move_effect = relation(MoveEffect, backref='moves')
|
||||||
Move.generation = relation(Generation, backref='moves')
|
Move.generation = relation(Generation, backref='moves')
|
||||||
Move.machines = relation(Machine, backref='move')
|
Move.machines = relation(Machine, backref='move')
|
||||||
Move.target = relation(MoveTarget, backref='moves')
|
Move.target = relation(MoveTarget, backref='moves')
|
||||||
Move.type = relation(Type, backref='moves')
|
Move.type = relation(Type, backref='moves')
|
||||||
|
|
||||||
|
Move.effect = rst.MoveEffectProperty()
|
||||||
|
Move.priority = association_proxy('move_effect', 'priority')
|
||||||
|
Move.short_effect = association_proxy('move_effect', 'short_effect')
|
||||||
|
|
||||||
Pokemon.abilities = relation(Ability, secondary=PokemonAbility.__table__,
|
Pokemon.abilities = relation(Ability, secondary=PokemonAbility.__table__,
|
||||||
order_by=PokemonAbility.slot,
|
order_by=PokemonAbility.slot,
|
||||||
backref='pokemon')
|
backref='pokemon')
|
||||||
|
|
Loading…
Reference in a new issue