Merge branch 'master' of git@veekun.com:pokedex
[zzz-pokedex.git] / pokedex / db / rst.py
index 474a275..9ee84ba 100644 (file)
@@ -33,12 +33,17 @@ are, apparently, global.
     return a reST node.
 """
 
     return a reST node.
 """
 
+import cgi
+
 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
 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
+from docutils.writers import UnfilteredWriter
+
+import sqlalchemy.types
 
 ### Subclasses of bits of docutils, to munge it into doing what I want
 class HTMLFragmentWriter(HTMLWriter):
 
 ### Subclasses of bits of docutils, to munge it into doing what I want
 class HTMLFragmentWriter(HTMLWriter):
@@ -50,6 +55,53 @@ class HTMLFragmentWriter(HTMLWriter):
         subs = self.interpolation_dict()
         return subs['body']
 
         subs = self.interpolation_dict()
         return subs['body']
 
+
+class TextishTranslator(docutils.nodes.SparseNodeVisitor):
+    """A simple translator that tries to return plain text that still captures
+    the spirit of the original (basic) formatting.
+
+    This will probably not be useful for anything complicated; it's only meant
+    for extremely simple text.
+    """
+
+    def __init__(self, document):
+        self.document = document
+        self.translated = u''
+
+    def visit_Text(self, node):
+        """Text is left alone."""
+        self.translated += node.astext()
+
+    def depart_paragraph(self, node):
+        """Append a blank line after a paragraph, unless it's the last of its
+        siblings.
+        """
+        if not node.parent:
+            return
+
+        # Loop over siblings.  If we see a sibling after we see this node, then
+        # append the blank line
+        seen_node = False
+        for sibling in node.parent:
+            if sibling is node:
+                seen_node = True
+                continue
+
+            if seen_node:
+                self.translated += u'\n\n'
+                return
+
+class TextishWriter(UnfilteredWriter):
+    """Translates reST back into plain text, aka more reST.  Difference is that
+    custom roles are handled, so you get "50% chance" instead of junk.
+    """
+
+    def translate(self):
+        visitor = TextishTranslator(self.document)
+        self.document.walkabout(visitor)
+        self.output = visitor.translated
+
+
 class UnicodeOutput(Output):
     """reST Unicode output.  The distribution only has a StringOutput, and I
     want me some Unicode.
 class UnicodeOutput(Output):
     """reST Unicode output.  The distribution only has a StringOutput, and I
     want me some Unicode.
@@ -85,7 +137,7 @@ roles.register_local_role('data', data_role)
 
 class RstString(object):
     """Wraps a reStructuredText string.  Stringifies to the original text, but
 
 class RstString(object):
     """Wraps a reStructuredText string.  Stringifies to the original text, but
-    may be translated to HTML with .to_html().
+    may be translated to HTML with .as_html().
     """
 
     def __init__(self, source_text, document_properties={}):
     """
 
     def __init__(self, source_text, document_properties={}):
@@ -129,16 +181,35 @@ class RstString(object):
         """Returns the string as HTML4."""
 
         document = self.rest_document
         """Returns the string as HTML4."""
 
         document = self.rest_document
-        destination = UnicodeOutput()
 
 
+        # Check for errors; don't want to leave the default error message cruft
+        # in here
+        if document.next_node(condition=docutils.nodes.system_message):
+            # Boo!  Cruft.
+            return u"""
+                <p><em>Error in markup!  Raw source is below.</em></p>
+                <pre>{0}</pre>
+            """.format( cgi.escape(self.source_text) )
+
+        destination = UnicodeOutput()
         writer = HTMLFragmentWriter()
         return writer.write(document, destination)
 
         writer = HTMLFragmentWriter()
         return writer.write(document, destination)
 
+    @property
+    def as_text(self):
+        """Returns the string mostly unchanged, save for our custom roles."""
+
+        document = self.rest_document
+
+        destination = UnicodeOutput()
+        writer = TextishWriter()
+        return writer.write(document, destination)
+
 
 class MoveEffectProperty(object):
     """Property that wraps a move effect.  Used like this:
 
 
 class MoveEffectProperty(object):
     """Property that wraps a move effect.  Used like this:
 
-        MoveClass.effect = MoveEffectProperty()
+        MoveClass.effect = MoveEffectProperty('effect')
 
         some_move.effect            # returns an RstString
         some_move.effect.as_html    # returns a chunk of HTML
 
         some_move.effect            # returns an RstString
         some_move.effect.as_html    # returns a chunk of HTML
@@ -162,3 +233,17 @@ class MoveEffectProperty(object):
         return RstString(getattr(move.move_effect, self.effect_column),
                          document_properties=dict(
                              _pokedex_handle_data=data_role_func))
         return RstString(getattr(move.move_effect, self.effect_column),
                          document_properties=dict(
                              _pokedex_handle_data=data_role_func))
+
+class RstTextColumn(sqlalchemy.types.TypeDecorator):
+    """Generic column type for reST text.
+
+    Do NOT use this for move effects!  They need to know what move they belong
+    to so they can fill in, e.g., effect chances.
+    """
+    impl = sqlalchemy.types.Unicode
+
+    def process_bind_param(self, value, dialect):
+        return unicode(value)
+
+    def process_result_value(self, value, dialect):
+        return RstString(value)