diff options
Diffstat (limited to 'modules/language/python/module')
-rw-r--r-- | modules/language/python/module/re.scm | 5 | ||||
-rw-r--r-- | modules/language/python/module/re/compile.scm | 8 | ||||
-rw-r--r-- | modules/language/python/module/re/parser.scm | 17 | ||||
-rw-r--r-- | modules/language/python/module/struct.scm | 222 | ||||
-rw-r--r-- | modules/language/python/module/textwrap.py | 478 |
5 files changed, 621 insertions, 109 deletions
diff --git a/modules/language/python/module/re.scm b/modules/language/python/module/re.scm index f16b697..3b8f8af 100644 --- a/modules/language/python/module/re.scm +++ b/modules/language/python/module/re.scm @@ -430,13 +430,16 @@ (define escape (let ((m (string->list "()[]#$*+-.^|\\"))) + (set! m (cons #\newline m)) (lambda (x) (let lp ((l (string->list x)) (r '())) (ice-match l ((x . l) (lp l (if (member x m) - (cons* #\\ x r) + (if (eq? x #\newline) + (cons* #\n #\\ r) + (cons* x #\\ r)) (cons x r)))) (() (list->string (reverse r)))))))) diff --git a/modules/language/python/module/re/compile.scm b/modules/language/python/module/re/compile.scm index 3d7bdb1..93d7c38 100644 --- a/modules/language/python/module/re/compile.scm +++ b/modules/language/python/module/re/compile.scm @@ -204,10 +204,10 @@ (#:dot #:dot) ((#:op x #:* ) (list #:op (reverse-form x) #:*)) ((#:op x #:+ ) (list #:op (reverse-form x) #:+)) - ((#:op x (#:rep m n)) (list #:op (reverse-form x) (#:rep m n))) - ((#:op x (#:rep m )) (list #:op (reverse-form x) (#:rep m))) - ((#:op x (#:rep? m n)) (list #:op (reverse-form x) (#:rep? m n))) - ((#:op x (#:rep? m )) (list #:op (reverse-form x) (#:rep? m))) + ((#:op x (#:rep m n)) (list #:op (reverse-form x) (list #:rep m n))) + ((#:op x (#:rep m )) (list #:op (reverse-form x) (list #:rep m))) + ((#:op x (#:rep? m n)) (list #:op (reverse-form x) (list #:rep? m n))) + ((#:op x (#:rep? m )) (list #:op (reverse-form x) (list #:rep? m))) ((#:op x #:? ) (list #:op (reverse-form x) #:?)) ((#:op x #:*?) (list #:op (reverse-form x) #:*?)) ((#:op x #:+?) (list #:op (reverse-form x) #:+?)) diff --git a/modules/language/python/module/re/parser.scm b/modules/language/python/module/re/parser.scm index 9ecc756..4d603c6 100644 --- a/modules/language/python/module/re/parser.scm +++ b/modules/language/python/module/re/parser.scm @@ -52,17 +52,20 @@ (define flags2 (f-list #:flags2 "(?" (mk-token (f* (f-reg! "[aiLmsux]"))) ")")) - +(define (bch f) (f-or! (f-seq (f-or! (f-tag "\\n") f-nl) + (f-out (list->string (list #\newline)))) + f)) (define bbody (f-cons (f-or! - (f-list #:range (mk-token (f-reg! ".")) - "-" (mk-token (f-reg! "."))) - (f-list #:ch (mk-token (f-reg! ".")))) + (f-list #:range (bch (mk-token (f-reg! "."))) + "-" (bch (mk-token (f-reg! ".")))) + (f-list #:ch (bch (mk-token (f-reg! "."))))) (ff* (f-or! - (f-list #:range (mk-token (f-not! (f-tag "]"))) + (f-list #:range (bch (mk-token (f-not! (f-tag "]")))) "-" - (mk-token (f-not! (f-tag "]")))) - (f-list #:ch (mk-token (f-not! (f-tag "]")))))))) + (bch (mk-token (f-not! (f-tag "]"))))) + (f-seq (f-tag " ") (f-out (list #:ch " "))) + (f-list #:ch (bch (mk-token (f-not! (f-tag "]"))))))))) (define (f-if a b c) (f-or! (f-seq a b) c)) (define choice diff --git a/modules/language/python/module/struct.scm b/modules/language/python/module/struct.scm index 5f5e872..b3d5b9f 100644 --- a/modules/language/python/module/struct.scm +++ b/modules/language/python/module/struct.scm @@ -1,19 +1,31 @@ (define-module (language python module struct) #:use-module (oop pf-objects) + #:use-module (ice-9 match) + #:use-module (rnrs bytevectors) + #:use-module (parser stis-parser) #:use-module (language python list) + #:use-module (language python bool) #:use-module (language python yield) + #:use-module (language python def) + #:use-module (language python exceptions) #:export (calcsize pack pack_into unpack unpack_from iter_unpack Struct error)) -(define-python-class StructError (Error)) +(define-python-class StructError (Exception)) + +(define bytevector-f32-ref bytevector-ieee-single-ref) +(define bytevector-f32-set! bytevector-ieee-single-set!) +(define bytevector-f64-ref bytevector-ieee-double-ref) +(define bytevector-f64-set! bytevector-ieee-double-set!) + (define error StructError) (define (analyze s) ;; Parser - (define f-head (make-token (f-or! (f-reg! "[@=<>!]") (f-out "@")))) - (define f-1 (make-token (f-reg! "[xcbB?hHiIlLqQnNefdspP]"))) - (define f-item (f-or! (f-list (make-token (f+ (f-tag! "[0-9]")) - string->number) + (define f-head (f-or! (mk-token (f-reg! "[@=<>!]")) (f-out "@"))) + (define f-1 (mk-token (f-reg! "[xcbB?hHiIlLqQnNefdspP]"))) + (define f-item (f-or! (f-list (mk-token (f+ (f-reg! "[0-9]")) + string->number) f-1) (f-list (f-out 1) f-1))) (define f-e (f-cons f-head (ff* f-item))) @@ -37,10 +49,10 @@ (define (incr i n) (+ i (if pack n - (+ n (let ((x (modulo n 8))) + (+ n (let ((x (modulo n 4))) (if (= x 0) 0 - (- 8 x))))))) + (- 4 x))))))) (define c-x (lambda (k) @@ -110,8 +122,8 @@ (cons (bytevector-u64-ref bv i end) (k bv (incr i 8)))))) - (define c-i c-i8) - (define c-I c-I8) + (define c-i c-i4) + (define c-I c-I4) (define c-l (if standard c-i4 c-l8)) (define c-L (if standard c-I4 c-L8)) @@ -134,7 +146,7 @@ (cons (bytevector-f64-ref bv i end) (k bv (incr i 8)))))) - (define c-g + (define c-e (lambda (k) (lambda (bv i) (let* ((num (bytevector-u16-ref bv i end)) @@ -143,7 +155,7 @@ (mant (logand #x3ff num))) (cons (cond ((= exp 0) - (* (if (= sign 0) 0 -1) + (* (if (= sign 0) 1 -1) (expt 2 -14) (string->number (format #f "0.~a" mant)))) ((= exp #x1f) @@ -151,9 +163,16 @@ (inf) (- (inf)))) (else - (* (if (= sign 0) 0 -1) + (* (if (= sign 0) 1 -1) (expt 2 (- exp 15)) - (string->number (format #f "1.~a" mant))))) + (+ 1 + (cond + ((< mant 10) + (/ mant 1000.0)) + ((< mant 100) + (/ mant 100.0)) + (else + (/ mant 10.0))))))) (k bv (incr i 2))))))) @@ -182,7 +201,7 @@ (let lp ((j 0) (l i)) (if (and (< j size) (< j N)) (begin - (bytevector-u8-set! bvv j (bytevector-u8-ref bv l) r) + (bytevector-u8-set! bvv j (bytevector-u8-ref bv l)) (lp (+ j 1) (+ l 1))) (cons bvv (k bv (incr i N))))))))) @@ -227,7 +246,7 @@ l (cons (list (- n 1) tp) l)))) bv i))) - (() (lambda (bv i) '()))))) + (() '())))) bv n)) (define (unpack format buffer) @@ -238,9 +257,9 @@ (unpacker (analyze format) buffer offset))) (define (iter_unpack format buffer) - (let ((l (analyze format)) - (n (len buffer)) - (m (calcsize l))) + (let* ((l format) + (n (len buffer)) + (m (calcsize l))) ((make-generator (lambda (yield) (let lp ((i 0)) @@ -250,60 +269,61 @@ (lp (+ i m)))))))))) (define (calcsize format) - (define type (car format)) - (define rest (cdr l)) - (define pack (member type '("@"))) - (define end (if (member type '("@")) - (native-endianness) - (if (member type '("<")) - 'little - 'big))) - (define standard (not (member type '("@")))) - - (define (sz p i) - (if p - i - (let ((x (modulo i 8))) - (if (= x 0) i (+ i (- 8 x)))))) - - (define (size n p tp) - (sz p - (match tp - ((or "x" "c" "B" "b" "?") - 1) - ((or "h" "H" "g") - 2) - ((or "i" "I" "f") - 4) - ((or "q" "Q" "n" "N" "d" "P") - 8) - ((or "l" "L") - (if standard 4 8)) - ((or "s" "p") - n)))) - - (if (string? format) - (calcsize (analyze format)) - (let ((type (car format))) - (let lp ((l (cdr format))) - (match l - (((1 tp) . l) - (+ (size 1 (null? l) tp))) - (((n (or "s" "p")) . l) - (+ (size n (null? l) tp) - (lp l))) - (((n tp) . l) - (+ (size (- n 1) #f tp) - (lp (cons (list 1 tp) l)))) - (() 0)))))) - + (let lp ((format format)) + (if (string? format) + (lp (analyze format)) + (let () + (define type (car format)) + (define rest (cdr format)) + (define pack (member type '("@"))) + (define end (if (member type '("@")) + (native-endianness) + (if (member type '("<")) + 'little + 'big))) + (define standard (not (member type '("@")))) + + (define (sz p i) + (if p + i + (let ((x (modulo i 4))) + (if (= x 0) i (+ i (- 4 x)))))) + + (define (size n p tp) + (sz p + (match tp + ((or "x" "c" "B" "b" "?") + 1) + ((or "h" "H" "e") + 2) + ((or "i" "I" "f") + 4) + ((or "q" "Q" "n" "N" "d" "P") + 8) + ((or "l" "L") + (if standard 4 8)) + ((or "s" "p") + n)))) + + (let lp ((l (cdr format))) + (match l + (((1 tp) . l) + (+ (size 1 (null? l) tp))) + (((n (and tp (or "s" "p"))) . l) + (+ (size n (null? l) tp) + (lp l))) + (((n tp) . l) + (+ (size (- n 1) #f tp) + (lp (cons (list 1 tp) l)))) + (() 0))))))) + (define (pack_into_ format bv offset l) - (let lp ((l format)) - (if (string? l) - (lp (analyze l)) + (let lp ((format format)) + (if (string? format) + (lp (analyze format)) (let () - (define type (car l)) - (define rest (cdr l)) + (define type (car format)) + (define rest (cdr format)) (define pack (member type '("@"))) (define end (if (member type '("@")) (native-endianness) @@ -311,7 +331,15 @@ 'little 'big))) (define standard (not (member type '("@")))) - + + (define (incr i n) + (+ i (if pack + n + (+ n (let ((x (modulo n 4))) + (if (= x 0) + 0 + (- 4 x))))))) + (define c-x (lambda (i) (bytevector-u8-set! bv i 0) @@ -320,52 +348,52 @@ (define c-c (lambda (i l) (bytevector-u8-set! bv i (pylist-ref (car l) 0)) - (values (inc i 1) (cdr l)))) + (values (incr i 1) (cdr l)))) (define c-b (lambda (i l) (bytevector-s8-set! bv i (car l)) - (values (inc i 1) (cdr l)))) + (values (incr i 1) (cdr l)))) (define c-B (lambda (i l) (bytevector-u8-set! bv i (car l)) - (values (inc i 1) (cdr l)))) + (values (incr i 1) (cdr l)))) (define c-? (lambda (i l) (bytevector-u8-set! bv i (if (bool (car l)) 1 0)) - (values (inc i 1) (cdr l)))) + (values (incr i 1) (cdr l)))) (define c-h (lambda (i l) (bytevector-s16-set! bv i (car l) end) - (values (inc i 2) (cdr l)))) + (values (incr i 2) (cdr l)))) (define c-H (lambda (i l) (bytevector-u16-set! bv i (car l) end) - (values (inc i 2) (cdr l)))) + (values (incr i 2) (cdr l)))) (define c-i (lambda (i l) (bytevector-s32-set! bv i (car l) end) - (values (inc i 4) (cdr l)))) + (values (incr i 4) (cdr l)))) (define c-I (lambda (i l) (bytevector-u32-set! bv i (car l) end) - (values (inc i 4) (cdr l)))) + (values (incr i 4) (cdr l)))) (define c-l8 (lambda (i l) (bytevector-s64-set! bv i (car l) end) - (values (inc i 8) (cdr l)))) + (values (incr i 8) (cdr l)))) (define c-L8 (lambda (i l) (bytevector-u64-set! bv i (car l) end) - (values (inc i 8) (cdr l)))) + (values (incr i 8) (cdr l)))) (define c-l (if standard c-i c-l8)) (define c-L (if standard c-I c-L8)) @@ -373,36 +401,36 @@ (define c-q (lambda (i l) (bytevector-s64-set! bv i (car l) end) - (values (inc i 8) (cdr l)))) + (values (incr i 8) (cdr l)))) (define c-Q (lambda (i l) (bytevector-u64-set! bv i (car l) end) - (values (inc i 8) (cdr l)))) + (values (incr i 8) (cdr l)))) (define c-n (lambda (i l) (bytevector-s64-set! bv i (car l) end) - (values (inc i 8) (cdr l)))) + (values (incr i 8) (cdr l)))) (define c-N (lambda (i l) (bytevector-u64-set! bv i (car l) end) - (values (inc i 8) (cdr l)))) + (values (incr i 8) (cdr l)))) (define c-e (let ((X (expt 2 -14)) (XX (expt 2 -24))) (lambda (i l) (let* ((x (car l)) - (s (>= x 0) 0 1) + (s (if (>= x 0) 0 1)) (x (abs x)) (e (if (< x X) (if (< x XX) #x1f - 0)) - (inexact->exact - (floor (+ (log2 x) 15)))) + 0) + (inexact->exact + (floor (+ (/ (log x) (log 2)) 15))))) (s? (= e 0)) (i? (= e #x1f)) (m (if i? @@ -414,26 +442,26 @@ (inexact->exact (floor (* x (expt 2 14))))))) - (x (logand (ash s 16) + (x (logior (ash s 16) (ash (logand e #x1f ) 10) (ash (logand m #x3ff) 00)))) (bytevector-u16-set! bv i x end) - (values (inc i 2) (cdr l)))))) + (values (incr i 2) (cdr l)))))) (define c-f (lambda (i l) (bytevector-f32-set! bv i (car l) end) - (values (inc i 4) (cdr l)))) + (values (incr i 4) (cdr l)))) (define c-d (lambda (i l) (bytevector-f64-set! bv i (car l) end) - (values (inc i 8) (cdr l)))) + (values (incr i 8) (cdr l)))) (define c-P (lambda (i l) (bytevector-u64-set! bv i (car l) end) - (values (inc i 8) (cdr l)))) + (values (incr i 8) (cdr l)))) (define c-s (lambda (i l n) @@ -443,7 +471,7 @@ (begin (bytevector-u8-set! bv k (pylist-ref x j)) (lp (+ j 1) (+ k 1))) - (values (inc i n) (cdr l))))))) + (values (incr i n) (cdr l))))))) (define c-p (lambda (i l n) @@ -461,7 +489,7 @@ (begin (bytevector-u8-set! bv k 0) (lp (+ j 1) (+ k 1))) - (values (inc i n) (cdr l))))))))) + (values (incr i n) (cdr l))))))))) (define tr (make-hash-table)) @@ -488,7 +516,7 @@ (hash-set! tr "p" c-p) (hash-set! tr "P" c-P) - (let lp ((p rest) (i offset) (l rest)) + (let lp ((p rest) (i offset) (l l)) (match p (((n (and tp (or "p" "s"))) . p) (call-with-values diff --git a/modules/language/python/module/textwrap.py b/modules/language/python/module/textwrap.py new file mode 100644 index 0000000..62e90ba --- /dev/null +++ b/modules/language/python/module/textwrap.py @@ -0,0 +1,478 @@ +module(textwrap) + +"""Text wrapping and filling. +""" + +# Copyright (C) 1999-2001 Gregory P. Ward. +# Copyright (C) 2002, 2003 Python Software Foundation. +# Written by Greg Ward <gward@python.net> + +import re + +__all__ = ['wrap', 'TextWrapper', 'fill', 'dedent', 'indent', 'shorten'] + +# Hardcode the recognized whitespace characters to the US-ASCII +# whitespace characters. The main reason for doing this is that +# some Unicode spaces (like \u00a0) are non-breaking whitespaces. +_whitespace = '\t\n\x0b\x0c\r ' + + +class TextWrapper: + """ + Object for wrapping/filling text. The public interface consists of + the wrap() and fill() methods; the other methods are just there for + subclasses to override in order to tweak the default behaviour. + If you want to completely replace the main wrapping algorithm, + you'll probably have to override _wrap_chunks(). + + Several instance attributes control various aspects of wrapping: + width (default: 70) + the maximum width of wrapped lines (unless break_long_words + is false) + initial_indent (default: "") + string that will be prepended to the first line of wrapped + output. Counts towards the line's width. + subsequent_indent (default: "") + string that will be prepended to all lines save the first + of wrapped output; also counts towards each line's width. + expand_tabs (default: true) + Expand tabs in input text to spaces before further processing. + Each tab will become 0 .. 'tabsize' spaces, depending on its position + in its line. If false, each tab is treated as a single character. + tabsize (default: 8) + Expand tabs in input text to 0 .. 'tabsize' spaces, unless + 'expand_tabs' is false. + replace_whitespace (default: true) + Replace all whitespace characters in the input text by spaces + after tab expansion. Note that if expand_tabs is false and + replace_whitespace is true, every tab will be converted to a + single space! + fix_sentence_endings (default: false) + Ensure that sentence-ending punctuation is always followed + by two spaces. Off by default because the algorithm is + (unavoidably) imperfect. + break_long_words (default: true) + Break words longer than 'width'. If false, those words will not + be broken, and some lines might be longer than 'width'. + break_on_hyphens (default: true) + Allow breaking hyphenated words. If true, wrapping will occur + preferably on whitespaces and right after hyphens part of + compound words. + drop_whitespace (default: true) + Drop leading and trailing whitespace from lines. + max_lines (default: None) + Truncate wrapped lines. + placeholder (default: ' [...]') + Append to the last line of truncated text. + """ + + unicode_whitespace_trans = {} + uspace = ord(' ') + for x in _whitespace: + unicode_whitespace_trans[ord(x)] = uspace + + # This funky little regex is just the trick for splitting + # text up into word-wrappable chunks. E.g. + # "Hello there -- you goof-ball, use the -b option!" + # splits into + # Hello/ /there/ /--/ /you/ /goof-/ball,/ /use/ /the/ /-b/ /option! + # (after stripping out empty strings). + word_punct = r'[\w!"\'&.,?]' + letter = r'[^\d\W]' + whitespace = r'[%s]' % re.escape(_whitespace) + nowhitespace = '[^' + whitespace[1:] + wordsep_re = re.compile(r''' + ( # any whitespace + %(ws)s+ + | # em-dash between words + (?<=%(wp)s) -{2,} (?=\w) + | # word, possibly hyphenated + %(nws)s+? (?: + # hyphenated word + -(?: (?<=%(lt)s{2}-) | (?<=%(lt)s-%(lt)s-)) + (?= %(lt)s -? %(lt)s) + | # end of word + (?=%(ws)s|\Z) + | # em-dash + (?<=%(wp)s) (?=-{2,}\w) + ) + )''' % {'wp': word_punct, 'lt': letter, + 'ws': whitespace, 'nws': nowhitespace}, + re.VERBOSE) + del word_punct, letter, nowhitespace + + # This less funky little regex just split on recognized spaces. E.g. + # "Hello there -- you goof-ball, use the -b option!" + # splits into + # Hello/ /there/ /--/ /you/ /goof-ball,/ /use/ /the/ /-b/ /option!/ + wordsep_simple_re = re.compile(r'(%s+)' % whitespace) + del whitespace + + # XXX this is not locale- or charset-aware -- string.lowercase + # is US-ASCII only (and therefore English-only) + sentence_end_re = re.compile(r'[a-z]' # lowercase letter + r'[\.\!\?]' # sentence-ending punct. + r'[\"\']?' # optional end-of-quote + r'\Z') # end of chunk + + def __init__(self, + width=70, + initial_indent="", + subsequent_indent="", + expand_tabs=True, + replace_whitespace=True, + fix_sentence_endings=False, + break_long_words=True, + drop_whitespace=True, + break_on_hyphens=True, + tabsize=8, + *, + max_lines=None, + placeholder=' [...]'): + self.width = width + self.initial_indent = initial_indent + self.subsequent_indent = subsequent_indent + self.expand_tabs = expand_tabs + self.replace_whitespace = replace_whitespace + self.fix_sentence_endings = fix_sentence_endings + self.break_long_words = break_long_words + self.drop_whitespace = drop_whitespace + self.break_on_hyphens = break_on_hyphens + self.tabsize = tabsize + self.max_lines = max_lines + self.placeholder = placeholder + + + # -- Private methods ----------------------------------------------- + # (possibly useful for subclasses to override) + + def _munge_whitespace(self, text): + """_munge_whitespace(text : string) -> string + + Munge whitespace in text: expand tabs and convert all other + whitespace characters to spaces. Eg. " foo\\tbar\\n\\nbaz" + becomes " foo bar baz". + """ + if self.expand_tabs: + text = text.expandtabs(self.tabsize) + if self.replace_whitespace: + text = text.translate(self.unicode_whitespace_trans) + return text + + + def _split(self, text): + """_split(text : string) -> [string] + + Split the text to wrap into indivisible chunks. Chunks are + not quite the same as words; see _wrap_chunks() for full + details. As an example, the text + Look, goof-ball -- use the -b option! + breaks into the following chunks: + 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ', + 'use', ' ', 'the', ' ', '-b', ' ', 'option!' + if break_on_hyphens is True, or in: + 'Look,', ' ', 'goof-ball', ' ', '--', ' ', + 'use', ' ', 'the', ' ', '-b', ' ', option!' + otherwise. + """ + if self.break_on_hyphens is True: + chunks = self.wordsep_re.split(text) + else: + chunks = self.wordsep_simple_re.split(text) + chunks = [c for c in chunks if c] + return chunks + + def _fix_sentence_endings(self, chunks): + """_fix_sentence_endings(chunks : [string]) + + Correct for sentence endings buried in 'chunks'. Eg. when the + original text contains "... foo.\\nBar ...", munge_whitespace() + and split() will convert that to [..., "foo.", " ", "Bar", ...] + which has one too few spaces; this method simply changes the one + space to two. + """ + i = 0 + patsearch = self.sentence_end_re.search + while i < len(chunks)-1: + if chunks[i+1] == " " and patsearch(chunks[i]): + chunks[i+1] = " " + i += 2 + else: + i += 1 + + def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): + """_handle_long_word(chunks : [string], + cur_line : [string], + cur_len : int, width : int) + + Handle a chunk of text (most likely a word, not whitespace) that + is too long to fit in any line. + """ + # Figure out when indent is larger than the specified width, and make + # sure at least one character is stripped off on every pass + if width < 1: + space_left = 1 + else: + space_left = width - cur_len + + # If we're allowed to break long words, then do so: put as much + # of the next chunk onto the current line as will fit. + if self.break_long_words: + cur_line.append(reversed_chunks[-1][:space_left]) + reversed_chunks[-1] = reversed_chunks[-1][space_left:] + + # Otherwise, we have to preserve the long word intact. Only add + # it to the current line if there's nothing already there -- + # that minimizes how much we violate the width constraint. + elif not cur_line: + cur_line.append(reversed_chunks.pop()) + + # If we're not allowed to break long words, and there's already + # text on the current line, do nothing. Next time through the + # main loop of _wrap_chunks(), we'll wind up here again, but + # cur_len will be zero, so the next line will be entirely + # devoted to the long word that we can't handle right now. + + def _wrap_chunks(self, chunks): + """_wrap_chunks(chunks : [string]) -> [string] + + Wrap a sequence of text chunks and return a list of lines of + length 'self.width' or less. (If 'break_long_words' is false, + some lines may be longer than this.) Chunks correspond roughly + to words and the whitespace between them: each chunk is + indivisible (modulo 'break_long_words'), but a line break can + come between any two chunks. Chunks should not have internal + whitespace; ie. a chunk is either all whitespace or a "word". + Whitespace chunks will be removed from the beginning and end of + lines, but apart from that whitespace is preserved. + """ + lines = [] + if self.width <= 0: + raise ValueError("invalid width %r (must be > 0)" % self.width) + if self.max_lines is not None: + if self.max_lines > 1: + indent = self.subsequent_indent + else: + indent = self.initial_indent + if len(indent) + len(self.placeholder.lstrip()) > self.width: + raise ValueError("placeholder too large for max width") + + # Arrange in reverse order so items can be efficiently popped + # from a stack of chucks. + chunks.reverse() + + while chunks: + + # Start the list of chunks that will make up the current line. + # cur_len is just the length of all the chunks in cur_line. + cur_line = [] + cur_len = 0 + + # Figure out which static string will prefix this line. + if lines: + indent = self.subsequent_indent + else: + indent = self.initial_indent + + # Maximum width for this line. + width = self.width - len(indent) + + # First chunk on line is whitespace -- drop it, unless this + # is the very beginning of the text (ie. no lines started yet). + if self.drop_whitespace and chunks[-1].strip() == '' and lines: + del chunks[-1] + + while chunks: + l = len(chunks[-1]) + + # Can at least squeeze this chunk onto the current line. + if cur_len + l <= width: + cur_line.append(chunks.pop()) + cur_len += l + + # Nope, this line is full. + else: + break + + # The current line is full, and the next chunk is too big to + # fit on *any* line (not just this one). + if chunks and len(chunks[-1]) > width: + self._handle_long_word(chunks, cur_line, cur_len, width) + cur_len = sum(map(len, cur_line)) + + # If the last chunk on this line is all whitespace, drop it. + if self.drop_whitespace and cur_line and cur_line[-1].strip() == '': + cur_len -= len(cur_line[-1]) + del cur_line[-1] + + if cur_line: + if (self.max_lines is None or + len(lines) + 1 < self.max_lines or + (not chunks or + self.drop_whitespace and + len(chunks) == 1 and + not chunks[0].strip()) and cur_len <= width): + # Convert current line back to a string and store it in + # list of all lines (return value). + lines.append(indent + ''.join(cur_line)) + else: + while cur_line: + if (cur_line[-1].strip() and + cur_len + len(self.placeholder) <= width): + cur_line.append(self.placeholder) + lines.append(indent + ''.join(cur_line)) + break + cur_len -= len(cur_line[-1]) + del cur_line[-1] + else: + if lines: + prev_line = lines[-1].rstrip() + if (len(prev_line) + len(self.placeholder) <= + self.width): + lines[-1] = prev_line + self.placeholder + break + lines.append(indent + self.placeholder.lstrip()) + break + + return lines + + def _split_chunks(self, text): + text = self._munge_whitespace(text) + return self._split(text) + + # -- Public interface ---------------------------------------------- + + def wrap(self, text): + """wrap(text : string) -> [string] + + Reformat the single paragraph in 'text' so it fits in lines of + no more than 'self.width' columns, and return a list of wrapped + lines. Tabs in 'text' are expanded with string.expandtabs(), + and all other whitespace characters (including newline) are + converted to space. + """ + chunks = self._split_chunks(text) + if self.fix_sentence_endings: + self._fix_sentence_endings(chunks) + return self._wrap_chunks(chunks) + + def fill(self, text): + """fill(text : string) -> string + + Reformat the single paragraph in 'text' to fit in lines of no + more than 'self.width' columns, and return a new string + containing the entire wrapped paragraph. + """ + return "\n".join(self.wrap(text)) + +# -- Convenience interface --------------------------------------------- + +def wrap(text, width=70, **kwargs): + """Wrap a single paragraph of text, returning a list of wrapped lines. + + Reformat the single paragraph in 'text' so it fits in lines of no + more than 'width' columns, and return a list of wrapped lines. By + default, tabs in 'text' are expanded with string.expandtabs(), and + all other whitespace characters (including newline) are converted to + space. See TextWrapper class for available keyword args to customize + wrapping behaviour. + """ + w = TextWrapper(width=width, **kwargs) + return w.wrap(text) + +def fill(text, width=70, **kwargs): + """Fill a single paragraph of text, returning a new string. + + Reformat the single paragraph in 'text' to fit in lines of no more + than 'width' columns, and return a new string containing the entire + wrapped paragraph. As with wrap(), tabs are expanded and other + whitespace characters converted to space. See TextWrapper class for + available keyword args to customize wrapping behaviour. + """ + w = TextWrapper(width=width, **kwargs) + return w.fill(text) + +def shorten(text, width, **kwargs): + """Collapse and truncate the given text to fit in the given width. + + The text first has its whitespace collapsed. If it then fits in + the *width*, it is returned as is. Otherwise, as many words + as possible are joined and then the placeholder is appended:: + + >>> textwrap.shorten("Hello world!", width=12) + 'Hello world!' + >>> textwrap.shorten("Hello world!", width=11) + 'Hello [...]' + """ + w = TextWrapper(width=width, max_lines=1, **kwargs) + return w.fill(' '.join(text.strip().split())) + +# -- Loosely related functionality ------------------------------------- + +_whitespace_only_re = re.compile('^[ \t]+$', re.MULTILINE) +_leading_whitespace_re = re.compile('(^[ \t]*)(?:[^ \t\n])', re.MULTILINE) + +def dedent(text): + """Remove any common leading whitespace from every line in `text`. + + This can be used to make triple-quoted strings line up with the left + edge of the display, while still presenting them in the source code + in indented form. + + Note that tabs and spaces are both treated as whitespace, but they + are not equal: the lines " hello" and "\\thello" are + considered to have no common leading whitespace. (This behaviour is + new in Python 2.5; older versions of this module incorrectly + expanded tabs before searching for common leading whitespace.) + """ + # Look for the longest leading string of spaces and tabs common to + # all lines. + margin = None + text = _whitespace_only_re.sub('', text) + indents = _leading_whitespace_re.findall(text) + for indent in indents: + if margin is None: + margin = indent + + # Current line more deeply indented than previous winner: + # no change (previous winner is still on top). + elif indent.startswith(margin): + pass + + # Current line consistent with and no deeper than previous winner: + # it's the new winner. + elif margin.startswith(indent): + margin = indent + + + # Find the largest common whitespace between current line and previous + # winner. + else: + for i, (x, y) in enumerate(zip(margin, indent)): + if x != y: + margin = margin[:i] + break + else: + margin = margin[:len(indent)] + + if margin: + text = re.sub(r'(?m)^' + margin, '', text) + return text + + +def indent(text, prefix, predicate=None): + """Adds 'prefix' to the beginning of selected lines in 'text'. + + If 'predicate' is provided, 'prefix' will only be added to the lines + where 'predicate(line)' is True. If 'predicate' is not provided, + it will default to adding 'prefix' to all non-empty lines that do not + consist solely of whitespace characters. + """ + if predicate is None: + def predicate(line): + return line.strip() + + def prefixed_lines(): + for line in text.splitlines(True): + yield (prefix + line if predicate(line) else line) + return ''.join(prefixed_lines()) |