4 """pylit: bidirectional text <-> code converter
6 Covert between a *text source* with embedded computer code
7 and a *code source* with embedded documentation.
10 from __future__
import print_function
14 # Literate programming with reStructuredText
15 # ++++++++++++++++++++++++++++++++++++++++++
17 # :Copyright: © 2005, 2007, 2015, 2021 Günter Milde.
18 # Released without warranty under the terms of the
19 # GNU General Public License (v. 3 or later)
29 # .. class:: borderless
31 # ====== ========== =========================================================
32 # 0.1 2005-06-29 Initial version.
33 # 0.1.1 2005-06-30 First literate version.
34 # 0.1.2 2005-07-01 Object oriented script using generators.
35 # 0.1.3 2005-07-10 Two state machine (later added 'header' state).
36 # 0.2b 2006-12-04 Start of work on version 0.2 (code restructuring).
37 # 0.2 2007-01-23 Published at ``pylit.berlios.de``.
38 # 0.2.1 2007-01-25 Outsourced non-core documentation to the PyLit pages.
39 # 0.2.2 2007-01-26 New behaviour of `diff` function.
40 # 0.2.3 2007-01-29 New `header` methods after suggestion by Riccardo Murri.
41 # 0.2.4 2007-01-31 Raise Error if code indent is too small.
42 # 0.2.5 2007-02-05 New command line option --comment-string.
43 # 0.2.6 2007-02-09 Add section with open questions,
44 # .. Code2Text: let only blank lines (no comment str)
45 # separate text and code,
46 # .. fix `Code2Text.header`.
47 # 0.2.7 2007-02-19 Simplify `Code2Text.header`,
48 # new `iter_strip` method replacing a lot of ``if``-s.
49 # 0.2.8 2007-02-22 Set `mtime` of outfile to the one of infile.
50 # 0.3 2007-02-27 New `Code2Text` converter after an idea by Riccardo Murri,
51 # .. explicit `option_defaults` dict for easier customisation.
52 # 0.3.1 2007-03-02 Expand hard-tabs to prevent errors in indentation,
53 # .. `Text2Code` now also works on blocks,
54 # .. removed dependency on SimpleStates module.
55 # 0.3.2 2007-03-06 Bug fix: do not set `language` in `option_defaults`
56 # .. renamed `code_languages` to `languages`.
57 # 0.3.3 2007-03-16 New language css,
58 # .. option_defaults -> defaults = optparse.Values(),
59 # .. simpler PylitOptions: don't store parsed values,
60 # don't parse at initialisation,
61 # .. OptionValues: return `None` for non-existing attributes,
62 # .. removed -infile and -outfile, use positional arguments.
63 # 0.3.4 2007-03-19 Documentation update,
64 # separate `execute` function.
65 # .. 2007-03-21 Code cleanup in `Text2Code.__iter__`.
66 # 0.3.5 2007-03-23 Removed "css" from known languages after learning that
67 # there is no C++ style "// " comment string in CSS2.
68 # 0.3.6 2007-04-24 Documentation update.
69 # 0.4 2007-05-18 Implement Converter.__iter__ as stack of iterator
70 # generators. Iterating over a converter instance now
71 # yields lines instead of blocks.
72 # .. Provide "hooks" for pre- and postprocessing filters.
73 # .. Rename states to reduce confusion with formats:
74 # "text" -> "documentation", "code" -> "code_block".
75 # 0.4.1 2007-05-22 Converter.__iter__: cleanup and reorganisation,
76 # rename parent class Converter -> TextCodeConverter.
77 # 0.4.2 2007-05-23 Merged Text2Code.converter and Code2Text.converter into
78 # TextCodeConverter.converter.
79 # 0.4.3 2007-05-30 Replaced use of defaults.code_extensions with
80 # values.languages.keys().
81 # .. Removed spurious `print` statement in code_block_handler.
82 # .. Added basic support for 'c' and 'css' languages with
83 # `dumb_c_preprocessor()`_ and `dumb_c_postprocessor()`_.
84 # 0.5 2007-06-06 Moved `collect_blocks()`_ out of `TextCodeConverter`_,
85 # .. bug fix: collect all trailing blank lines into a block.
86 # .. Expand tabs with `expandtabs_filter()`_.
87 # 0.6 2007-06-20 Configurable code-block marker (default ``::``)
88 # 0.6.1 2007-06-28 Bug fix: reset self.code_block_marker_missing.
89 # 0.7 2007-12-12 prepending an empty string to sys.path in run_doctest()
90 # to allow imports from the current working dir.
91 # 0.7.1 2008-01-07 If outfile does not exist, do a round-trip conversion
92 # and report differences (as with outfile=='-').
93 # 0.7.2 2008-01-28 Do not add missing code-block separators with
94 # `doctest_run` on the code source. Keeps lines consistent.
95 # 0.7.3 2008-04-07 Use value of code_block_marker for insertion of missing
96 # transition marker in Code2Text.code_block_handler
97 # .. Add "shell" to defaults.languages
98 # 0.7.4 2008-06-23 Add "latex" to defaults.languages
99 # 0.7.5 2009-05-14 Bugfix: ignore blank lines in test for end of code block
100 # 0.7.6 2009-12-15 language-dependent code-block markers (after a
101 # feature request and patch by `jrioux`),
102 # .. use DefaultDict for language-dependent defaults,
103 # .. new setting `add_missing_marker`_.
104 # 0.7.7 2010-06-23 New command line option --codeindent.
105 # 0.7.8 2011-03-30 Do not overwrite custom `add_missing_marker` value,
106 # allow directive options following the 'code' directive.
107 # 0.7.9 2011-04-05 Decode doctest string if 'magic comment' gives encoding.
108 # 0.7.10 2013-06-07 Add "lua" to defaults.languages
109 # 0.7.11 2020-10-10 Return 0, if input and output file are of same age.
110 # 0.8.0 2022-06-29 Fix ``--execute`` behaviour and tests.
111 # .. Change default `codeindent`_ to 2.
112 # .. Switch to `argparse`_. Remove class `OptionValues`.
113 # ====== ========== =========================================================
117 __version__
= '0.8.0'
119 __docformat__
= 'restructuredtext'
125 # PyLit is a bidirectional converter between two formats of a computer
128 # * a (reStructured) text document with program code embedded in
130 # * a compilable (or executable) code source with *documentation*
131 # embedded in comment blocks
148 # As `collections.defaultdict` adds key/value pairs when the default
149 # constructor is called, we define an alternative that does not mutate the
150 # dict as side-effect. ::
152 class DefaultDict(dict):
153 """Dictionary with default value."""
157 def __missing__(self
, key
):
158 # cf. file:///usr/share/doc/python3/html/library/stdtypes.html#dict
165 # The `defaults` object provides a central repository for default
166 # values and their customisation. ::
168 defaults
= argparse
.Namespace()
172 # * the initialisation of data arguments in TextCodeConverter_ and
175 # * completion of `command line arguments`_ in
176 # `PylitOptions.complete_values()`_.
178 # This allows the easy creation of _`back-ends` that customise the
179 # defaults and then call `main()`_ e.g.
183 # #!/usr/bin/env python
186 # pylit.defaults.code_block_marker['c++'] = '.. code-block:: c++'
187 # pylit.defaults.languages['.def'] = 'latex'
188 # pylit.defaults.languages['.dfu'] = 'latex'
192 # .. note:: Defaults for the `command line arguments`_ can also be specified
193 # as keyword arguments to ``main()``
197 # #!/usr/bin/env python
199 # pylit.main(language='c++')
201 # The following default values are defined in pylit.py:
206 # Code language. Determined from languages_ if ``None``::
208 defaults
.language
= None
213 # Mapping of code file extensions to code language::
215 defaults
.languages
= DefaultDict({".c": "c",
226 # The result can be overridden by the ``--language`` command line option.
228 # The fallback language, used if there is no matching extension (e.g. if pylit
229 # is used as filter) and no ``--language`` is specified is ``"python"``::
231 defaults
.languages
.default
= 'python'
233 # It can be changed programmatically by changing the ``.default``
237 # >>> pylit.defaults.languages.default = 'c++'
238 # >>> pylit.defaults.languages['.camel']
241 # .. _text_extension:
246 # List of known extensions of (reStructured) text files. The first
247 # extension in this list is used by the `_get_outfile_name()`_ method to
248 # generate a text output filename::
250 defaults
.text_extensions
= [".txt", ".rst"]
255 # Used in Code2Text_ to recognise text blocks and in Text2Code_ to format
256 # text blocks as comments.
257 # Determined from comment_strings_ if ``None``::
259 defaults
.comment_string
= None
265 # Comment strings for known languages.
266 # The fallback value is ``'# '``.
268 # **Comment strings include trailing whitespace.** ::
270 defaults
.comment_strings
= DefaultDict({"css": '// ',
279 defaults
.comment_strings
.default
= '# '
284 # Marker string for a header code block in the text source. No trailing
285 # whitespace needed as indented code follows.
286 # Must be a valid rst directive that accepts code on the same line, e.g.
287 # ``'..admonition::'``.
289 # Default is a comment marker::
291 defaults
.header_string
= '..'
297 # Markup at the end of a documentation block.
299 # The `code_block_marker` string is determined based on the code language_
300 # and `inserted into a regular expression`_.
302 # defaults.code_block_marker = None # get from `code_block_markers`
307 # Language-specific code-block markers can be defined programmatically in
310 # The fallback value is Docutils' marker for a `literal block`_::
312 defaults
.code_block_markers
= DefaultDict()
313 defaults
.code_block_markers
.default
= '::'
315 # In a document where code examples are only one of several uses of
316 # literal blocks, it is more appropriate to single out the source code,
317 # e.g., with the double colon at a separate line ("expanded form")
319 # ``defaults.code_block_marker.default = ':: *'``
321 # or a dedicated ``.. code-block::`` directive
323 # ``defaults.code_block_marker['c++'] = '.. code-block:: *c++'``
325 # The latter form also allows mixing code in different languages in one
326 # literate source file.
332 # Strip documentation (or code) blocks from the output::
334 defaults
.strip
= False
340 # Strip `code_block_marker`_ from the end of documentation blocks when
341 # converting to code format. Makes the code more concise but looses the
342 # synchronisation of line numbers in text and code formats.
344 # Can be used together with `add_missing_marker`_ to change
345 # the `code_block_marker`_ in a round trip::
347 defaults
.strip_marker
= False
353 # When converting from code format to text format, add a `code_block_marker`_
354 # at the end of documentation blocks if it is missing::
356 defaults
.add_missing_marker
= True
358 # Keep this at ``True``, if you want to re-convert to code format later!
361 # .. _defaults.preprocessors:
366 # Preprocess the data with language-specific Filters_
367 # (cf. `register filters`_)::
369 defaults
.preprocessors
= {}
371 # .. _defaults.postprocessors:
376 # Postprocess the data with language-specific Filters_
377 # (cf. `register filters`_)::
379 defaults
.postprocessors
= {}
381 # .. _defaults.codeindent:
386 # Number of spaces to indent code blocks in `Code2Text.code_block_handler()`_::
388 defaults
.codeindent
= 2
390 # In `Text2Code.code_block_handler()`_, the codeindent is determined by the
391 # first recognised code line (header or first indented literal block
392 # of the text source).
397 # What to do if the outfile already exists? (ignored if `outfile` == '-')::
399 defaults
.overwrite
= 'update'
403 # :'yes': overwrite eventually existing `outfile`,
404 # :'update': fail if the `outfile` is newer than `infile`,
405 # :'no': fail if `outfile` exists.
408 # Actions: execute, doctest, diff
409 # -------------------------------
410 # If true, these actions replace or follow the txt<->code conversion.
411 # See `command line arguments`_. ::
413 defaults
.execute
= False
414 defaults
.doctest
= False
415 defaults
.diff
= False
420 # The following settings are auto-determined if None
421 # (see `PylitOptions.complete_values()`_).
422 # Initialize them here as they will not be set by
423 # `ArgumentParser.parse_args()`_::
425 # defaults.infile = '' # required
426 defaults
.outfile
= None
427 defaults
.replace
= None
428 defaults
.txt2code
= None
434 # Try to import optional extensions::
437 import pylit_elisp
# noqa
445 # The converter classes implement a simple state machine to separate and
446 # transform documentation and code blocks. For this task, only a very limited
447 # parsing is needed. PyLit's parser assumes:
449 # * `indented literal blocks`_ in a text source are code blocks.
451 # * comment blocks in a code source where every line starts with a matching
452 # `comment_string`_ are documentation blocks.
458 class TextCodeConverter(object):
459 """Parent class for the converters `Text2Code` and `Code2Text`.
462 # The parent class defines data attributes and functions used in both
463 # `Text2Code`_ converting a text source to executable code source, and
464 # `Code2Text`_ converting commented code to a text source.
469 # Class default values are fetched from the `defaults`_ object and can be
470 # overridden by matching keyword arguments during class instantiation.
471 # This also works with keyword arguments to `get_converter()`_ and `main()`_,
472 # as these functions pass on unused keyword args to the instantiation of a
473 # converter class. ::
475 language
= defaults
.languages
[None]
476 comment_strings
= defaults
.comment_strings
477 comment_string
= "" # set in __init__ (if empty)
478 codeindent
= defaults
.codeindent
479 header_string
= defaults
.header_string
480 code_block_markers
= defaults
.code_block_markers
481 code_block_marker
= "" # set in __init__ (if empty)
482 strip
= defaults
.strip
483 strip_marker
= defaults
.strip_marker
484 add_missing_marker
= defaults
.add_missing_marker
485 directive_option_regexp
= re
.compile(r
' +:(\w|[-._+:])+:( |$)')
486 state
= "" # type of current block, see `TextCodeConverter.convert`_
491 # .. _TextCodeConverter.__init__:
496 # Initialising sets the `data` attribute, an iterable object yielding lines of
497 # the source to convert. [#]_
499 # .. [#] The most common choice of data is a `file` object with the text
502 # To convert a string into a suitable object, use its splitlines()
503 # method like ``"2 lines\nof source".splitlines(True)``.
506 # Additional keyword arguments are stored as instance variables,
507 # overwriting the class defaults::
509 def __init__(self
, data
, **keyw
):
510 """data -- iterable data object
511 (list, file, generator, string, ...)
512 **keyw -- remaining keyword arguments are
513 stored as data-attributes
516 self
.__dict
__.update(keyw
)
518 # If empty, `code_block_marker` and `comment_string` are set according
519 # to the `language`::
521 if not self
.code_block_marker
:
522 self
.code_block_marker
= self
.code_block_markers
[self
.language
]
523 if not self
.comment_string
:
524 self
.comment_string
= self
.comment_strings
[self
.language
]
525 self
.stripped_comment_string
= self
.comment_string
.rstrip()
527 # Pre- and postprocessing filters are set (with
528 # `TextCodeConverter.get_filter`_)::
530 self
.preprocessor
= self
.get_filter("preprocessors", self
.language
)
531 self
.postprocessor
= self
.get_filter("postprocessors", self
.language
)
533 # .. _inserted into a regular expression:
535 # Finally, a regular_expression for the `code_block_marker` is compiled
536 # to find valid cases of `code_block_marker` in a given line and return
537 # the groups: ``\1 prefix, \2 code_block_marker, \3 remainder`` ::
539 marker
= self
.code_block_marker
541 # the default marker may occur at the end of a text line
542 self
.marker_regexp
= re
.compile('^( *(?!\.\.).*)(::)([ \n]*)$')
544 # marker must be on a separate line
545 self
.marker_regexp
= re
.compile('^( *)(%s)(.*\n?)$' % marker
)
547 # .. _TextCodeConverter.__iter__:
552 # Return an iterator for the instance. Iteration yields lines of converted
555 # The iterator is a chain of iterators acting on `self.data` that does
558 # * text<->code format conversion
561 # Pre- and postprocessing are only performed, if filters for the current
562 # language are registered in `defaults.preprocessors`_ and|or
563 # `defaults.postprocessors`_. The filters must accept an iterable as first
564 # argument and yield the processed input data line-wise.
568 """Iterate over input data source and yield converted lines
570 return self
.postprocessor(self
.convert(self
.preprocessor(self
.data
)))
573 # .. _TextCodeConverter.__call__:
577 # The special `__call__` method allows the use of class instances as callable
578 # objects. It returns the converted data as list of lines::
581 """Iterate over state-machine and return results as list of lines."""
582 return [line
for line
in self
]
585 # .. _TextCodeConverter.__str__:
589 # Return converted data as string::
592 return "".join(self())
595 # Helpers and convenience methods
596 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
598 # .. _TextCodeConverter.convert:
603 # The `convert` method generates an iterator that does the actual code <-->
604 # text format conversion. The converted data is yielded line-wise and the
605 # instance's `status` argument indicates whether the current line is "header",
606 # "documentation", or "code_block"::
608 def convert(self
, lines
):
609 """Iterate over lines of a program document and convert
610 between "text" and "code" format
613 # Initialise internal data arguments. (Done here, so that every new iteration
614 # re-initialises them.)
617 # the "type" of the currently processed block of lines. One of
619 # :"": initial state: check for header,
620 # :"header": leading code block: strip `header_string`,
621 # :"documentation": documentation part: comment out,
622 # :"code_block": literal blocks containing source code: unindent.
629 # * Do not confuse the internal attribute `_codeindent` with the
630 # configurable `codeindent` (without the leading underscore).
631 # * `_codeindent` is set in `Text2Code.code_block_handler()`_ to the indent
632 # of the first non-blank "code_block" line and stripped from all
633 # "code_block" lines in the text-to-code conversion,
634 # * `codeindent` is set in `__init__` to `defaults.codeindent`_ and added to
635 # "code_block" lines in the code-to-text conversion.
642 # * set by `Text2Code.documentation_handler()`_ to the minimal indent of a
643 # documentation block,
644 # * used in `Text2Code.set_state`_ to find the end of a code block.
650 # `_add_code_block_marker`
651 # If the last paragraph of a documentation block does not end with a
652 # code_block_marker_, it should be added (otherwise, the back-conversion
655 # `_add_code_block_marker` is set by `Code2Text.documentation_handler()`_
656 # and evaluated by `Code2Text.code_block_handler()`_, because the
657 # documentation_handler does not know whether the next block will be
658 # documentation (with no need for a code_block_marker) or a code block.
662 self
._add
_code
_block
_marker
= False
664 # Determine the state of the block and convert with the matching "handler"::
666 for block
in collect_blocks(expandtabs_filter(lines
)):
668 self
.set_state(block
)
669 except StopIteration:
671 for line
in getattr(self
, self
.state
+"_handler")(block
):
675 # .. _TextCodeConverter.get_filter:
681 def get_filter(self
, filter_set
, language
):
682 """Return language specific filter"""
683 if self
.__class
__ == Text2Code
:
684 key
= "text2"+language
685 elif self
.__class
__ == Code2Text
:
686 key
= language
+"2text"
690 return getattr(defaults
, filter_set
)[key
]
691 except (AttributeError, KeyError, TypeError):
692 # print("there is no %r filter in %r"%(key, filter_set))
694 return identity_filter
699 # Return the number of leading spaces in `line`::
701 def get_indent(self
, line
):
702 """Return the indentation of `string`.
704 return len(line
) - len(line
.lstrip())
710 # The `Text2Code` converter separates *code-blocks* [#]_ from *documentation*.
711 # Code blocks are unindented, documentation is commented (or filtered, if the
712 # ``strip`` option is True).
714 # .. [#] Only `indented literal blocks`_ are considered code-blocks. `quoted
715 # literal blocks`_, `parsed-literal blocks`_, and `doctest blocks`_ are
716 # treated as part of the documentation. This allows the inclusion of
722 # Mark that there is no double colon before the doctest block in the
725 # The class inherits the interface and helper functions from
726 # TextCodeConverter_ and adds functions specific to the text-to-code format
729 class Text2Code(TextCodeConverter
):
730 """Convert a (reStructured) text source to code source
733 # .. _Text2Code.set_state:
739 def set_state(self
, block
):
740 """Determine state of `block`. Set `self.state`
743 # `set_state` is used inside an iteration. Hence, if we are out of data, a
744 # StopItertion exception should be raised::
749 # The new state depends on the active state (from the last block) and
750 # features of the current block. It is either "header", "documentation", or
753 # If the current state is "" (first block), check for
754 # the `header_string` indicating a leading code block::
757 # print("set state for %r"%block)
758 if block
[0].startswith(self
.header_string
):
759 self
.state
= "header"
761 self
.state
= "documentation"
763 # If the current state is "documentation", the next block is also
764 # documentation. The end of a documentation part is detected in the
765 # `Text2Code.documentation_handler()`_::
767 # elif self.state == "documentation":
768 # self.state = "documentation"
770 # A "code_block" ends with the first less indented, non-blank line.
771 # `_textindent` is set by the documentation handler to the indent of the
772 # preceding documentation block::
774 elif self
.state
in ["code_block", "header"]:
775 indents
= [self
.get_indent(line
) for line
in block
777 # print("set_state:", indents, self._textindent)
778 if indents
and min(indents
) <= self
._textindent
:
779 self
.state
= 'documentation'
781 self
.state
= 'code_block'
783 # TODO: (or not to do?) insert blank line before the first line with too-small
784 # codeindent using self.ensure_trailing_blank_line(lines, line) (would need
785 # split and push-back of the documentation part)?
788 # .. _Text2Code.header_handler():
793 # Sometimes code needs to remain on the first line(s) of the document to be
794 # valid. The most common example is the "shebang" line that tells a POSIX
795 # shell how to process an executable file
799 # #!/usr/bin/env python
801 # In Python, the special comment to indicate the encoding, e.g.
802 # ``# -*- coding: iso-8859-1 -*-``, must occur before any other comment
805 # If we want to keep the line numbers in sync for text and code source, the
806 # reStructured Text markup for these header lines must start at the same line
807 # as the first header line. Therefore, header lines could not be marked as
808 # literal block (this would require the ``::`` and an empty line above the
811 # OTOH, a comment may start at the same line as the comment marker and it
812 # includes subsequent indented lines. Comments are visible in the reStructured
813 # Text source but hidden in the pretty-printed output.
815 # With a header converted to comment in the text source, everything before
816 # the first documentation block (i.e. before the first paragraph using the
817 # matching comment string) will be hidden away (in HTML or PDF output).
819 # This seems a good compromise, the advantages
821 # * line numbers are kept
822 # * the "normal" code_block conversion rules (indent/unindent by `codeindent`)
824 # * greater flexibility: you can hide a repeating header in a project
825 # consisting of many source files.
827 # set off the disadvantages
829 # - it may come as surprise if a part of the file is not "printed",
830 # - one more syntax element to learn for rst newbies to start with pylit,
831 # (however, starting from the code source, this will be auto-generated)
833 # In the case that there is no matching comment at all, the complete code
834 # source will become a comment -- however, in this case it is not very likely
835 # the source is a literate document anyway.
837 # If needed for the documentation, it is possible to quote the header in (or
838 # after) the first documentation block, e.g. as `parsed literal`.
841 def header_handler(self
, lines
):
842 """Format leading code block"""
843 # strip header string from first line
844 lines
[0] = lines
[0].replace(self
.header_string
, "", 1)
845 # yield remaining lines formatted as code-block
846 for line
in self
.code_block_handler(lines
):
850 # .. _Text2Code.documentation_handler():
852 # documentation_handler()
853 # ~~~~~~~~~~~~~~~~~~~~~~~
855 # The 'documentation' handler processes everything that is not recognised as
856 # "code_block". Documentation is quoted with `self.comment_string`
857 # (or filtered with `--strip=True`).
859 # If end-of-documentation marker is detected,
861 # * set state to 'code_block'
862 # * set `self._textindent` (needed by `Text2Code.set_state`_ to find the
863 # next "documentation" block)
867 def documentation_handler(self
, lines
):
868 """Convert documentation blocks from text to code format
871 # test lines following the code-block marker for false positives
872 if (self
.state
== "code_block" and line
.rstrip()
873 and not self
.directive_option_regexp
.search(line
)):
874 self
.state
= "documentation"
875 # test for end of documentation block
876 if self
.marker_regexp
.search(line
):
877 self
.state
= "code_block"
878 self
._textindent
= self
.get_indent(line
)
882 # do not comment blank lines preceding a code block
884 yield self
.comment_string
+ line
886 if self
.state
== "code_block":
889 yield self
.comment_string
.rstrip() + line
892 # .. _Text2Code.code_block_handler():
894 # code_block_handler()
895 # ~~~~~~~~~~~~~~~~~~~~
897 # The "code_block" handler is called with an indented literal block. It
898 # removes leading whitespace up to the indentation of the first code line in
899 # the file (this deviation from Docutils behaviour allows indented blocks of
902 def code_block_handler(self
, block
):
903 """Convert indented literal blocks to source code format
906 # If still unset, determine the indentation of code blocks from first non-blank
909 if self
._codeindent
== 0:
910 self
._codeindent
= self
.get_indent(block
[0])
912 # Yield unindented lines after check whether we can safely unindent. If the
913 # line is less indented then `_codeindent`, something got wrong. ::
916 if line
.lstrip() and self
.get_indent(line
) < self
._codeindent
:
917 raise ValueError("code block contains line less indented than"
918 " %d spaces \n%r"%(self
._codeindent
, block
))
919 yield line
.replace(" "*self
._codeindent
, "", 1)
925 # The `Code2Text` converter does the opposite of `Text2Code`_ -- it processes
926 # a source in "code format" (i.e. in a programming language), extracts
927 # documentation from comment blocks, and puts program code in literal blocks.
929 # The class inherits the interface and helper functions from
930 # TextCodeConverter_ and adds functions specific to the text-to-code format
933 class Code2Text(TextCodeConverter
):
934 """Convert code source to text source
940 # Check if block is "header", "documentation", or "code_block":
942 # A paragraph is "documentation", if every non-blank line starts with a
943 # matching comment string (including whitespace except for commented blank
946 def set_state(self
, block
):
947 """Determine state of `block`."""
949 # skip documentation lines (commented, blank or blank comment)
950 if (line
.startswith(self
.comment_string
)
952 or line
.rstrip() == self
.comment_string
.rstrip()):
954 # non-commented line found:
956 self
.state
= "header"
958 self
.state
= "code_block"
962 # keep state if the block is just a blank line
963 # if len(block) == 1 and self._is_blank_codeline(line):
965 self
.state
= "documentation"
971 # Handle a leading code block. (See `Text2Code.header_handler()`_ for a
972 # discussion of the "header" state.) ::
974 def header_handler(self
, lines
):
975 """Format leading code block"""
978 # get iterator over the lines that formats them as code-block
979 lines
= iter(self
.code_block_handler(lines
))
980 # prepend header string to first line
981 yield self
.header_string
+ next(lines
)
982 # yield remaining lines
986 # .. _Code2Text.documentation_handler():
988 # documentation_handler()
989 # ~~~~~~~~~~~~~~~~~~~~~~~
991 # The *documentation state* handler converts a comment to a documentation
992 # block by stripping the leading `comment string` from every line::
994 def documentation_handler(self
, block
):
995 """Uncomment documentation blocks in source code
998 # Strip comment strings::
1000 lines
= [self
.uncomment_line(line
) for line
in block
]
1002 # If the code block is stripped, the literal marker would lead to an
1003 # error when the text is converted with Docutils. Strip it as well. ::
1005 if self
.strip
or self
.strip_marker
:
1006 self
.strip_code_block_marker(lines
)
1008 # Otherwise, check for the `code_block_marker`_ at the end of the
1009 # documentation block (skipping directive options that might follow it)::
1011 elif self
.add_missing_marker
:
1012 for line
in lines
[::-1]:
1013 if self
.marker_regexp
.search(line
):
1014 self
._add
_code
_block
_marker
= False
1017 and not self
.directive_option_regexp
.search(line
)):
1018 self
._add
_code
_block
_marker
= True
1021 self
._add
_code
_block
_marker
= True
1032 # Return documentation line after stripping comment string. Consider the
1033 # case that a blank line has a comment string without trailing whitespace::
1035 def uncomment_line(self
, line
):
1036 """Return uncommented documentation line"""
1037 line
= line
.replace(self
.comment_string
, "", 1)
1038 if line
.rstrip() == self
.stripped_comment_string
:
1039 line
= line
.replace(self
.stripped_comment_string
, "", 1)
1043 # .. _Code2Text.code_block_handler():
1045 # code_block_handler()
1046 # ~~~~~~~~~~~~~~~~~~~~
1048 # The `code_block` handler returns the code block as indented literal
1049 # block (or filters it, if ``self.strip == True``). The amount of the code
1050 # indentation is controlled by `self.codeindent` (default 2). ::
1052 def code_block_handler(self
, lines
):
1053 """Covert code blocks to text format (indent or strip)
1057 # eventually insert transition marker
1058 if self
._add
_code
_block
_marker
:
1059 self
.state
= "documentation"
1060 yield self
.code_block_marker
+ "\n"
1062 self
._add
_code
_block
_marker
= False
1063 self
.state
= "code_block"
1065 if not line
.rstrip():
1066 yield line
# don't add indent to blank lines
1068 yield " "*self
.codeindent
+ line
1071 # strip_code_block_marker()
1072 # ~~~~~~~~~~~~~~~~~~~~~~~~~
1074 # Replace the literal marker with the equivalent of Docutils replace rules
1076 # * strip ``::``-line (and preceding blank line) if on a line on its own
1077 # * strip ``::`` if it is preceded by whitespace.
1078 # * convert ``::`` to a single colon if preceded by text
1080 # `lines` is a list of documentation lines (with a trailing blank line).
1081 # It is modified in-place::
1083 def strip_code_block_marker(self
, lines
):
1087 return # just one line (no trailing blank line)
1089 # match with regexp: `match` is None or has groups
1090 # \1 leading text, \2 code_block_marker, \3 remainder
1091 match
= self
.marker_regexp
.search(line
)
1093 if not match
: # no code_block_marker present
1095 if not match
.group(1): # `code_block_marker` on an extra line
1097 # delete preceding line if it is blank
1098 if len(lines
) >= 2 and not lines
[-2].lstrip():
1100 elif match
.group(1).rstrip() < match
.group(1):
1101 # '::' follows whitespace
1102 lines
[-2] = match
.group(1).rstrip() + match
.group(3)
1103 else: # '::' follows text
1104 lines
[-2] = match
.group(1).rstrip() + ':' + match
.group(3)
1110 # Filters allow pre- and post-processing of the data to bring it in a format
1111 # suitable for the "normal" text<->code conversion. An example is conversion
1112 # of `C` ``/*`` ``*/`` comments into C++ ``//`` comments (and back).
1113 # Another example is the conversion of `C` ``/*`` ``*/`` comments into C++
1114 # ``//`` comments (and back).
1116 # Filters are generator functions that return an iterator acting on a
1117 # `data` iterable and yielding processed `data` lines.
1123 # The most basic filter is the identity filter, that returns its argument as
1126 def identity_filter(data
):
1127 """Return data iterator without any processing"""
1131 # expandtabs_filter()
1132 # -------------------
1134 # Expand hard-tabs in every line of `data` (cf. `str.expandtabs`).
1136 # This filter is applied to the input data by `TextCodeConverter.convert`_ as
1137 # hard tabs can lead to errors when the indentation is changed. ::
1139 def expandtabs_filter(data
):
1140 """Yield data tokens with hard-tabs expanded"""
1142 yield line
.expandtabs()
1148 # A filter to aggregate "paragraphs" (blocks separated by blank
1149 # lines). Yields lists of lines::
1151 def collect_blocks(lines
):
1152 """collect lines in a list
1154 yield list for each paragraph, i.e. block of lines separated by a
1155 blank line (whitespace only).
1157 Trailing blank lines are collected as well.
1159 blank_line_reached
= False
1162 if blank_line_reached
and line
.rstrip():
1164 blank_line_reached
= False
1167 if not line
.rstrip():
1168 blank_line_reached
= True
1173 # dumb_c_preprocessor()
1174 # ---------------------
1176 # This is a basic filter to convert `C` to `C++` comments. Works line-wise and
1177 # only converts lines that
1179 # * start with "/\* " and end with " \*/" (followed by whitespace only)
1181 # A more sophisticated version would also
1183 # * convert multi-line comments
1185 # + Keep indentation or strip 3 leading spaces?
1187 # * account for nested comments
1189 # * only convert comments that are separated from code by a blank line
1193 def dumb_c_preprocessor(data
):
1194 """change `C` ``/* `` `` */`` comments into C++ ``// `` comments"""
1195 comment_string
= defaults
.comment_strings
["c++"]
1199 if (line
.startswith(boc_string
)
1200 and line
.rstrip().endswith(eoc_string
)):
1201 line
= line
.replace(boc_string
, comment_string
, 1)
1202 line
= "".join(line
.rsplit(eoc_string
, 1))
1205 # Unfortunately, the `replace` method of strings does not support negative
1206 # numbers for the `count` argument:
1208 # >>> "foo */ baz */ bar".replace(" */", "", -1) == "foo */ baz bar"
1211 # However, there is the `rsplit` method, that can be used together with `join`:
1213 # >>> "".join("foo */ baz */ bar".rsplit(" */", 1)) == "foo */ baz bar"
1217 # dumb_c_postprocessor()
1218 # ----------------------
1220 # Undo the preparations by the dumb_c_preprocessor and re-insert valid comment
1223 def dumb_c_postprocessor(data
):
1224 """change C++ ``// `` comments into `C` ``/* `` `` */`` comments"""
1225 comment_string
= defaults
.comment_strings
["c++"]
1229 if line
.rstrip() == comment_string
.rstrip():
1230 line
= line
.replace(comment_string
, "", 1)
1231 elif line
.startswith(comment_string
):
1232 line
= line
.replace(comment_string
, boc_string
, 1)
1233 line
= line
.rstrip() + eoc_string
+ "\n"
1242 defaults
.preprocessors
['c2text'] = dumb_c_preprocessor
1243 defaults
.preprocessors
['css2text'] = dumb_c_preprocessor
1244 defaults
.postprocessors
['text2c'] = dumb_c_postprocessor
1245 defaults
.postprocessors
['text2css'] = dumb_c_postprocessor
1251 # Using this script from the command line will convert a file according to its
1252 # extension. This default can be overridden by a couple of options.
1254 # Dual source handling
1255 # --------------------
1257 # How to determine which source is up-to-date?
1258 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1260 # - `Set the modification time`_ of `outfile` to the one of `infile` to
1261 # indicate that the source files are 'synchronised'.
1263 # * Are there problems to expect from "backdating" a file? Which?
1265 # Looking at http://www.unix.com/showthread.php?t=20526, it seems
1266 # perfectly legal to set `mtime` (while leaving `ctime`) as `mtime` is a
1267 # description of the "actuality" of the data in the file.
1269 # - alternatively move input file to a backup copy (with option: `--replace`)
1271 # - check modification date before overwriting
1272 # (with option: `--overwrite=update`)
1274 # - check modification date before editing (implemented as `Jed editor`_
1275 # function `pylit_check()` in `pylit.sl`_)
1277 # .. _Jed editor: http://www.jedsoft.org/jed/
1278 # .. _pylit.sl: http://jedmodes.sourceforge.net/mode/pylit/
1280 # Recognised Filename Extensions
1281 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1283 # Instead of defining a new extension for "pylit" literate programs,
1284 # by default ``.txt`` will be appended for the text source and stripped by
1285 # the conversion to the code source. I.e. for a Python program foo:
1287 # * the code source is called ``foo.py``
1288 # * the text source is called ``foo.py.txt``
1289 # * the html rendering is called ``foo.py.html``
1295 # The `PylitOptions` class comprises an option parser and methods for
1296 # completion of command line arguments from defaults and context::
1298 class PylitOptions(object):
1299 """Storage and handling of command line arguments for pylit"""
1308 """Set up an `OptionParser` instance for pylit command line arguments
1310 p
= argparse
.ArgumentParser(usage
=main
.__doc
__)
1312 # .. _command line arguments:
1314 # Positional arguments (I/O):
1316 # The "infile" argument is required unless there is a value in the
1317 # `namespace` passed to `p.parse_args()`.
1318 # We need to cheat here, because this behaviour is not supported by
1319 # `argparse` (cf. `issue 29670`_).
1321 # The default value is set to `argparse.SUPPRESS` to prevent overwriting an
1322 # existing value in the `namespace` (cf. `issue 28734`_). ::
1324 p
.add_argument('infile', metavar
='INFILE',
1325 nargs
='?', default
=argparse
.SUPPRESS
,
1326 help='input file ("-" for stdin)')
1327 p
.add_argument('outfile', metavar
='OUTFILE',
1328 nargs
='?', default
=argparse
.SUPPRESS
,
1329 help=u
'output file, default: auto-determined')
1331 # Conversion settings::
1333 p
.add_argument("-c", "--code2txt", action
="store_false",
1335 help='convert code source to text')
1336 p
.add_argument("-t", "--txt2code", action
="store_true",
1337 help='convert text source to code'
1338 ' (default determined from input file name)')
1339 p
.add_argument("--language",
1340 choices
=list(defaults
.comment_strings
.keys()),
1341 help="use LANGUAGE's native comment style"
1342 ' (default: determined from input file extension)')
1343 p
.add_argument("--comment-string", dest
="comment_string",
1344 help="documentation block marker in code source "
1345 "(including trailing whitespace, "
1346 "default: language dependent)")
1347 p
.add_argument("-m", "--code-block-marker", dest
="code_block_marker",
1348 help='syntax token starting a code block.'
1349 ' (default "%s")' % defaults
.code_block_markers
.default
)
1350 p
.add_argument("--codeindent", type=int,
1351 help='Number of spaces to indent code blocks with'
1352 ' code2text (default %d)' % defaults
.codeindent
)
1354 # Output file handling::
1356 p
.add_argument("--overwrite", action
="store",
1357 choices
=['yes', 'no', 'update'],
1358 help='overwrite output file (default "%s")'
1359 % defaults
.overwrite
)
1360 p
.add_argument("--replace", action
="store_true",
1361 help="move infile to a backup copy (appending '~')")
1362 # TODO: do we need this? If yes, make mtime update depend on it!
1363 # p.add_argument("--keep-mtime", action="store_true",
1364 # help="do not set the modification time of the outfile "
1365 # "to the corresponding value of the infile")
1366 p
.add_argument("-s", "--strip", action
="store_true",
1367 help='"export" by stripping documentation or code')
1371 p
.add_argument("-d", "--diff", action
="store_true",
1372 help="test for differences to existing file")
1373 p
.add_argument("--doctest", action
="store_true",
1374 help="run doctest.testfile() on the text version")
1375 p
.add_argument("-e", "--execute", action
="store_true",
1376 help="execute code (Python only)")
1377 p
.add_argument('-v', '--version', action
='version',
1378 version
=__version__
)
1383 # .. _PylitOptions.complete_values():
1388 # Complete an OptionValues instance `values`. Use module-level defaults and
1389 # context information to set missing option values to sensible defaults (if
1392 def complete_values(self
, values
):
1393 """complete option values with module and context sensible defaults
1395 x.complete_values(values) -> values
1396 values -- OptionValues instance
1399 # The "infile" argument is required but may be pre-set in the `namespace`
1400 # passed to `p.parse_args()` (cf. `issue 29670`_::
1404 except AttributeError:
1405 self
.parser
.error('the following argument is required: infile')
1407 # Guess conversion direction from `infile` filename::
1409 if getattr(values
, 'txt2code', None) is None:
1410 in_extension
= os
.path
.splitext(values
.infile
)[1]
1411 if in_extension
in defaults
.text_extensions
:
1412 values
.txt2code
= True
1413 elif in_extension
in defaults
.languages
.keys():
1414 values
.txt2code
= False
1416 values
.txt2code
= None
1418 # Auto-determine the output file name::
1420 if not values
.outfile
:
1421 values
.outfile
= self
._get
_outfile
_name
(values
)
1423 # Second try: Guess conversion direction from outfile filename::
1425 if values
.txt2code
is None:
1426 out_extension
= os
.path
.splitext(values
.outfile
)[1]
1427 values
.txt2code
= not (out_extension
in defaults
.text_extensions
)
1429 # Set the language of the code::
1431 if values
.language
is None:
1432 if values
.txt2code
is True:
1433 code_extension
= os
.path
.splitext(values
.outfile
)[1]
1434 elif values
.txt2code
is False:
1435 code_extension
= os
.path
.splitext(values
.infile
)[1]
1436 values
.language
= defaults
.languages
[code_extension
]
1441 # _get_outfile_name()
1442 # ~~~~~~~~~~~~~~~~~~~
1444 # Construct a matching filename for the output file. The output filename is
1445 # constructed from `infile` by the following rules:
1447 # * '-' (stdin) results in '-' (stdout)
1448 # * strip the `text_extension`_ (txt2code) or
1449 # * add the `text_extension`_ (code2txt)
1450 # * fallback: if no guess can be made, add ".out"
1454 def _get_outfile_name(self
, values
):
1455 """Return a matching output filename for `infile`
1457 # if input is stdin, default output is stdout
1458 if values
.infile
== '-':
1461 # Derive from `infile` name: strip or add text extension
1462 (base
, ext
) = os
.path
.splitext(values
.infile
)
1463 if ext
in defaults
.text_extensions
:
1465 if ext
and ext
in defaults
.languages
or values
.txt2code
is False:
1466 return values
.infile
+ defaults
.text_extensions
[0] # add
1468 return values
.infile
+ ".out"
1471 # .. _PylitOptions.__call__():
1476 # Use PylitOptions instances as *callables*: Calling a `PylitOptions` instance
1477 # parses the argument list to extract option values and completes them based
1478 # on "context-sensitive defaults".
1479 # Keyword arguments overwrite the `defaults`_
1480 # and are overwritten by command line arguments.
1482 # Attention: passing a `namespace` to `ArgumentParser.parse_args()` has a
1485 # […] if you give an existing object, the option defaults will not be
1488 # -- https://docs.python.org/dev/library/optparse.html#parsing-arguments
1490 # .. The argument is renamed from `values` to `namespace` in Python 3.
1491 # Positional argument defaults are initialized unless the default value
1492 # `argparse.SUPPRESS` is specified.
1496 def __call__(self
, args
=sys
.argv
[1:], **kwargs
):
1497 """parse and complete command line args, return option values
1499 settings
= vars(defaults
).copy() # don't change global settings
1500 settings
.update(kwargs
)
1501 settings
= argparse
.Namespace(**settings
)
1503 settings
= self
.parser
.parse_args(args
, settings
)
1504 settings
= self
.complete_values(settings
)
1505 # print(f'{settings.outfile=}')
1506 # for k,v in vars(settings).items():
1517 # Return file objects for in- and output. If the input path is missing,
1518 # write usage and abort. (An alternative would be to use stdin as default.
1519 # However, this leaves the uninitiated user with a non-responding application
1520 # if (s)he just tries the script without any arguments) ::
1522 def open_streams(infile
='-', outfile
='-', overwrite
='update', **keyw
):
1523 """Open and return the input and output stream
1525 open_streams(infile, outfile) -> (in_stream, out_stream)
1527 in_stream -- file(infile) or sys.stdin
1528 out_stream -- file(outfile) or sys.stdout
1529 overwrite -- 'yes': overwrite eventually existing `outfile`,
1530 'update': fail if the `outfile` is newer than `infile`,
1531 'no': fail if `outfile` exists.
1533 Irrelevant if `outfile` == '-'.
1535 if overwrite
not in ('yes', 'no', 'update'):
1536 raise ValueError('Argument "overwrite" must be "yes", "no",'
1537 ' or update, not "%s".' % overwrite
)
1539 strerror
= "Missing input file name ('-' for stdin; -h for help)"
1540 raise IOError(2, strerror
, infile
)
1542 in_stream
= sys
.stdin
1544 in_stream
= open(infile
, 'r')
1546 out_stream
= sys
.stdout
1547 elif overwrite
== 'no' and os
.path
.exists(outfile
):
1548 raise IOError(17, "Output file exists!", outfile
)
1549 elif overwrite
== 'update' and is_newer(outfile
, infile
) is None:
1550 print('Output file "%s" is as old as input file!\n'
1551 'Use "--overwrite=yes", if you want to overwrite it.' % outfile
,
1554 elif overwrite
== 'update' and is_newer(outfile
, infile
):
1555 raise IOError(1, "Output file is newer than input file!", outfile
)
1557 out_stream
= open(outfile
, 'w')
1558 return (in_stream
, out_stream
)
1565 def is_newer(path1
, path2
):
1566 """Check if `path1` is newer than `path2` (using mtime)
1568 Compare modification time of files at path1 and path2.
1570 Non-existing files are considered oldest: Return False if path1 does not
1571 exist and True if path2 does not exist.
1573 Return None if the modification time differs less than 1/10 second.
1574 (This evaluates to False in a Boolean context but allows a test
1578 mtime1
= os
.path
.getmtime(path1
)
1582 mtime2
= os
.path
.getmtime(path2
)
1585 if abs(mtime1
- mtime2
) < 0.1:
1587 return mtime1
> mtime2
1593 # Get an instance of the converter state machine::
1595 def get_converter(data
, txt2code
=True, **keyw
):
1597 return Text2Code(data
, **keyw
)
1599 return Code2Text(data
, **keyw
)
1609 def run_doctest(infile
="-", txt2code
=True,
1610 globs
={}, verbose
=False, optionflags
=0, **keyw
):
1611 """run doctest on the text source
1614 # Allow imports from the current working dir by prepending an empty string to
1615 # sys.path (see doc of sys.path())::
1617 sys
.path
.insert(0, '')
1619 # Import classes from the doctest module::
1621 from doctest
import DocTestParser
, DocTestRunner
1623 # Read in source. Make sure it is in text format, as tests in comments are not
1624 # found by doctest::
1626 (data
, out_stream
) = open_streams(infile
, "-")
1627 if txt2code
is False:
1628 keyw
.update({'add_missing_marker': False})
1629 converter
= Code2Text(data
, **keyw
)
1630 docstring
= str(converter
)
1632 docstring
= data
.read()
1634 # decode doc string if there is a "magic comment" in the first or second line
1635 # (http://docs.python.org/reference/lexical_analysis.html#encoding-declarations)
1638 if sys
.version_info
< (3, 0):
1639 firstlines
= ' '.join(docstring
.splitlines()[:2])
1640 match
= re
.search(r
'coding[=:]\s*([-\w.]+)', firstlines
)
1642 docencoding
= match
.group(1)
1643 docstring
= docstring
.decode(docencoding
)
1645 # Use the doctest Advanced API to run all doctests in the source text::
1647 test
= DocTestParser().get_doctest(docstring
, globs
, name
="",
1648 filename
=infile
, lineno
=0)
1649 runner
= DocTestRunner(verbose
, optionflags
)
1652 # give feedback also if no failures occurred
1653 if not runner
.failures
:
1654 print("%d failures in %d tests"%(runner
.failures
, runner
.tries
))
1655 return runner
.failures
, runner
.tries
1663 def diff(infile
='-', outfile
='-', txt2code
=True, **keyw
):
1664 """Report differences between converted infile and existing outfile
1666 If outfile does not exist or is '-', do a round-trip conversion and
1672 instream
= open(infile
)
1673 # for diffing, we need a copy of the data as list::
1674 data
= instream
.readlines()
1676 converter
= get_converter(data
, txt2code
, **keyw
)
1679 if outfile
!= '-' and os
.path
.exists(outfile
):
1680 outstream
= open(outfile
)
1681 old
= outstream
.readlines()
1683 newname
= "<conversion of %s>"%infile
1687 # back-convert the output data
1688 converter
= get_converter(new
, not txt2code
)
1690 newname
= "<round-conversion of %s>"%infile
1692 # find and print the differences
1693 is_different
= False
1694 # print(type(old), old)
1695 # print(type(new), new)
1696 delta
= difflib
.unified_diff(old
, new
,
1697 fromfile
=oldname
, tofile
=newname
)
1700 print(line
, end
=' ')
1701 if not is_different
:
1704 print("no differences found")
1711 # Works only for python code.
1713 # Does not work with `eval`, as code is not just one expression. ::
1715 def execute(infile
="-", txt2code
=True, **keyw
):
1716 """Execute the input file. Convert first, if it is a text source.
1719 with
open(infile
) as f
:
1720 data
= f
.readlines()
1722 data
= str(Text2Code(data
, **keyw
))
1729 # If this script is called from the command line, the `main` function will
1730 # convert the input (file or stdin) between text and code formats.
1732 # Setting values for the conversion can be given as keyword arguments to
1733 # `main()`_. The option defaults will be updated by command line arguments
1734 # and extended with "intelligent guesses" by `PylitOptions`_ and passed on to
1735 # helper functions and the converter instantiation.
1737 # This allows easy customisation for programmatic use
1738 # -- just call `main` with the appropriate keyword options,
1739 # e.g., ``pylit.main(comment_string="## ")``. ::
1741 def main(args
=sys
.argv
[1:], **settings
):
1742 """%(prog)s [options] INFILE [OUTFILE]
1744 Convert between (reStructured) text source with embedded code,
1745 and code source with embedded documentation (comment blocks)
1747 The special filename '-' stands for standard in- and output.
1750 # Parse and complete the options::
1752 settings
= PylitOptions()(args
, **settings
)
1754 # Special actions with early return::
1756 if settings
.doctest
:
1757 return run_doctest(**vars(settings
).copy())
1760 return diff(**vars(settings
).copy())
1762 if settings
.execute
:
1763 return execute(**vars(settings
).copy())
1765 # Open in- and output streams::
1768 (data
, out_stream
) = open_streams(**vars(settings
).copy())
1769 except IOError as ex
:
1770 print("IOError: %s %s" % (ex
.filename
, ex
.strerror
))
1773 # Get a converter instance::
1775 converter
= get_converter(data
, **vars(settings
).copy())
1777 # Convert and write to out_stream::
1779 out_stream
.write(str(converter
))
1781 if out_stream
is not sys
.stdout
:
1782 print('Output written to %r' % out_stream
.name
)
1785 # If input and output are from files, _`set the modification time` (`mtime`)
1786 # of the output file to the one of the input file to indicate that the
1787 # contained information is equal. [#]_ ::
1789 # print("fractions?", os.stat_float_times())
1791 os
.utime(settings
.outfile
, (os
.path
.getatime(settings
.outfile
),
1792 os
.path
.getmtime(settings
.infile
)))
1796 ## print("mtime", os.path.getmtime(settings.infile), settings.infile)
1797 ## print("mtime", os.path.getmtime(settings.outfile), settings.outfile)
1799 # .. [#] Make sure the corresponding file object (here `out_stream`) is
1800 # closed, as otherwise the change will be overwritten when `close` is
1801 # called afterwards (either explicitly or at program exit).
1804 # Rename the infile to a backup copy if ``--replace`` is set::
1806 if settings
.replace
:
1807 os
.rename(settings
.infile
, settings
.infile
+ "~")
1810 # Run main, if called from the command line::
1812 if __name__
== '__main__':
1819 # Open questions and ideas for further development
1824 # * can we gain from using "shutils" over "os.path" and "os"?
1825 # * use pylint or pyChecker to enforce a consistent style?
1830 # * Use templates for the "intelligent guesses" (with Python syntax for string
1831 # replacement with dicts: ``"hello %(what)s" % {'what': 'world'}``)
1833 # * Is it sensible to offer the `header_string` option also as command line
1836 # treatment of blank lines
1837 # ------------------------
1839 # Alternatives: Keep blank lines blank
1841 # - "never" (current setting) -> "visually merges" all documentation
1842 # if there is no interjacent code
1844 # - "always" -> disrupts documentation blocks,
1846 # - "if empty" (no whitespace). Comment if there is whitespace.
1848 # This would allow non-obstructing markup but unfortunately this is (in
1849 # most editors) also non-visible markup.
1851 # + "if double" (if there is more than one consecutive blank line)
1853 # With this handling, the "visual gap" remains in both, text and code
1860 # * Ignore "matching comments" in literal strings?
1862 # Too complicated: Would need a specific detection algorithm for every
1863 # language that supports multi-line literal strings (C++, PHP, Python)
1865 # * Warn if a comment in code will become documentation after round-trip?
1868 # docstrings in code blocks
1869 # -------------------------
1871 # * How to handle docstrings in code blocks? (it would be nice to convert them
1872 # to rst-text if ``__docformat__ == restructuredtext``)
1874 # TODO: Ask at Docutils users|developers
1879 # Specify a path for user additions and plug-ins. This would require to
1880 # convert Pylit from a pure module to a package...
1882 # 6.4.3 Packages in Multiple Directories
1884 # Packages support one more special attribute, __path__. This is initialized
1885 # to be a list containing the name of the directory holding the package's
1886 # __init__.py before the code in that file is executed. This
1887 # variable can be modified; doing so affects future searches for modules and
1888 # subpackages contained in the package.
1890 # While this feature is not often needed, it can be used to extend the set
1891 # of modules found in a package.
1896 # .. _Docutils: http://docutils.sourceforge.net/
1897 # .. _Sphinx: http://sphinx.pocoo.org
1898 # .. _Pygments: http://pygments.org/
1899 # .. _code-block directive:
1900 # http://docutils.sourceforge.net/sandbox/code-block-directive/
1901 # .. _literal block:
1902 # .. _literal blocks:
1903 # http://docutils.sf.net/docs/ref/rst/restructuredtext.html#literal-blocks
1904 # .. _indented literal block:
1905 # .. _indented literal blocks:
1906 # http://docutils.sf.net/docs/ref/rst/restructuredtext.html#indented-literal-blocks
1907 # .. _quoted literal block:
1908 # .. _quoted literal blocks:
1909 # http://docutils.sf.net/docs/ref/rst/restructuredtext.html#quoted-literal-blocks
1910 # .. _parsed-literal blocks:
1911 # http://docutils.sf.net/docs/ref/rst/directives.html#parsed-literal-block
1912 # .. _doctest block:
1913 # .. _doctest blocks:
1914 # http://docutils.sf.net/docs/ref/rst/restructuredtext.html#doctest-blocks
1915 # .. _issue 28734: https://bugs.python.org/issue28734
1916 # .. _issue 29670: https://bugs.python.org/issue29670
1917 # .. _argparse: https://docs.python.org/dev/library/argparse.html
1918 # .. _ArgumentParser.parse_args():
1919 # https://docs.python.org/dev/library/argparse.html#the-parse-args-method