Made short_effect also an rST column, so effect chance works.
[zzz-pokedex.git] / pokedex / db / rst.py
1 # encoding: utf8
2 r"""Functionality for handling reStructuredText fields in the database.
3
4 This module defines the following extra text roles. By default, they merely
5 bold the contents of the tag. Calling code may redefine them with
6 `docutils.parsers.rst.roles.register_local_role`. Docutils role extensions
7 are, apparently, global.
8
9 `ability`
10 `item`
11 `move`
12 `pokemon`
13 These all wrap objects of the corresponding type. They're intended to be
14 used to link to these items.
15
16 `mechanic`
17 This is a general-purpose reference role. The Web Pokédex uses these to
18 link to pages on mechanics. Amongst the things tagged with this are:
19 * Stats, e.g., Attack, Speed
20 * Major status effects, e.g., paralysis, freezing
21 * Minor status effects not unique to a single move, e.g., confusion
22 * Battle mechanics, e.g., "regular damage", "lowers/raises" a stat
23
24 `data`
25 Depends on context. Created for move effect chances; some effects contain
26 text like "Has a \:data\:\`move.effect_chance\` chance to...". Here, the
27 enclosed text is taken as a reference to a column on the associated move.
28 Other contexts may someday invent their own constructs.
29
30 This is actually implemented by adding a `_pokedex_handle_data` attribute
31 to the reST document itself, which the `data` role handler attempts to
32 call. This function takes `rawtext` and `text` as arguments and should
33 return a reST node.
34 """
35
36 from docutils.frontend import OptionParser
37 from docutils.io import Output
38 import docutils.nodes
39 from docutils.parsers.rst import Parser, roles
40 import docutils.utils
41 from docutils.writers.html4css1 import Writer as HTMLWriter
42
43 ### Subclasses of bits of docutils, to munge it into doing what I want
44 class HTMLFragmentWriter(HTMLWriter):
45 """Translates reST to HTML, but only as a fragment. Enclosing <body>,
46 <head>, and <html> tags are omitted.
47 """
48
49 def apply_template(self):
50 subs = self.interpolation_dict()
51 return subs['body']
52
53 class UnicodeOutput(Output):
54 """reST Unicode output. The distribution only has a StringOutput, and I
55 want me some Unicode.
56 """
57
58 def write(self, data):
59 """Returns data (a Unicode string) unaltered."""
60 return data
61
62
63 ### Text roles
64
65 def generic_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
66 node = docutils.nodes.emphasis(rawtext, text, **options)
67 return [node], []
68
69 roles.register_local_role('ability', generic_role)
70 roles.register_local_role('item', generic_role)
71 roles.register_local_role('move', generic_role)
72 roles.register_local_role('type', generic_role)
73 roles.register_local_role('pokemon', generic_role)
74 roles.register_local_role('mechanic', generic_role)
75
76 def data_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
77 document = inliner.document
78 node = document._pokedex_handle_data(rawtext, text)
79 return [node], []
80
81 roles.register_local_role('data', data_role)
82
83
84 ### Public classes
85
86 class RstString(object):
87 """Wraps a reStructuredText string. Stringifies to the original text, but
88 may be translated to HTML with .to_html().
89 """
90
91 def __init__(self, source_text, document_properties={}):
92 """
93 `document_properties`
94 List of extra properties to attach to the reST document object.
95 """
96 self.source_text = source_text
97 self.document_properties = document_properties
98 self._rest_document = None
99
100 def __unicode__(self):
101 return self.source_text
102
103 @property
104 def rest_document(self):
105 """reST parse tree of the source text.
106
107 This property is lazy-loaded.
108 """
109
110 # Return it if we have it
111 if self._rest_document:
112 return self._rest_document
113
114 parser = Parser()
115 settings = OptionParser(components=(Parser,HTMLWriter)).get_default_values()
116 document = docutils.utils.new_document('pokedex', settings)
117
118 # Add properties (in this case, probably just the data role handler)
119 document.__dict__.update(self.document_properties)
120
121 # PARSE
122 parser.parse(self.source_text, document)
123
124 self._rest_document = document
125 return document
126
127 @property
128 def as_html(self):
129 """Returns the string as HTML4."""
130
131 document = self.rest_document
132 destination = UnicodeOutput()
133
134 writer = HTMLFragmentWriter()
135 return writer.write(document, destination)
136
137
138 class MoveEffectProperty(object):
139 """Property that wraps a move effect. Used like this:
140
141 MoveClass.effect = MoveEffectProperty()
142
143 some_move.effect # returns an RstString
144 some_move.effect.as_html # returns a chunk of HTML
145
146 This class also performs `%` substitution on the effect, replacing
147 `%(effect_chance)d` with the move's actual effect chance. Also this is a
148 lie and it doesn't yet.
149 """
150
151 def __init__(self, effect_column):
152 self.effect_column = effect_column
153
154 def __get__(self, move, move_class):
155 # Attach a function for handling the `data` role
156 # XXX make this a little more fault-tolerant.. maybe..
157 def data_role_func(rawtext, text):
158 assert text[0:5] == 'move.'
159 newtext = getattr(move, text[5:])
160 return docutils.nodes.Text(newtext, rawtext)
161
162 return RstString(getattr(move.move_effect, self.effect_column),
163 document_properties=dict(
164 _pokedex_handle_data=data_role_func))