Merge remote-tracking branch 'origin/encounters-i18n'
[zzz-pokedex.git] / pokedex / db / markdown.py
1 # encoding: utf8
2 u"""Implements the markup used for description and effect text in the database.
3
4 The language used is a variation of Markdown and Markdown Extra. There are
5 docs for each at http://daringfireball.net/projects/markdown/ and
6 http://michelf.com/projects/php-markdown/extra/ respectively.
7
8 Pokédex links are represented with the extended syntax `[name]{type}`, e.g.,
9 `[Eevee]{pokemon}`. The actual code that parses these is in spline-pokedex.
10 """
11 from __future__ import absolute_import
12
13 import markdown
14 import sqlalchemy.types
15
16 class MarkdownString(object):
17 """Wraps a Markdown string. Stringifies to the original text, but .as_html
18 will return an HTML rendering.
19
20 To add extensions to the rendering (which is necessary for rendering links
21 correctly, and which spline-pokedex does), you must append to this class's
22 `markdown_extensions` list. Yep, that's gross.
23 """
24
25 markdown_extensions = ['extra']
26
27 def __init__(self, source_text):
28 self.source_text = source_text
29 self._as_html = None
30
31 def __unicode__(self):
32 return self.source_text
33
34 def __str__(self):
35 return unicode(self.source_text).encode()
36
37 def __html__(self):
38 return self.as_html
39
40 @property
41 def as_html(self):
42 """Returns the string as HTML4."""
43
44 if self._as_html is not None:
45 return self._as_html
46
47 md = markdown.Markdown(
48 extensions=self.markdown_extensions,
49 safe_mode='escape',
50 output_format='xhtml1',
51 )
52
53 self._as_html = md.convert(self.source_text)
54
55 return self._as_html
56
57 @property
58 def as_text(self):
59 """Returns the string in a plaintext-friendly form.
60
61 At the moment, this is just the original source text.
62 """
63 return self.source_text
64
65 def _markdownify_effect_text(move, effect_text):
66 effect_text = effect_text.replace(
67 u'$effect_chance',
68 unicode(move.effect_chance),
69 )
70
71 return MarkdownString(effect_text)
72
73 class MoveEffectProperty(object):
74 """Property that wraps move effects. Used like this:
75
76 MoveClass.effect = MoveEffectProperty('effect')
77
78 some_move.effect # returns a MarkdownString
79 some_move.effect.as_html # returns a chunk of HTML
80
81 This class also performs simple substitution on the effect, replacing
82 `$effect_chance` with the move's actual effect chance.
83
84 Use `MoveEffectPropertyMap` for dict-like association proxies.
85 """
86
87 def __init__(self, effect_column):
88 self.effect_column = effect_column
89
90 def __get__(self, obj, cls):
91 prop = getattr(obj.move_effect, self.effect_column)
92 return _markdownify_effect_text(obj, prop)
93
94 class MoveEffectPropertyMap(MoveEffectProperty):
95 """Similar to `MoveEffectProperty`, but works on dict-like association
96 proxies.
97 """
98 def __get__(self, obj, cls):
99 prop = getattr(obj.move_effect, self.effect_column)
100 newdict = dict(prop)
101 for key in newdict:
102 newdict[key] = _markdownify_effect_text(obj, newdict[key])
103 return newdict
104
105 class MarkdownColumn(sqlalchemy.types.TypeDecorator):
106 """Generic SQLAlchemy column type for Markdown text.
107
108 Do NOT use this for move effects! They need to know what move they belong
109 to so they can fill in, e.g., effect chances. Use the MoveEffectProperty
110 property class above.
111 """
112 impl = sqlalchemy.types.Unicode
113
114 def process_bind_param(self, value, dialect):
115 if value is None:
116 return None
117
118 if not isinstance(value, basestring):
119 # Can't assign, e.g., MarkdownString objects yet
120 raise NotImplementedError
121
122 return unicode(value)
123
124 def process_result_value(self, value, dialect):
125 if value is None:
126 return None
127
128 return MarkdownString(value)