From 928eaca4a45860646ca2e1a436136412c9b8c07d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 12 Sep 2011 18:56:23 +0300 Subject: [PATCH] Make bad links in Markdown not fail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Links such as []{pokemon:mewthree} can come from users, so they should not crash the parser. So, when an object is not found (or more than one is found), call identifier_url() directly, instead of failing to get the object for object_url(). Essentially, treat the link as having an unknown category (like mechanic:, currently). The test that check the pokédex descriptions updated so that only links to known objects and "mechanic:" are allowed. --- pokedex/db/markdown.py | 42 ++++++++++++++++++++++------------- pokedex/tests/test_strings.py | 39 ++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/pokedex/db/markdown.py b/pokedex/db/markdown.py index 52c3a9e..dc0e2cf 100644 --- a/pokedex/db/markdown.py +++ b/pokedex/db/markdown.py @@ -170,21 +170,27 @@ class PokedexLinkPattern(markdown.inlinepatterns.Pattern): query = query.join(tables.Pokemon.species) query = query.filter( tables.PokemonSpecies.identifier == pokemon_ident) - obj = query.one() else: - obj = util.get(self.session, table, target) - url = self.factory.object_url(category, obj) - url = url or self.factory.identifier_url(category, target) - name = None - # Translations can be incomplete; in which case we want to use a - # fallback. - if table in [tables.Type] and self.string_language: - # Type wants to be localized to the same language as the text - 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: - name = obj.name + query = session.query(table) + query = query.filter(table.identifier == target) + try: + obj = query.one() + except Exception: + obj = name = target + url = self.factory.identifier_url(category, obj) + else: + url = self.factory.object_url(category, obj) + url = url or self.factory.identifier_url(category, target) + name = None + # Translations can be incomplete; in which case we want to use + # a fallback. + if table in [tables.Type] and self.string_language: + # Type wants to be localized to the text language + 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: + name = obj.name if url: el = self.factory.make_link(category, obj, url, label or name) else: @@ -222,12 +228,18 @@ class PokedexLinkExtension(markdown.Extension): Returns None by default, which causes to be used in place of . + + This method is also called for non-existent objects, e.g. + []{pokemon:bogus}. """ return None def object_url(self, category, obj): - """Return the URL for the ORM object `obj`. + u"""Return the URL for the ORM object `obj`. Returns None by default, which causes identifier_url to be tried. + + Note that obj may be a Pokémon form. Unlike other returned objects, + these do not have identifiers. Be sure to test this case. """ return None diff --git a/pokedex/tests/test_strings.py b/pokedex/tests/test_strings.py index 782f651..a0e6381 100644 --- a/pokedex/tests/test_strings.py +++ b/pokedex/tests/test_strings.py @@ -92,9 +92,9 @@ def test_markdown(): def test_markdown_string(): en = util.get(connection, tables.Language, 'en') - md = markdown.MarkdownString('[]{move:thunderbolt} [paralyzes]{mechanic:paralysis} []{form:sky shaymin}', connection, en) - assert unicode(md) == 'Thunderbolt paralyzes Sky Shaymin' - assert md.as_html() == '

Thunderbolt paralyzes Sky Shaymin

' + md = markdown.MarkdownString('[]{move:thunderbolt} [paralyzes]{mechanic:paralysis} []{form:sky shaymin}. []{pokemon:mewthree} does not exist.', connection, en) + assert unicode(md) == 'Thunderbolt paralyzes Sky Shaymin. mewthree does not exist.' + assert md.as_html() == '

Thunderbolt paralyzes Sky Shaymin. mewthree does not exist.

' class ObjectTestExtension(markdown.PokedexLinkExtension): def object_url(self, category, obj): @@ -106,12 +106,12 @@ def test_markdown_string(): class IdentifierTestExtension(markdown.PokedexLinkExtension): def identifier_url(self, category, ident): - return "%s/%s" % (category, ident) + return "%s/%s" % (category, ident) assert md.as_html(extension_cls=ObjectTestExtension) == ( - '

Thunderbolt paralyzes Sky Shaymin

') + '

Thunderbolt paralyzes Sky Shaymin. mewthree does not exist.

') assert md.as_html(extension_cls=IdentifierTestExtension) == ( - '

Thunderbolt paralyzes Sky Shaymin

') + '

Thunderbolt paralyzes Sky Shaymin. mewthree does not exist.

') def markdown_column_params(): """Check all markdown values @@ -136,23 +136,28 @@ def test_markdown_values(parent_class, translation_class, column_name): query = connection.query(parent_class) if translation_class: query = query.join(translation_class) - for item in query: - for language, markdown in getattr(item, column_name + '_map').items(): - if markdown is None: + for item in query: + for language, md_text in getattr(item, column_name + '_map').items(): + + if md_text is None: continue key = u"Markdown in {0} #{1}'s {2} (lang={3})".format( parent_class.__name__, item.id, column_name, language.identifier) - try: - text = markdown.as_text() - except NoResultFound: - assert False, u"{0} references something that doesn't exist:\n{1}".format( - key, markdown.source_text) - except AttributeError: - print markdown - raise + class TestExtension(markdown.PokedexLinkExtension): + def object_url(self, category, obj): + "Swallow good links" + return 'ok' + + def identifier_url(self, category, ident): + "Only allow mechanic links here (good links handled in object_url)" + assert category == 'mechanic', ( + '%s: unknown link target: {%s:%s}' % + (key, category, ident)) + + text = md_text.as_html(extension_cls=TestExtension) error_message = u"{0} leaves syntax cruft:\n{1}" error_message = error_message.format(key, text)