# Python 2.7 Directives from __future__ import absolute_import # Python 2.7 Standard Library import ast import doctest import json from subprocess import Popen, PIPE import sys # Local Library import pandoc from pandoc.types import * # This doctest extension require pandoc 1.16 from subprocess import Popen, PIPE p = Popen(["pandoc", "-v"], stdout=PIPE) if b"pandoc 1.16" not in p.communicate()[0]: raise RuntimeError("pandoc 1.16 not found") # TODO: # - optional round-trip check (global setting) # - "inject" pandoc.read errors in the doctest so that we can catch them. # Mmmm we probably can't do it, unless we do some fugly stack patching. # So what ? pandoc.read on any output JSON generated by pandoc is not # supposed to fail ... Implement a "safe" read on top of it ? # Return a reference error message ? (maybe the full traceback?) # And make sure that after that, it is properly handled? # ------------------------------------------------------------------------------ # Global Variables check_round_trip = True # New Doctest Directive: PANDOC PANDOC = doctest.register_optionflag("PANDOC") doctest.PANDOC = PANDOC doctest.__all__.append("PANDOC") doctest.COMPARISON_FLAGS = doctest.COMPARISON_FLAGS | PANDOC # Helpers from subprocess import Popen, PIPE import json def to_json(txt): p = Popen(["pandoc", "-tjson"], stdout=PIPE, stdin=PIPE, stderr=PIPE) json_string = p.communicate(input=txt.encode("utf-8"))[0].decode("utf-8") json_doc = json.loads(json_string) return json_doc def linebreak(text, length=80): text = text.replace(u"\n", "") chunks = [text[i:i+length] for i in range(0, len(text), length)] return "\n".join(chunks) + "\n" # Implement the pandoc output checker and monkey-patch doctest: _doctest_OutputChecker = doctest.OutputChecker class PandocOutputChecker(_doctest_OutputChecker): def round_trip_check(self, json_doc): json_doc_2 = None try: doc = pandoc.read(json_doc) json_doc_2 = pandoc.write(doc) except Exception as error: jsond_doc_2 = error if json_doc == json_doc_2 : return True else: return json_doc, json_doc_2 def text_repr_to_docs(self, text_repr): text = ast.literal_eval(text_repr) if not isinstance(text, str): raise TypeError("{0!r} is not a string".format(text)) json_doc = to_json(text) doc = pandoc.read(json_doc) return doc, json_doc def str_error(self, error): message = "" if len(error.args): message = error.args[0] return "Traceback (most recent call last)\n" + \ " ...\n" + \ "{0}: {1}\n".format(type(error).__name__, message) # TODO: need an eval-based checker for Python2/3 compatibility. def check_output(self, want, got, optionflags): if optionflags & PANDOC: try: want = eval(want.replace("\n", "")) doc, json_doc = self.text_repr_to_docs(got) got = doc return got == want except Exception as error: got = self.str_error(error) return False super_check_output = _doctest_OutputChecker.check_output check = super_check_output(self, want, got, optionflags) if optionflags & PANDOC & check_round_trip: check = check and (self.round_trip_check(json_got) is True) return check def output_difference(self, example, got, optionflags): if optionflags & PANDOC: example.want = linebreak(example.want, 76) doc, json_doc = None, None try: doc, json_doc = self.text_repr_to_docs(got) got = linebreak(repr(doc), 76) if check_round_trip: check = self.round_trip_check(json_doc) if check is not True: json_doc, json_doc_2 = check json_doc = linebreak(repr(json_doc), 72).strip() if isinstance(json_doc_2, Exception): json_doc_2 = self.str_error(json_doc_2) json_doc_2 = "\n".join([4*" " + line for line in json_doc2.split("\n")]) else: json_doc_2 = linebreak(repr(json_doc_2), 72).strip() got = ("Traceback (most recent call last)\n" +\ " ....\n" +\ "ValueError: failed JSON read+write round-trip:\n" +\ " {0}\n" +\ "vs:\n {1}\n").format(json_doc, json_doc_2) except Exception as error: got = self.str_error(error) super_output_difference = _doctest_OutputChecker.output_difference return super_output_difference(self, example, got, optionflags) doctest.OutputChecker = PandocOutputChecker