# http://bugs.python.org/issue12806, http://bugs.python.org/file22977/argparse_formatter.py """ In http://bugs.python.org/issue22029 argparse CSS white-space: like control for individual text blocks I propose a set of `str` subclasses that can be used to define the wrapping style of individual text blocks. The idea is adapted from the HTML '
' tag, and the CSS white-space: option. `argparse.WhitespaceStyle` is a cover class that defines various utility methods, including `_str_format` which handles all of the `%` formatting. The individual subclasses implement their own version of `_split_lines` and `_fill_text`. I chose a standard set of classes based on the CSS white-space options: Normal() - full white space compression and wrapping. This is the default default of text in `argparse`. Pre() - preformatting, the same as the `Raw` formatters NoWrap() - Pre plus whitespace compression PreLine() PreWrap() In `HelpFormatter`, `_split_lines`, `_fill_lines`, `_str_format` delegate the action to text's own methods. Plain text is handled as `Normal()`. I also defined a `WSList` class. This is a list of Style class objects. It has the same API as the Style classes, iterating over the items. Where possible these methods try to return an object of the same type as self. ------------------ Here I demonstrate two ways that these classes could be used to implement a hybrid formatter. The first is a simple adaptation of the `PareML` formatter from `paraformatter.py`. It shows how a custom style class could be defined. The second is defines a `preformat` function, which converts the text block into a `WSList`, a list of style text objects. The wrappable paragraphs are `Normal()`, the preformatted indented lines are `Pre()`. Blank lines are `Pre(' ')`. I've explored writing a `Hanging` class, which performs a hanging indent on list item sentences. """ import argparse import re import textwrap import os, shutil os.environ['COLUMNS'] = str(shutil.get_terminal_size().columns) from paraformatter import _para_reformat class ParaML(argparse.WhitespaceStyle): """ adapted from http://bugs.python.org/file28091/paraformatter.py """ _whitespace_matcher = re.compile(r'\s+') def _split_lines(self, width): lines = _para_reformat(self, self, width, multiline=True) return self.copy_class(lines) def _fill_text(self, width, indent): lines =_para_reformat(self, self, width, indent, multiline=True) text = '\n'.join(lines) return self.copy_class(text) from argparse import Normal, Pre, WSList def preformat(text, multiline=True, keepblank=True, para_style=Normal, indent_style=Pre): """Convert a multiline text block into wrappable lines and preformated indented blocks Returns a WSList of style objects """ text = textwrap.dedent(text) _whitespace_matcher = re.compile(r'\s+') new_lines = list() main_indent = len(re.match(r'( *)',text).group(1)) if main_indent: print('indent', main_indent) def blocker (text): block = list() for line in text.splitlines(): line_indent = len(re.match(r'( *)',line).group(1)) isindented = line_indent - main_indent > 0 isblank = re.match(r'\s*$', line) if isblank or isindented: # A no-wrap line. if block: # Yield previously accumulated block . yield True, ' '.join(block) # of text if any, for wrapping. block = list() yield False, line # And now yield our no-wrap line. else: # We have a regular text line. if multiline: # In multiline mode accumulate it. block.append(line) else: # Not in multiline mode, yield it yield True, line # for wrapping. if block: # Yield any text block left over. yield (True, ' '.join(block)) for wrap, line in blocker(text): if wrap: new_lines.append(para_style(line)) else: # The line was a no-wrap one so leave the formatting alone. line = line[main_indent:] if len(line)==0: if keepblank: line = ' ' # ' ' preserves a blank line between paragraphs new_lines.append(Pre(line)) # else - don't append anything else: new_lines.append(indent_style(line)) return WSList(new_lines) if __name__ == '__main__': parser = argparse.ArgumentParser(prog='ParaML', description=ParaML('''\ This description help text will have this first long line wrapped to fit the target window size so that your text remains flexible. 1. But lines such as 2. this that that are indented beyond the first line's indent, 3. are reproduced verbatim, with no wrapping. or other formatting applied. The ParagraphFormatterML class will treat consecutive lines of text as a single block to rewrap. So there is no need to end lines with backslashes to create a single long logical line. As with docstrings, the leading space to the text block is ignored.''')) parser.print_help() print('---------------------\n') parser = argparse.ArgumentParser(prog='Preformat', description=preformat('''\ This description help text will have this first long line wrapped to fit the target window size so that your text remains flexible. 1. But lines such as 2. this that that are indented beyond the first line's indent, 3. are reproduced verbatim, with no wrapping. or other formatting applied. The ParagraphFormatterML class will treat consecutive lines of text as a single block to rewrap. So there is no need to end lines with backslashes to create a single long logical line. As with docstrings, the leading space to the text block is ignored.''')) parser.add_argument('--example', help=preformat('''\ This argument's help text will have this first long line wrapped to fit the target window size so that your text remains flexible. 1. But lines such as 2. this that that are indented beyond the first line's indent, 3. are reproduced verbatim, with no wrapping. or other formatting applied. The ParagraphFormatterML class will treat consecutive lines of text as a single block to rewrap. So there is no need to end lines with backslashes to create a single long logical line. As with docstrings, the leading space to the text block is ignored.''')) parser.print_help()