2010-05-31 22:13:34 +00:00
|
|
|
# encoding: utf8
|
|
|
|
u"""Implements the markup used for description and effect text in the database.
|
|
|
|
|
|
|
|
The language used is a variation of Markdown and Markdown Extra. There are
|
|
|
|
docs for each at http://daringfireball.net/projects/markdown/ and
|
|
|
|
http://michelf.com/projects/php-markdown/extra/ respectively.
|
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
Pokédex links are represented with the syntax `[label]{category:identifier}`,
|
|
|
|
e.g., `[Eevee]{pokemon:eevee}`. The label can (and should) be left out, in
|
|
|
|
which case it is replaced by the name of the thing linked to.
|
2010-05-31 22:13:34 +00:00
|
|
|
"""
|
2010-06-01 00:06:33 +00:00
|
|
|
from __future__ import absolute_import
|
2010-05-31 22:13:34 +00:00
|
|
|
|
2011-04-20 13:27:57 +00:00
|
|
|
import re
|
|
|
|
|
2010-05-31 22:13:34 +00:00
|
|
|
import markdown
|
|
|
|
import sqlalchemy.types
|
2011-04-20 19:47:37 +00:00
|
|
|
from sqlalchemy.orm.session import object_session
|
2010-05-31 22:13:34 +00:00
|
|
|
|
|
|
|
class MarkdownString(object):
|
2011-04-20 19:47:37 +00:00
|
|
|
"""Wraps a Markdown string.
|
|
|
|
|
|
|
|
Use unicode() and __html__ for text and HTML representations.
|
|
|
|
The as_text() and as_html() functions do the same, but accept optional
|
|
|
|
arguments that may affect the rendering.
|
|
|
|
The `source_text` property holds the original text.
|
|
|
|
|
|
|
|
init args:
|
|
|
|
`source_text`: the text in Markdown syntax
|
|
|
|
`session`: A DB session used for looking up linked objects
|
|
|
|
`language`: The language the string is in. If None, the session default
|
|
|
|
is used.
|
2010-05-31 22:13:34 +00:00
|
|
|
"""
|
|
|
|
|
2011-04-20 13:27:57 +00:00
|
|
|
default_link_extension = None
|
2010-05-31 22:13:34 +00:00
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
def __init__(self, source_text, session, language):
|
2010-05-31 22:13:34 +00:00
|
|
|
self.source_text = source_text
|
2011-04-20 19:47:37 +00:00
|
|
|
self.session = session
|
|
|
|
self.language = language
|
2010-05-31 22:13:34 +00:00
|
|
|
|
|
|
|
def __unicode__(self):
|
2011-04-20 19:47:37 +00:00
|
|
|
return self.as_text()
|
2010-05-31 22:13:34 +00:00
|
|
|
|
2011-03-31 20:51:19 +00:00
|
|
|
def __str__(self):
|
2011-04-20 19:47:37 +00:00
|
|
|
return self.as_text().encode()
|
2011-03-31 20:51:19 +00:00
|
|
|
|
|
|
|
def __html__(self):
|
2011-04-20 19:47:37 +00:00
|
|
|
return self.as_html()
|
2011-04-20 13:27:57 +00:00
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
def as_html(self, object_url=None, identifier_url=None, make_link=None):
|
2011-04-20 13:27:57 +00:00
|
|
|
"""Returns the string as HTML.
|
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
If given, the optional arguments will be used instead of those in the
|
|
|
|
session's pokedex_link_maker. See MarkdownLinkMaker for documentation.
|
2011-04-20 13:27:57 +00:00
|
|
|
"""
|
2010-05-31 22:13:34 +00:00
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
extension = self.session.pokedex_link_maker.get_extension(
|
|
|
|
self.language,
|
|
|
|
object_url=object_url,
|
|
|
|
identifier_url=identifier_url,
|
|
|
|
make_link=make_link,
|
|
|
|
)
|
2010-05-31 22:13:34 +00:00
|
|
|
|
|
|
|
md = markdown.Markdown(
|
2011-04-20 13:27:57 +00:00
|
|
|
extensions=['extra', extension],
|
2010-05-31 22:13:34 +00:00
|
|
|
safe_mode='escape',
|
|
|
|
output_format='xhtml1',
|
|
|
|
)
|
|
|
|
|
2011-04-20 13:27:57 +00:00
|
|
|
return md.convert(self.source_text)
|
2010-05-31 22:13:34 +00:00
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
def as_text(self):
|
2010-05-31 22:13:34 +00:00
|
|
|
"""Returns the string in a plaintext-friendly form.
|
2011-04-20 19:47:37 +00:00
|
|
|
|
|
|
|
Currently there are no tunable parameters
|
2010-05-31 22:13:34 +00:00
|
|
|
"""
|
2011-04-20 13:27:57 +00:00
|
|
|
# Since Markdown is pretty readable by itself, we just have to replace
|
|
|
|
# the links by their text.
|
|
|
|
# XXX: The tables get unaligned
|
2011-04-20 19:47:37 +00:00
|
|
|
|
|
|
|
link_maker = MarkdownLinkMaker(self.session)
|
|
|
|
pattern = PokedexLinkPattern(link_maker, self.language)
|
2011-04-20 13:27:57 +00:00
|
|
|
regex = '()%s()' % pattern.regex
|
|
|
|
def handleMatch(m):
|
|
|
|
return pattern.handleMatch(m).text
|
2011-04-20 19:47:37 +00:00
|
|
|
|
2011-04-20 13:27:57 +00:00
|
|
|
return re.sub(regex, handleMatch, self.source_text)
|
2010-05-31 22:13:34 +00:00
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
def _markdownify_effect_text(move, effect_text, language=None):
|
|
|
|
session = object_session(move)
|
|
|
|
|
2011-04-20 11:05:52 +00:00
|
|
|
if effect_text is None:
|
|
|
|
return effect_text
|
2011-03-24 05:17:02 +00:00
|
|
|
effect_text = effect_text.replace(
|
|
|
|
u'$effect_chance',
|
|
|
|
unicode(move.effect_chance),
|
|
|
|
)
|
2010-05-31 22:13:34 +00:00
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
return MarkdownString(effect_text, session, language)
|
2011-02-04 04:22:44 +00:00
|
|
|
|
2011-03-24 05:17:02 +00:00
|
|
|
class MoveEffectProperty(object):
|
2011-02-04 04:22:44 +00:00
|
|
|
"""Property that wraps move effects. Used like this:
|
|
|
|
|
2011-03-24 05:17:02 +00:00
|
|
|
MoveClass.effect = MoveEffectProperty('effect')
|
|
|
|
|
|
|
|
some_move.effect # returns a MarkdownString
|
|
|
|
some_move.effect.as_html # returns a chunk of HTML
|
2011-02-04 04:22:44 +00:00
|
|
|
|
|
|
|
This class also performs simple substitution on the effect, replacing
|
|
|
|
`$effect_chance` with the move's actual effect chance.
|
2011-04-03 08:25:59 +00:00
|
|
|
|
|
|
|
Use `MoveEffectPropertyMap` for dict-like association proxies.
|
2011-02-04 04:22:44 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, effect_column):
|
|
|
|
self.effect_column = effect_column
|
|
|
|
|
2011-03-24 05:17:02 +00:00
|
|
|
def __get__(self, obj, cls):
|
|
|
|
prop = getattr(obj.move_effect, self.effect_column)
|
|
|
|
return _markdownify_effect_text(obj, prop)
|
2010-05-31 22:13:34 +00:00
|
|
|
|
2011-04-03 08:25:59 +00:00
|
|
|
class MoveEffectPropertyMap(MoveEffectProperty):
|
|
|
|
"""Similar to `MoveEffectProperty`, but works on dict-like association
|
|
|
|
proxies.
|
|
|
|
"""
|
|
|
|
def __get__(self, obj, cls):
|
|
|
|
prop = getattr(obj.move_effect, self.effect_column)
|
|
|
|
newdict = dict(prop)
|
|
|
|
for key in newdict:
|
2011-04-20 19:47:37 +00:00
|
|
|
newdict[key] = _markdownify_effect_text(obj, newdict[key], key)
|
2011-04-03 08:25:59 +00:00
|
|
|
return newdict
|
|
|
|
|
2011-04-20 13:27:57 +00:00
|
|
|
class PokedexLinkPattern(markdown.inlinepatterns.Pattern):
|
|
|
|
"""Matches [label]{category:target}.
|
2011-04-20 19:47:37 +00:00
|
|
|
|
|
|
|
Handles matches using factory
|
2011-04-20 13:27:57 +00:00
|
|
|
"""
|
|
|
|
regex = ur'(?x) \[ ([^]]*) \] \{ ([-a-z0-9]+) : ([-a-z0-9]+) \}'
|
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
def __init__(self, factory, string_language, game_language=None):
|
2011-04-20 13:27:57 +00:00
|
|
|
markdown.inlinepatterns.Pattern.__init__(self, self.regex)
|
2011-04-20 19:47:37 +00:00
|
|
|
self.factory = factory
|
|
|
|
self.session = factory.session
|
|
|
|
self.string_language = string_language
|
|
|
|
self.game_language = game_language
|
2011-04-20 13:27:57 +00:00
|
|
|
|
|
|
|
def handleMatch(self, m):
|
|
|
|
from pokedex.db import tables, util
|
|
|
|
start, label, category, target, end = m.groups()
|
|
|
|
try:
|
|
|
|
table = dict(
|
|
|
|
ability=tables.Ability,
|
|
|
|
item=tables.Item,
|
|
|
|
location=tables.Location,
|
|
|
|
move=tables.Move,
|
|
|
|
pokemon=tables.Pokemon,
|
|
|
|
type=tables.Type,
|
|
|
|
)[category]
|
|
|
|
except KeyError:
|
|
|
|
obj = name = target
|
2011-04-20 19:47:37 +00:00
|
|
|
url = self.factory.identifier_url(category, obj)
|
2011-04-20 13:27:57 +00:00
|
|
|
else:
|
2011-04-20 19:47:37 +00:00
|
|
|
session = self.session
|
|
|
|
obj = util.get(self.session, table, target)
|
|
|
|
url = self.factory.object_url(category, obj)
|
|
|
|
url = url or self.factory.identifier_url(category, obj.identifier)
|
|
|
|
name = None
|
|
|
|
# Translations can be incomplete; in which case we want to use a
|
|
|
|
# fallback.
|
2011-04-20 13:27:57 +00:00
|
|
|
if table in [tables.Type]:
|
|
|
|
# Type wants to be localized to the same language as the text
|
2011-04-20 19:47:37 +00:00
|
|
|
name = obj.name_map.get(self.string_language)
|
|
|
|
if not name and self.game_language:
|
|
|
|
name = obj.name_map.get(self.game_language)
|
|
|
|
if not name:
|
2011-04-20 13:27:57 +00:00
|
|
|
name = obj.name
|
|
|
|
if url:
|
2011-04-20 19:47:37 +00:00
|
|
|
el = self.factory.make_link(category, obj, url, label or name)
|
2011-04-20 13:27:57 +00:00
|
|
|
else:
|
|
|
|
el = markdown.etree.Element('span')
|
|
|
|
el.text = markdown.AtomicString(label or name)
|
|
|
|
return el
|
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
class MarkdownLinkMaker(object):
|
|
|
|
"""Creates Markdown extensions for handling links for the given session.
|
2011-04-20 13:27:57 +00:00
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
There are two ways to customize the link handling: either override the
|
|
|
|
*_url methods in a subclass, or give them as arguments to get_extension
|
|
|
|
(or MarkdownString.as_html).
|
2011-04-20 13:27:57 +00:00
|
|
|
"""
|
2011-04-20 19:47:37 +00:00
|
|
|
def __init__(self, session=None):
|
|
|
|
self.session = session
|
2011-04-20 13:27:57 +00:00
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
def get_extension(self, language=None, object_url=None, identifier_url=None,
|
|
|
|
make_link=None):
|
|
|
|
"""Get a Markdown extension that handles links using the given language.
|
|
|
|
"""
|
|
|
|
link_maker = self
|
|
|
|
class LinkExtension(markdown.Extension):
|
|
|
|
def extendMarkdown(self, md, md_globals):
|
|
|
|
self.identifier_url = identifier_url or link_maker.identifier_url
|
|
|
|
self.object_url = object_url or link_maker.object_url
|
|
|
|
self.make_link = make_link or link_maker.make_link
|
|
|
|
self.session = link_maker.session
|
|
|
|
pattern = PokedexLinkPattern(self, language)
|
|
|
|
md.inlinePatterns['pokedex-link'] = pattern
|
|
|
|
|
|
|
|
return LinkExtension()
|
2011-04-20 13:27:57 +00:00
|
|
|
|
|
|
|
def make_link(self, category, obj, url, text):
|
|
|
|
"""Make an <a> element
|
|
|
|
|
|
|
|
Override this to set custom attributes, e.g. title.
|
|
|
|
"""
|
|
|
|
el = markdown.etree.Element('a')
|
|
|
|
el.set('href', url)
|
|
|
|
el.text = markdown.AtomicString(text)
|
|
|
|
return el
|
|
|
|
|
|
|
|
def identifier_url(self, category, identifier):
|
|
|
|
"""Return the URL for the given {category:identifier} link
|
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
For ORM objects, object_url is tried first
|
2011-04-20 13:27:57 +00:00
|
|
|
|
|
|
|
Returns None by default, which causes <span> to be used in place of <a>
|
|
|
|
"""
|
|
|
|
return None
|
|
|
|
|
|
|
|
def object_url(self, category, obj):
|
|
|
|
"""Return the URL for the ORM object obj
|
|
|
|
|
2011-04-20 19:47:37 +00:00
|
|
|
Returns None by default, which causes identifier_url to be used
|
2011-04-20 13:27:57 +00:00
|
|
|
"""
|
2011-04-20 19:47:37 +00:00
|
|
|
return None
|