Don't add indentation to blank lines. Only warn if output has same age.
[pylit.git] / test / pylit_test.py
blob3cf9b98845619524e8d2915572394a62bf645fa3
1 #!/usr/bin/env python3
3 ## pylit_test.py
4 ## *************
5 ## Test pylit.py Python Module
6 ## +++++++++++++++++++++++++++
7 ##
8 ## :Copyright: 2006 Guenter Milde.
9 ## Released under the terms of the GNU General Public License
10 ## (v. 2 or later)
12 ## .. contents::
14 ## A catalogue of errors
15 ## =====================
17 ## from file:///home/milde/Texte/Doc/Programmierung/Software-Carpentry/lec/unit.html
19 ## * Numbers: zero, largest, smallest magnitude, most negative
20 ## * Structures: empty, exactly one element, maximum number of elements
21 ## - Duplicate elements (e.g., letter "J" appears three times in a string)
22 ## - Aliased elements (e.g., a list contains two references to another list)
23 ## - Circular structures (e.g., a list that contains a reference to itself)
24 ## * Searching: no match found, one match found, multiple matches found,
25 ## everything matches
26 ## - Code like x = find_all(structure)[0] is almost always wrong
27 ## - Should also check aliased matches (same thing found multiple times)
29 ## ::
31 """pylit_test.py: test the "literal python" module"""
33 from pprint import pprint
34 import operator
35 from pylit import *
36 import nose
39 ## Text <-> Code conversion
40 ## ========================
42 ## Test strings
43 ## ============
45 ## Example of text, code and stripped code with typical features"::
47 text = """.. #!/usr/bin/env python
48 # -*- coding: iso-8859-1 -*-
50 Leading text
52 in several paragraphs followed by a literal block::
54 block1 = 'first block'
56 Some more text and the next block. ::
58 block2 = 'second block'
59 print(block1, block2)
61 Trailing text.
62 """
64 ## The converter expects the data in separate lines (iterator or list)
65 ## with trailing newlines. We use the `splitlines` string method with
66 ## `keepends=True`::
68 textdata = text.splitlines(True)
70 ## If a "code" source is converted with the `strip` option, only text blocks
71 ## are extracted, which leads to::
73 stripped_text = """Leading text
75 in several paragraphs followed by a literal block:
77 Some more text and the next block.
79 Trailing text.
80 """
82 ## The code corresponding to the text test string.
84 ## Using a triple-quoted string for the code (and stripped_code) can create
85 ## problems with the conversion of this test by pylit (as the text parts
86 ## would be converted to text).
87 ## A workaround is using a different comment string for the text blocks and
88 ## converting with e.g. ``pylit --comment-string='## ' pylit_test.py``.
90 ## ::
92 code = """#!/usr/bin/env python
93 # -*- coding: iso-8859-1 -*-
95 # Leading text
97 # in several paragraphs followed by a literal block::
99 block1 = 'first block'
101 # Some more text and the next block. ::
103 block2 = 'second block'
104 print(block1, block2)
106 # Trailing text.
109 codedata = code.splitlines(True)
111 ## Converting the text teststring with the `strip` option leads to::
113 stripped_code = """#!/usr/bin/env python
114 # -*- coding: iso-8859-1 -*-
116 block1 = 'first block'
118 block2 = 'second block'
119 print(block1, block2)
123 ## pprint(textdata)
124 ## pprint(stripped_code.splitlines(True))
126 ## Containers for special case examples:
128 ## 1. Text2Code samples
129 ## ``textsamples["what"] = (<text data>, <output>, <output (with `strip`)``
130 ## ::
132 textsamples = {}
134 ## 2. Code2Text samples
135 ## ``codesamples["what"] = (<code data>, <output>, <output (with `strip`)``
136 ## ::
138 codesamples = {}
140 ## Auxiliary function to test the textsamples and codesamples::
142 def check_converter(key, converter, output):
143 print("E:", key)
144 extract = converter()
145 print(extract)
146 outstr = "".join(extract)
147 print("soll:", repr(output))
148 print("ist: ", repr(outstr))
149 assert output == outstr
151 ## Test generator for textsample tests::
153 def test_Text2Code_samples():
154 for key, sample in textsamples.items():
155 yield (check_converter, key,
156 Text2Code(sample[0].splitlines(True)), sample[1])
157 if len(sample) == 3:
158 yield (check_converter, key,
159 Text2Code(sample[0].splitlines(True), strip=True),
160 sample[2])
162 ## Test generator for codesample tests::
164 def test_Code2Text_samples():
165 for key, sample in codesamples.items():
166 yield (check_converter, key,
167 Code2Text(sample[0].splitlines(True)), sample[1])
168 if len(sample) == 3:
169 yield (check_converter, key,
170 Code2Text(sample[0].splitlines(True), strip=True),
171 sample[2])
173 ## Pre and postprocessing filters (for testing the filter hooks)
175 ## ::
177 def r2l_filter(data):
178 print("applying r2l filter")
179 for line in data:
180 yield line.replace("r", "l")
182 ## ::
184 defaults.preprocessors["rl2text"] = r2l_filter
186 ## ::
188 def l2r_filter(data):
189 print("applying l2r filter")
190 for line in data:
191 yield line.replace("l", "r")
193 ## ::
195 defaults.preprocessors["text2rl"] = l2r_filter
197 ## ::
199 def x2u_filter(data):
200 print("applying x2u filter")
201 for line in data:
202 yield line.replace("x", "u")
204 ## ::
206 defaults.postprocessors["x2text"] = x2u_filter
208 ## ::
210 def u2x_filter(data):
211 print("applying u2x filter")
212 for line in data:
213 yield line.replace("u", "x")
215 ## ::
217 defaults.postprocessors["text2x"] = u2x_filter
219 ## ::
221 def test_x2u_filter():
222 soll = text.replace("x", "u")
223 result = "".join([line for line in x2u_filter(textdata)])
224 print("soll", repr(text))
225 print("ist", repr(result))
226 assert soll == result
230 ## TextCodeConverter
231 ## =================
233 ## ::
235 class test_TextCodeConverter(object):
236 """Test the TextCodeConverter parent class
239 ## ::
241 def check_marker_regexp_true(self, sample, converter):
242 match = converter.marker_regexp.search(sample)
243 print('marker: %r; sample %r' %(converter.code_block_marker, sample))
244 print('match %r'%match)
245 assert match is not None
247 ## ::
249 def check_marker_regexp_false(self, sample, converter):
250 print('marker: %r; sample %r' %(converter.code_block_marker, sample))
251 assert converter.marker_regexp.search(sample) is None
253 ## ::
255 def test_marker_regexp(self):
256 # Samples
257 literal = ['::',
258 ' ::',
259 't ::',
260 'text::',
261 ' indented::',
262 ' indented ::',
263 'more text :: ',
264 ' indented text :: ',
265 '. no-directive::',
266 'a .. directive:: somewhere::']
267 directives = ['.. code-block:: python',
268 ' .. code-block:: python',
269 '.. code-block:: python listings',
270 ' .. code-block:: python listings']
271 misses = ['.. comment string ::',
272 '.. ::',
273 'text:']
274 # default code_block_marker ('::')
275 self.converter = TextCodeConverter(textdata)
276 assert self.converter.code_block_marker == '::'
277 # self.converter is not seen by the check_marker_regexp_true() method
278 for sample in literal:
279 yield (self.check_marker_regexp_true, sample, self.converter)
280 for sample in directives+misses:
281 yield (self.check_marker_regexp_false, sample, self.converter)
282 # code-block directive as marker
283 self.converter = TextCodeConverter(textdata,
284 code_block_marker='.. code-block::')
285 assert self.converter.code_block_marker == '.. code-block::'
286 for sample in directives:
287 yield (self.check_marker_regexp_true, sample, self.converter)
288 for sample in literal+misses:
289 yield (self.check_marker_regexp_false, sample, self.converter)
291 ## ::
293 def test_get_indent(self):
294 converter = TextCodeConverter(textdata)
295 assert converter.get_indent("foo") == 0
296 assert converter.get_indent(" foo") == 1
297 assert converter.get_indent(" foo") == 2
299 ## ::
301 def test_collect_blocks(self):
302 converter = TextCodeConverter(textdata)
303 textblocks = [block for block in collect_blocks(textdata)]
304 print(textblocks)
305 assert len(textblocks) == 7, "text sample has 7 blocks"
306 # assert reduce(operator.__add__, textblocks) == textdata
307 assert [line for textblock in textblocks
308 for line in textblock] == textdata
310 ## Text2Code
311 ## =========
313 ## ::
315 class test_Text2Code(object):
316 """Test the Text2Code class converting rst->code"""
318 ## ::
320 def setUp(self):
321 self.converter = Text2Code(textdata)
323 ## test helper funs ::
325 def test_set_state_empty(self):
326 try:
327 self.converter.set_state([])
328 raise AssertionError("should raise StopIteration")
329 except StopIteration:
330 pass
332 def test_set_state_header(self):
333 """test for "header" or "documentation" for first block"""
334 self.converter.state = "" # normally set by the `convert` method
335 self.converter.set_state([".. header", " block"])
336 assert self.converter.state == "header"
337 self.converter.state = "" # normally set by the `convert` method
338 self.converter.set_state(["documentation", "block"])
339 assert self.converter.state == "documentation"
341 def test_set_state_code_block(self):
342 """test for "header" or "documentation" for "code_block" """
343 # normally set by the `convert` method
344 self.converter._textindent = 0
345 self.converter.state = "code_block"
346 self.converter.set_state(["documentation", " block"])
347 assert self.converter.state == "documentation"
349 self.converter.state = "code_block"
350 self.converter.set_state([" documentation", "block"])
351 assert self.converter.state == "documentation"
353 self.converter.state = "code_block"
354 self.converter.set_state([" code", " block"])
355 print(self.converter.state)
356 assert self.converter.state == "code_block"
358 def test_header_handler(self):
359 """should strip header-string from header"""
360 self.converter._codeindent = 0
361 sample = [".. header", " block"]
362 lines = [line for line in self.converter.header_handler(sample)]
363 print(lines)
364 assert lines == ["header", "block"]
366 def test_documentation_handler(self):
367 """should add comment string to documentation"""
368 sample = ["doc", "block", ""]
369 lines = [line for line
370 in self.converter.documentation_handler(sample)]
371 print(lines)
372 assert lines == ["# doc", "# block", "#"]
374 def test_documentation_handler_set_state(self):
375 """should add comment string to documentation"""
376 sample = ["doc", "block::", ""]
377 lines = [line for line
378 in self.converter.documentation_handler(sample)]
379 print(lines)
380 assert lines == ["# doc", "# block::", ""]
381 assert self.converter.state == "code_block"
383 def test_code_block_handler(self):
384 """should un-indent code-blocks"""
385 self.converter._codeindent = 0 # normally set in `convert`
386 sample = [" code", " block", ""]
387 lines = [line for line
388 in self.converter.code_block_handler(sample)]
389 print(lines)
390 assert lines == ["code", "block", ""]
393 ## base tests on the "long" test data ::
395 def test_call(self):
396 """Calling a Text2Code instance should return the converted data as list of lines"""
397 output = self.converter()
398 print(repr(codedata))
399 print(repr(output))
400 assert codedata == output
402 def test_call_strip(self):
403 """strip=True should strip text parts"""
404 self.converter.strip = True
405 output = self.converter()
406 print(repr(stripped_code.splitlines(True)))
407 print(repr(output))
408 assert stripped_code.splitlines(True) == output
410 def test_str(self):
411 outstr = str(self.converter)
412 print(repr(code))
413 print(repr(outstr))
414 assert code == outstr
416 def test_str_strip1(self):
417 """strip=True should strip text parts.
419 Version 1 with `strip` given as optional argument"""
420 outstr = str(Text2Code(textdata, strip=True))
421 print("ist ", repr(outstr))
422 print("soll", repr(stripped_code))
423 # pprint(outstr)
424 assert stripped_code == outstr
426 def test_str_strip2(self):
427 """strip=True should strip text parts
429 Version 2 with `strip` set after instantiation"""
430 self.converter.strip = True
431 outstr = str(self.converter)
432 print("ist ", repr(outstr))
433 print("soll", repr(stripped_code))
434 # pprint(outstr)
435 assert stripped_code == outstr
437 def test_malindented_code_line(self):
438 """raise error if code line is less indented than code-indent"""
439 data1 = [".. #!/usr/bin/env python\n", # indent == 4 * " "
440 "\n",
441 " print('hello world'"] # indent == 2 * " ")
442 data2 = ["..\t#!/usr/bin/env python\n", # indent == 8 * " "
443 "\n",
444 " print('hello world'"] # indent == 2 * " ")
445 for data in (data1, data2):
446 try:
447 blocks = Text2Code(data)()
448 assert False, "wrong indent did not raise ValueError"
449 except ValueError:
450 pass
452 def test_str_different_comment_string(self):
453 """Convert only comments with the specified comment string to text
455 data = [".. #!/usr/bin/env python\n",
456 '\n',
457 '::\n', # leading code block as header
458 '\n',
459 " block1 = 'first block'\n",
460 '\n',
461 'more text']
462 soll = "\n".join(["#!/usr/bin/env python",
464 "##::",
466 "block1 = 'first block'",
468 "##more text"]
470 outstr = str(Text2Code(data, comment_string="##"))
471 print("soll:", repr(soll))
472 print("ist: ", repr(outstr))
473 assert outstr == soll
475 # Filters: test pre- and postprocessing of data
477 def test_get_filter_preprocessor(self):
478 """should return filter from filter_set for language"""
479 preprocessor = self.converter.get_filter("preprocessors", "rl")
480 print(preprocessor)
481 assert preprocessor == l2r_filter
483 def test_get_filter_postprocessor(self):
484 """should return filter from filter_set for language"""
485 postprocessor = self.converter.get_filter("postprocessors", "x")
486 print(postprocessor)
487 assert postprocessor == u2x_filter
489 def test_get_css_postprocessor(self):
490 """should return filter from filter_set for language"""
491 postprocessor = self.converter.get_filter("postprocessors", "css")
492 print(postprocessor)
493 assert postprocessor == dumb_c_postprocessor
495 def test_get_filter_nonexisting_language_filter(self):
496 """should return identity_filter if language has no filter in set"""
497 preprocessor = self.converter.get_filter("preprocessors", "foo")
498 print(preprocessor)
499 assert preprocessor == identity_filter
501 def test_get_filter_nonexisting_filter_set(self):
502 """should return identity_filter if filter_set does not exist"""
503 processor = self.converter.get_filter("foo_filters", "foo")
504 print(processor)
505 assert processor == identity_filter
507 def test_preprocessor(self):
508 """Preprocess data with registered preprocessor for language"""
509 output = Text2Code(textdata, language="x", comment_string="# ")()
510 soll = [line for line in u2x_filter(codedata)]
511 print("soll: ", repr(soll))
512 print("ist: ", repr(output))
513 assert output == soll
515 def test_postprocessor(self):
516 """Preprocess data with registered postprocessor for language"""
517 output = Text2Code(textdata, language="x", comment_string="# ")()
518 soll = [line for line in u2x_filter(codedata)]
519 print("soll:", repr(soll))
520 print("ist: ", repr(output))
521 assert output == soll
523 ## Special Cases
524 ## -------------
526 ## Code follows text block without blank line
527 ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
529 ## End of text block detected ('::') but no paragraph separator (blank line)
530 ## follows
532 ## It is an reStructuredText syntax error, if a "literal block
533 ## marker" is not followed by a blank line.
535 ## Assuming that no double colon at end of line occurs accidentally,
536 ## pylit could fix this and issue a warning::
538 # Do we need this feature? (Complicates code a lot)
539 # textsamples["ensure blank line after text"] = (
540 # """text followed by a literal block::
541 # block1 = 'first block'
542 # """,
543 # """# text followed by a literal block::
545 # block1 = 'first block'
546 # """)
548 ## Text follows code block without blank line
549 ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
551 ## End of code block detected (a line not more indented than the preceding text
552 ## block)
554 ## reStructuredText syntax demands a paragraph separator (blank line) before
555 ## it.
557 ## Assuming that the unindent is not accidental, pylit could fix this and
558 ## issues a warning::
560 # Do we need this feature? (Complicates code)
561 # textsamples["ensure blank line after code"] = (
562 # """::
564 # block1 = 'first block'
565 # more text
566 # """,
567 # """# ::
569 # block1 = 'first block'
571 # more text
572 # """)
574 ## Options follow code-block directive
575 ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
577 textsamples["code-block directive options"] = (
578 """\
580 :option: argument
582 this = 'code'
583 """,
584 """\
585 # ::
586 # :option: argument
588 this = 'code'
589 """)
591 textsamples["no code-block directive options"] = (
592 """\
594 text following ``::`` without blank line
596 more documentation
597 """,
598 """\
599 # ::
600 # text following ``::`` without blank line
602 # more documentation
603 """)
605 ## A double colon on a line on its own
606 ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
608 ## As a double colon is added by the Code2Text conversion after a text block
609 ## (if not already present), it could be removed by the Text2Code conversion
610 ## to keep the source small and pretty.
612 ## However, this would put the text and code source line numbers out of sync,
613 ## which is bad for error reporting, failing doctests, and the JED editor
614 ## support with the `pylit_buffer()` function in
615 ## https://jedmodes.sourceforge.io/mode/pylit/.
617 ## Maybe this could be left to a post-processing filter::
619 # textsamples["remove single double colon"] = (
620 # ["text followed by a literal block\n",
621 # "\n",
622 # "::\n",
623 # "\n",
624 # " foo = 'first'\n"]
625 # ["", # empty header
626 # "# text followed by a literal block\n\n",
627 # "foo = 'first'\n"]
629 ## header samples
630 ## ~~~~~~~~~~~~~~
631 ## Convert a leading reStructured text comment (variant: only if there is
632 ## content on the first line) to a leading code block. Return an empty list,
633 ## if there is no header. ::
635 textsamples["simple header"] = (".. print('hello world')",
636 "print('hello world')")
638 textsamples["no header (start with text)"] = (
639 """a classical example without header::
641 print('hello world')
642 """,
643 """# a classical example without header::
645 print('hello world')
646 """)
649 textsamples["no header (start with blank line)"] = (
651 a classical example without header::
653 print('hello world')
654 """,
655 """#
656 # a classical example without header::
658 print('hello world')
659 """)
662 textsamples["standard header, followed by text"] = (
663 """.. #!/usr/bin/env python
664 # -*- coding: iso-8859-1 -*-
666 a classical example with header::
668 print('hello world')
669 """,
670 """#!/usr/bin/env python
671 # -*- coding: iso-8859-1 -*-
673 # a classical example with header::
675 print('hello world')
676 """)
678 textsamples["standard header, followed by code"] = (
679 """.. #!/usr/bin/env python
681 print('hello world')
682 """,
683 """#!/usr/bin/env python
685 print('hello world')
686 """)
688 textsamples["null string"] = ("", "", "")
690 ## Code2Text
691 ## =========
693 ## ::
695 class test_Code2Text(object):
697 def setUp(self):
698 self.converter = Code2Text(codedata)
700 ## Code2Text.strip_literal_marker
702 ## * strip `::`-line as well as preceding blank line if on a line on its own
703 ## * strip `::` if it is preceded by whitespace.
704 ## * convert `::` to a single colon if preceded by text
706 ## ::
707 def check_strip_code_block_marker(self, sample):
708 """test Code2Text.strip_code_block_marker"""
709 ist = sample[0].splitlines(True)
710 soll = sample[1].splitlines(True)
711 print("before", ist)
712 converter = Code2Text(codedata)
713 converter.strip_code_block_marker(ist)
714 print("soll:", repr(soll))
715 print("ist: ", repr(ist))
716 assert ist == soll
719 def test_strip_code_block_marker(self):
720 samples = (("text\n\n::\n\n", "text\n\n"),
721 ("text\n::\n\n", "text\n\n"),
722 ("text ::\n\n", "text\n\n"),
723 ("text::\n\n", "text:\n\n"),
724 ("text:\n\n", "text:\n\n"),
725 ("text\n\n", "text\n\n"),
726 ("text\n", "text\n")
728 for sample in samples:
729 yield (self.check_strip_code_block_marker, sample)
731 ## Code2Text.set_state
732 ## ::
734 def test_set_state(self):
735 samples = (("code_block", ["code_block\n"], "code_block"),
736 ("code_block", ["#code_block\n"], "code_block"),
737 ("code_block", ["## code_block\n"], "code_block"),
738 ("code_block", ["# documentation\n"], "documentation"),
739 ("code_block", ["# documentation\n"], "documentation"),
740 ("code_block", ["# \n"], "documentation"),
741 ("code_block", ["#\n"], "documentation"),
742 ("code_block", ["\n"], "documentation"),
743 ("", ["code_block\n"], "header"),
744 ("", ["# documentation\n"], "documentation"),
745 ("documentation", ["code_block\n"], "code_block"),
746 ("documentation", ["# documentation\n"], "documentation"),
748 print("comment string", repr(self.converter.comment_string))
749 for (old_state, lines, soll) in samples:
750 self.converter.state = old_state
751 self.converter.set_state(lines)
752 print(repr(lines), "old state", old_state)
753 print("soll", repr(soll),)
754 print("result", repr(self.converter.state))
755 assert soll == self.converter.state
757 ## base tests on the "long" test strings ::
759 def test_call(self):
760 output = self.converter()
761 print(repr(textdata))
762 print(repr(output))
763 assert textdata == output
765 def test_call_strip(self):
766 output = Code2Text(codedata, strip=True)()
767 print(repr(stripped_text.splitlines(True)))
768 print(repr(output))
769 assert stripped_text.splitlines(True) == output
771 def test_str(self):
772 """Test Code2Text class converting code->text"""
773 outstr = str(self.converter)
774 # print(text)
775 print("soll:", repr(text))
776 print("ist: ", repr(outstr))
777 assert text == outstr
779 def test_str_strip(self):
780 """Test Code2Text class converting code->rst with strip=True
782 Should strip code blocks
784 outstr = str(Code2Text(codedata, strip=True))
785 print(repr(stripped_text))
786 print(repr(outstr))
787 assert stripped_text == outstr
789 def test_str_different_comment_string(self):
790 """Convert only comments with the specified comment string to text
792 outstr = str(Code2Text(codedata, comment_string="##", strip=True))
793 print(outstr)
794 assert outstr == ""
795 data = ['# ::\n',
796 '\n',
797 'block1 = "first block"\n',
798 '\n',
799 '## more text']
800 soll = ('.. # ::\n' # leading code block as header
801 '\n'
802 ' block1 = "first block"\n'
803 '\n'
804 ' more text' # keep space (not part of comment string)
806 outstr = str(Code2Text(data, comment_string="##"))
807 print("soll:", repr(soll))
808 print("ist: ", repr(outstr))
809 assert outstr == soll
811 def test_call_different_code_block_marker(self):
812 """recognize specified code-block marker
814 data = ["# .. code-block:: python\n",
815 "\n",
816 "block1 = 'first block'\n",
817 "\n",
818 "# more text\n"]
819 soll = ['.. code-block:: python\n',
820 '\n',
821 " block1 = 'first block'\n",
822 '\n',
823 ' more text\n'] # keep space (not part of comment string)
825 converter = Code2Text(data, code_block_marker='.. code-block::')
826 output = converter()
827 print("soll:", repr(soll))
828 print("ist: ", repr(output))
829 assert output == soll
831 # Filters: test pre- and postprocessing of Code2Text data conversion
833 def test_get_filter_preprocessor(self):
834 """should return Code2Text preprocessor for language"""
835 preprocessor = self.converter.get_filter("preprocessors", "rl")
836 print(preprocessor)
837 assert preprocessor == r2l_filter
839 def test_get_css_preprocessor(self):
840 """should return filter from filter_set for language"""
841 preprocessor = self.converter.get_filter("preprocessors", "css")
842 print(preprocessor)
843 assert preprocessor == dumb_c_preprocessor
845 def test_get_filter_postprocessor(self):
846 """should return Code2Text postprocessor for language"""
847 postprocessor = self.converter.get_filter("postprocessors", "x")
848 print(postprocessor)
849 assert postprocessor == x2u_filter
851 def test_get_filter_nonexisting_language_filter(self):
852 """should return identity_filter if language has no filter in set"""
853 preprocessor = self.converter.get_filter("preprocessors", "foo")
854 print(preprocessor)
855 assert preprocessor == identity_filter
857 def test_get_filter_nonexisting_filter_set(self):
858 """should return identity_filter if filter_set does not exist"""
859 processor = self.converter.get_filter("foo_filters", "foo")
860 print(processor)
861 assert processor == identity_filter
863 def test_preprocessor(self):
864 """Preprocess data with registered preprocessor for language"""
865 converter = Code2Text(codedata, language="rl", comment_string="# ")
866 print("preprocessor", converter.preprocessor)
867 print("postprocessor", converter.postprocessor)
868 output = converter()
869 soll = [line.replace("r", "l") for line in textdata]
870 print("ist: ", repr(output))
871 print("soll:", repr(soll))
872 assert output == soll
874 def test_postprocessor(self):
875 """Postprocess data with registered postprocessor for language"""
876 output = Code2Text(codedata, language="x", comment_string="# ")()
877 soll = [line.replace("x", "u") for line in textdata]
878 print("soll:", repr(soll))
879 print("ist: ", repr(output))
880 assert output == soll
883 ## Special cases
884 ## -------------
886 ## blank comment line
887 ## ~~~~~~~~~~~~~~~~~~
889 ## Normally, whitespace in the comment string is significant, i.e. with
890 ## ``comment_string = "# "``, a line ``"#something\n"`` will count as code.
892 ## However, if a comment line is blank, trailing whitespace in the comment
893 ## string should be ignored, i.e. ``#\n`` is recognised as a blank text line::
895 codesamples["ignore trailing whitespace in comment string for blank line"] = (
896 """# ::
898 block1 = 'first block'
901 # more text
902 """,
903 """::
905 block1 = 'first block'
908 more text
909 """)
911 ## No blank line after text
912 ## ~~~~~~~~~~~~~~~~~~~~~~~~
914 ## If a matching comment precedes or follows a code line (i.e. any line
915 ## without matching comment) without a blank line in between, it counts as code
916 ## line.
918 ## This will keep small inline comments close to the code they comment on. It
919 ## will also keep blocks together where one commented line does not match the
920 ## comment string (the whole block will be kept as commented code)
921 ## ::
923 codesamples["comment before code (without blank line)"] = (
924 """\
925 # this is text::
927 # this is a comment
928 foo = 'first'
929 """,
930 """\
931 this is text::
933 # this is a comment
934 foo = 'first'
935 """,
936 """\
937 this is text:
939 """)
941 codesamples["comment block before code (without blank line)"] = (
942 """\
943 # no text (watch the comment sign in the next line)::
945 # this is a comment
946 foo = 'first'
947 """,
948 """\
949 .. # no text (watch the comment sign in the next line)::
951 # this is a comment
952 foo = 'first'
953 """,
956 codesamples["comment after code (without blank line)"] = (
957 """\
958 # ::
960 block1 = 'first block'
961 # commented code
963 # text again
964 """,
965 """\
968 block1 = 'first block'
969 # commented code
971 text again
972 """,
974 text again
975 """)
977 codesamples["comment block after code (without blank line)"] = (
978 """\
979 # ::
981 block1 = 'first block'
982 # commented code
984 # still comment
985 """,
986 """::
988 block1 = 'first block'
989 # commented code
991 # still comment
992 """,
994 """)
996 ## missing literal block marker
997 ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
999 ## If text (with matching comment string) is followed by code (line(s) without
1000 ## matching comment string), but there is no double colon at the end, back
1001 ## conversion would not recognise the end of text!
1003 ## Therefore, pylit adds a paragraph containing only ``::`` -- the literal
1004 ## block marker in expanded form. (While it would in many cases be nicer to
1005 ## add the double colon to the last text line, this is not always valid rst
1006 ## syntax, e.g. after a section header or a list. Therefore the automatic
1007 ## insertion will use the save form, feel free to correct this by hand.)::
1009 codesamples["insert missing double colon after text block"] = (
1010 """# text followed by code without double colon
1012 foo = 'first'
1013 """,
1014 """text followed by code without double colon
1018 foo = 'first'
1019 """,
1020 """text followed by code without double colon
1022 """)
1024 codesamples["ignore directive options when looking for code-block marker"] = (
1025 """\
1026 # ::
1027 # :option: argument
1028 # :option2: argument
1030 this = 'code'
1031 """,
1032 """\
1034 :option: argument
1035 :option2: argument
1037 this = 'code'
1038 """)
1040 codesamples["code-block marker followed by text not a directive option"] = (
1041 """\
1042 # ::
1043 # text following ``::`` without blank line
1045 this = 'code'
1046 """,
1047 """\
1049 text following ``::`` without blank line
1053 this = 'code'
1054 """)
1057 ## header samples
1058 ## ~~~~~~~~~~~~~~
1060 ## Convert a header (leading code block) to a reStructured text comment. ::
1062 codesamples["no matching comment, just code"] = (
1063 """print('hello world')
1065 print('ende')
1066 """,
1067 """.. print('hello world')
1069 print('ende')
1070 """)
1072 codesamples["empty header (start with matching comment)"] = (
1073 """# a classical example without header::
1075 print('hello world')
1076 """,
1077 """a classical example without header::
1079 print('hello world')
1080 """,
1081 """a classical example without header:
1083 """)
1085 codesamples["standard header, followed by text"] = (
1086 """#!/usr/bin/env python
1087 # -*- coding: iso-8859-1 -*-
1089 # a classical example with header::
1091 print('hello world')
1092 """,
1093 """.. #!/usr/bin/env python
1094 # -*- coding: iso-8859-1 -*-
1096 a classical example with header::
1098 print('hello world')
1099 """,
1100 """a classical example with header:
1102 """)
1104 codesamples["standard header, followed by code"] = (
1105 """#!/usr/bin/env python
1107 print('hello world')
1108 """,
1109 """.. #!/usr/bin/env python
1111 print('hello world')
1112 """,
1115 ## Filter tests
1116 ## ============
1118 ## ::
1120 css_code = ['/* import the default Docutils style sheet */\n',
1121 '/* --------------------------------------- */\n',
1122 '\n',
1123 '/* :: */\n',
1124 '\n',
1125 '/*comment*/\n',
1126 '@import url("html4css1.css"); /* style */\n']
1128 ## ::
1130 css_filtered_code = ['// import the default Docutils style sheet\n',
1131 '// ---------------------------------------\n',
1132 '\n',
1133 '// ::\n',
1134 '\n',
1135 '/*comment*/\n',
1136 '@import url("html4css1.css"); /* style */\n']
1138 ## ::
1140 def test_dumb_c_preprocessor():
1141 """convert `C` to `C++` comments"""
1142 output = [line for line in dumb_c_preprocessor(css_code)]
1143 print("ist: %r"%output)
1144 print("soll: %r"%css_filtered_code)
1145 assert output == css_filtered_code
1147 ## ::
1149 def test_dumb_c_postprocessor():
1150 """convert `C++` to `C` comments"""
1151 output = [line for line in dumb_c_postprocessor(css_filtered_code)]
1152 print("ist: %r"%output)
1153 print("soll: %r"%css_code)
1154 assert output == css_code
1158 ## ::
1160 if __name__ == "__main__":
1161 nose.runmodule() # requires nose 0.9.1
1162 sys.exit()