srcanamdw/codescanner/pyinstaller/optik/textwrap.py
changeset 1 22878952f6e2
equal deleted inserted replaced
0:509e4801c378 1:22878952f6e2
       
     1 """Text wrapping and filling.
       
     2 """
       
     3 
       
     4 # Copyright (C) 1999-2001 Gregory P. Ward.
       
     5 # Copyright (C) 2002, 2003 Python Software Foundation.
       
     6 # Written by Greg Ward <gward@python.net>
       
     7 
       
     8 __revision__ = "$Id: textwrap.py,v 1.1 2009/02/05 23:03:30 stechong Exp $"
       
     9 
       
    10 import string, re
       
    11 import types
       
    12 
       
    13 # Do the right thing with boolean values for all known Python versions
       
    14 # (so this module can be copied to projects that don't depend on Python
       
    15 # 2.3, e.g. Optik and Docutils).
       
    16 try:
       
    17     True, False
       
    18 except NameError:
       
    19     (True, False) = (1, 0)
       
    20 
       
    21 # For Python 1.5, just ignore unicode (try it as str)
       
    22 try:
       
    23     unicode
       
    24 except NameError:
       
    25     unicode=str
       
    26 
       
    27 __all__ = ['TextWrapper', 'wrap', 'fill']
       
    28 
       
    29 # Hardcode the recognized whitespace characters to the US-ASCII
       
    30 # whitespace characters.  The main reason for doing this is that in
       
    31 # ISO-8859-1, 0xa0 is non-breaking whitespace, so in certain locales
       
    32 # that character winds up in string.whitespace.  Respecting
       
    33 # string.whitespace in those cases would 1) make textwrap treat 0xa0 the
       
    34 # same as any other whitespace char, which is clearly wrong (it's a
       
    35 # *non-breaking* space), 2) possibly cause problems with Unicode,
       
    36 # since 0xa0 is not in range(128).
       
    37 _whitespace = '\t\n\x0b\x0c\r '
       
    38 
       
    39 class TextWrapper:
       
    40     """
       
    41     Object for wrapping/filling text.  The public interface consists of
       
    42     the wrap() and fill() methods; the other methods are just there for
       
    43     subclasses to override in order to tweak the default behaviour.
       
    44     If you want to completely replace the main wrapping algorithm,
       
    45     you'll probably have to override _wrap_chunks().
       
    46 
       
    47     Several instance attributes control various aspects of wrapping:
       
    48       width (default: 70)
       
    49         the maximum width of wrapped lines (unless break_long_words
       
    50         is false)
       
    51       initial_indent (default: "")
       
    52         string that will be prepended to the first line of wrapped
       
    53         output.  Counts towards the line's width.
       
    54       subsequent_indent (default: "")
       
    55         string that will be prepended to all lines save the first
       
    56         of wrapped output; also counts towards each line's width.
       
    57       expand_tabs (default: true)
       
    58         Expand tabs in input text to spaces before further processing.
       
    59         Each tab will become 1 .. 8 spaces, depending on its position in
       
    60         its line.  If false, each tab is treated as a single character.
       
    61       replace_whitespace (default: true)
       
    62         Replace all whitespace characters in the input text by spaces
       
    63         after tab expansion.  Note that if expand_tabs is false and
       
    64         replace_whitespace is true, every tab will be converted to a
       
    65         single space!
       
    66       fix_sentence_endings (default: false)
       
    67         Ensure that sentence-ending punctuation is always followed
       
    68         by two spaces.  Off by default because the algorithm is
       
    69         (unavoidably) imperfect.
       
    70       break_long_words (default: true)
       
    71         Break words longer than 'width'.  If false, those words will not
       
    72         be broken, and some lines might be longer than 'width'.
       
    73     """
       
    74 
       
    75     whitespace_trans = string.maketrans(_whitespace, ' ' * len(_whitespace))
       
    76 
       
    77     unicode_whitespace_trans = {}
       
    78     uspace = ord(unicode(' '))
       
    79     for x in map(ord, _whitespace):
       
    80         unicode_whitespace_trans[x] = uspace
       
    81 
       
    82     # This funky little regex is just the trick for splitting
       
    83     # text up into word-wrappable chunks.  E.g.
       
    84     #   "Hello there -- you goof-ball, use the -b option!"
       
    85     # splits into
       
    86     #   Hello/ /there/ /--/ /you/ /goof-/ball,/ /use/ /the/ /-b/ /option!
       
    87     # (after stripping out empty strings).
       
    88     try:
       
    89         wordsep_re = re.compile(r'(\s+|'                  # any whitespace
       
    90                                 r'[^\s\w]*\w{2,}-(?=\w{2,})|' # hyphenated words
       
    91                                 r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))')   # em-dash
       
    92     except:
       
    93         # Under python 1.5, the above regular expression does not compile because
       
    94         # of positive look-behind assertions (?<=). This stripped down version
       
    95         # does but it causes some regressions in the testsuite. Better than
       
    96         # nothing...
       
    97         wordsep_re = re.compile(r'(\s+|'                  # any whitespace
       
    98                                 r'[^\s\w]*\w{2,}-(?=\w{2,})|' # hyphenated words
       
    99                                 r')')   # em-dash
       
   100 
       
   101     # XXX this is not locale- or charset-aware -- string.lowercase
       
   102     # is US-ASCII only (and therefore English-only)
       
   103     sentence_end_re = re.compile(r'[%s]'              # lowercase letter
       
   104                                  r'[\.\!\?]'          # sentence-ending punct.
       
   105                                  r'[\"\']?'           # optional end-of-quote
       
   106                                  % string.lowercase)
       
   107 
       
   108 
       
   109     def __init__(self,
       
   110                  width=70,
       
   111                  initial_indent="",
       
   112                  subsequent_indent="",
       
   113                  expand_tabs=True,
       
   114                  replace_whitespace=True,
       
   115                  fix_sentence_endings=False,
       
   116                  break_long_words=True):
       
   117         self.width = width
       
   118         self.initial_indent = initial_indent
       
   119         self.subsequent_indent = subsequent_indent
       
   120         self.expand_tabs = expand_tabs
       
   121         self.replace_whitespace = replace_whitespace
       
   122         self.fix_sentence_endings = fix_sentence_endings
       
   123         self.break_long_words = break_long_words
       
   124 
       
   125 
       
   126     # -- Private methods -----------------------------------------------
       
   127     # (possibly useful for subclasses to override)
       
   128 
       
   129     def _munge_whitespace(self, text):
       
   130         """_munge_whitespace(text : string) -> string
       
   131 
       
   132         Munge whitespace in text: expand tabs and convert all other
       
   133         whitespace characters to spaces.  Eg. " foo\tbar\n\nbaz"
       
   134         becomes " foo    bar  baz".
       
   135         """
       
   136         if self.expand_tabs:
       
   137             text = string.expandtabs(text)
       
   138         if self.replace_whitespace:
       
   139             if isinstance(text, types.StringType):
       
   140                 text = string.translate(text, self.whitespace_trans)
       
   141             elif isinstance(text, types.UnicodeType):
       
   142                 # This has to be Python 2.0+ (no unicode before), so
       
   143                 # use directly string methods (the string module does not
       
   144                 # support translate() with dictionary for unicode).
       
   145                 text = text.translate(self.unicode_whitespace_trans)
       
   146         return text
       
   147 
       
   148 
       
   149     def _split(self, text):
       
   150         """_split(text : string) -> [string]
       
   151 
       
   152         Split the text to wrap into indivisible chunks.  Chunks are
       
   153         not quite the same as words; see wrap_chunks() for full
       
   154         details.  As an example, the text
       
   155           Look, goof-ball -- use the -b option!
       
   156         breaks into the following chunks:
       
   157           'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ',
       
   158           'use', ' ', 'the', ' ', '-b', ' ', 'option!'
       
   159         """
       
   160         chunks = self.wordsep_re.split(text)
       
   161         chunks = filter(None, chunks)
       
   162         return chunks
       
   163 
       
   164     def _fix_sentence_endings(self, chunks):
       
   165         """_fix_sentence_endings(chunks : [string])
       
   166 
       
   167         Correct for sentence endings buried in 'chunks'.  Eg. when the
       
   168         original text contains "... foo.\nBar ...", munge_whitespace()
       
   169         and split() will convert that to [..., "foo.", " ", "Bar", ...]
       
   170         which has one too few spaces; this method simply changes the one
       
   171         space to two.
       
   172         """
       
   173         i = 0
       
   174         pat = self.sentence_end_re
       
   175         while i < len(chunks)-1:
       
   176             if chunks[i+1] == " " and pat.search(chunks[i]):
       
   177                 chunks[i+1] = "  "
       
   178                 i = i+2
       
   179             else:
       
   180                 i = i+1
       
   181 
       
   182     def _handle_long_word(self, chunks, cur_line, cur_len, width):
       
   183         """_handle_long_word(chunks : [string],
       
   184                              cur_line : [string],
       
   185                              cur_len : int, width : int)
       
   186 
       
   187         Handle a chunk of text (most likely a word, not whitespace) that
       
   188         is too long to fit in any line.
       
   189         """
       
   190         space_left = max(width - cur_len, 1)
       
   191 
       
   192         # If we're allowed to break long words, then do so: put as much
       
   193         # of the next chunk onto the current line as will fit.
       
   194         if self.break_long_words:
       
   195             cur_line.append(chunks[0][0:space_left])
       
   196             chunks[0] = chunks[0][space_left:]
       
   197 
       
   198         # Otherwise, we have to preserve the long word intact.  Only add
       
   199         # it to the current line if there's nothing already there --
       
   200         # that minimizes how much we violate the width constraint.
       
   201         elif not cur_line:
       
   202             cur_line.append(chunks.pop(0))
       
   203 
       
   204         # If we're not allowed to break long words, and there's already
       
   205         # text on the current line, do nothing.  Next time through the
       
   206         # main loop of _wrap_chunks(), we'll wind up here again, but
       
   207         # cur_len will be zero, so the next line will be entirely
       
   208         # devoted to the long word that we can't handle right now.
       
   209 
       
   210     def _wrap_chunks(self, chunks):
       
   211         """_wrap_chunks(chunks : [string]) -> [string]
       
   212 
       
   213         Wrap a sequence of text chunks and return a list of lines of
       
   214         length 'self.width' or less.  (If 'break_long_words' is false,
       
   215         some lines may be longer than this.)  Chunks correspond roughly
       
   216         to words and the whitespace between them: each chunk is
       
   217         indivisible (modulo 'break_long_words'), but a line break can
       
   218         come between any two chunks.  Chunks should not have internal
       
   219         whitespace; ie. a chunk is either all whitespace or a "word".
       
   220         Whitespace chunks will be removed from the beginning and end of
       
   221         lines, but apart from that whitespace is preserved.
       
   222         """
       
   223         lines = []
       
   224         if self.width <= 0:
       
   225             raise ValueError("invalid width %r (must be > 0)" % self.width)
       
   226 
       
   227         while chunks:
       
   228 
       
   229             # Start the list of chunks that will make up the current line.
       
   230             # cur_len is just the length of all the chunks in cur_line.
       
   231             cur_line = []
       
   232             cur_len = 0
       
   233 
       
   234             # Figure out which static string will prefix this line.
       
   235             if lines:
       
   236                 indent = self.subsequent_indent
       
   237             else:
       
   238                 indent = self.initial_indent
       
   239 
       
   240             # Maximum width for this line.
       
   241             width = self.width - len(indent)
       
   242 
       
   243             # First chunk on line is whitespace -- drop it, unless this
       
   244             # is the very beginning of the text (ie. no lines started yet).
       
   245             if string.strip(chunks[0]) == '' and lines:
       
   246                 del chunks[0]
       
   247 
       
   248             while chunks:
       
   249                 l = len(chunks[0])
       
   250 
       
   251                 # Can at least squeeze this chunk onto the current line.
       
   252                 if cur_len + l <= width:
       
   253                     cur_line.append(chunks.pop(0))
       
   254                     cur_len = cur_len + l
       
   255 
       
   256                 # Nope, this line is full.
       
   257                 else:
       
   258                     break
       
   259 
       
   260             # The current line is full, and the next chunk is too big to
       
   261             # fit on *any* line (not just this one).
       
   262             if chunks and len(chunks[0]) > width:
       
   263                 self._handle_long_word(chunks, cur_line, cur_len, width)
       
   264 
       
   265             # If the last chunk on this line is all whitespace, drop it.
       
   266             if cur_line and string.strip(cur_line[-1]) == '':
       
   267                 del cur_line[-1]
       
   268 
       
   269             # Convert current line back to a string and store it in list
       
   270             # of all lines (return value).
       
   271             if cur_line:
       
   272                 lines.append(indent + string.join(cur_line, ''))
       
   273 
       
   274         return lines
       
   275 
       
   276 
       
   277     # -- Public interface ----------------------------------------------
       
   278 
       
   279     def wrap(self, text):
       
   280         """wrap(text : string) -> [string]
       
   281 
       
   282         Reformat the single paragraph in 'text' so it fits in lines of
       
   283         no more than 'self.width' columns, and return a list of wrapped
       
   284         lines.  Tabs in 'text' are expanded with string.expandtabs(),
       
   285         and all other whitespace characters (including newline) are
       
   286         converted to space.
       
   287         """
       
   288         text = self._munge_whitespace(text)
       
   289         indent = self.initial_indent
       
   290         chunks = self._split(text)
       
   291         if self.fix_sentence_endings:
       
   292             self._fix_sentence_endings(chunks)
       
   293         return self._wrap_chunks(chunks)
       
   294 
       
   295     def fill(self, text):
       
   296         """fill(text : string) -> string
       
   297 
       
   298         Reformat the single paragraph in 'text' to fit in lines of no
       
   299         more than 'self.width' columns, and return a new string
       
   300         containing the entire wrapped paragraph.
       
   301         """
       
   302         return string.join(self.wrap(text), "\n")
       
   303 
       
   304 
       
   305 # -- Convenience interface ---------------------------------------------
       
   306 
       
   307 def wrap(text, width=70, **kwargs):
       
   308     """Wrap a single paragraph of text, returning a list of wrapped lines.
       
   309 
       
   310     Reformat the single paragraph in 'text' so it fits in lines of no
       
   311     more than 'width' columns, and return a list of wrapped lines.  By
       
   312     default, tabs in 'text' are expanded with string.expandtabs(), and
       
   313     all other whitespace characters (including newline) are converted to
       
   314     space.  See TextWrapper class for available keyword args to customize
       
   315     wrapping behaviour.
       
   316     """
       
   317     kwargs["width"] = width
       
   318     w = apply(TextWrapper, (), kwargs)
       
   319     return w.wrap(text)
       
   320 
       
   321 def fill(text, width=70, **kwargs):
       
   322     """Fill a single paragraph of text, returning a new string.
       
   323 
       
   324     Reformat the single paragraph in 'text' to fit in lines of no more
       
   325     than 'width' columns, and return a new string containing the entire
       
   326     wrapped paragraph.  As with wrap(), tabs are expanded and other
       
   327     whitespace characters converted to space.  See TextWrapper class for
       
   328     available keyword args to customize wrapping behaviour.
       
   329     """
       
   330     kwargs["width"] = width
       
   331     w = apply(TextWrapper, (), kwargs)
       
   332     return w.fill(text)
       
   333 
       
   334 
       
   335 # -- Loosely related functionality -------------------------------------
       
   336 
       
   337 def dedent(text):
       
   338     """dedent(text : string) -> string
       
   339 
       
   340     Remove any whitespace than can be uniformly removed from the left
       
   341     of every line in `text`.
       
   342 
       
   343     This can be used e.g. to make triple-quoted strings line up with
       
   344     the left edge of screen/whatever, while still presenting it in the
       
   345     source code in indented form.
       
   346 
       
   347     For example:
       
   348 
       
   349         def test():
       
   350             # end first line with \ to avoid the empty line!
       
   351             s = '''\
       
   352             hello
       
   353               world
       
   354             '''
       
   355             print repr(s)          # prints '    hello\n      world\n    '
       
   356             print repr(dedent(s))  # prints 'hello\n  world\n'
       
   357     """
       
   358     lines = string.split(string.expandtabs(text), '\n')
       
   359     margin = None
       
   360     for line in lines:
       
   361         content = string.lstrip(line)
       
   362         if not content:
       
   363             continue
       
   364         indent = len(line) - len(content)
       
   365         if margin is None:
       
   366             margin = indent
       
   367         else:
       
   368             margin = min(margin, indent)
       
   369 
       
   370     if margin is not None and margin > 0:
       
   371         for i in range(len(lines)):
       
   372             lines[i] = lines[i][margin:]
       
   373 
       
   374     return string.join(lines, "\n")