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.databases.mysql import *
|
||||
|
||||
from pokedex.db import rst
|
||||
|
||||
metadata = MetaData()
|
||||
TableBase = declarative_base(metadata=metadata)
|
||||
|
||||
|
@ -174,7 +176,7 @@ class MoveEffect(TableBase):
|
|||
id = Column(Integer, primary_key=True, nullable=False)
|
||||
priority = Column(SmallInteger, nullable=False)
|
||||
short_effect = Column(Unicode(128), nullable=False)
|
||||
effect = Column(Unicode(255), nullable=False)
|
||||
effect = Column(Unicode(5120), nullable=False)
|
||||
|
||||
class MoveTarget(TableBase):
|
||||
__tablename__ = 'move_targets'
|
||||
|
@ -395,12 +397,16 @@ LocationArea.location = relation(Location, backref='areas')
|
|||
Machine.generation = relation(Generation)
|
||||
|
||||
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.machines = relation(Machine, backref='move')
|
||||
Move.target = relation(MoveTarget, 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__,
|
||||
order_by=PokemonAbility.slot,
|
||||
backref='pokemon')
|
||||
|
|
Loading…
Reference in a new issue