Welcome, guest | Sign In | My Account | Store | Cart

Python 2.3 module textwrap can justify text in three modes: left/right/center. Sometimes 'align' mode become more useful.

Python, 144 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
## {{{ Recipe 414870 (r3): Align text string using spaces between words to fit specified width 
#!/usr/bin/env python
'''
align_string.py

Align string with spaces between words to fit specified width

Author: Denis Barmenkov <denis.barmenkov@gmail.com>

Copyright: this code is free, but if you want to use it, 
           please keep this multiline comment along with function source. 
           Thank you.

2005-05-22 13:27 - first revision
2010-03-09 17:01 - added align_paragraph()
2010-03-09 17:56 - added check for paragraph's last line
2010-03-09 18:16 - fork for pipe align sample script
'''
import re
import sys
import textwrap

__author__ = 'Denis Barmenkov <denis.barmenkov@gmail.com>'
#__source__ = 'http://code.activestate.com/recipes/414870/'
__source__ = 'http://code.activestate.com/recipes/577093/'

def items_len(l):
    return sum([ len(x) for x in l] )

lead_re = re.compile(r'(^\s+)(.*)$')

def align_string(s, width, last_paragraph_line=0):
    '''
    align string to specified width 
    '''
    # detect and save leading whitespace
    m = lead_re.match(s) 
    if m is None:
        left, right, w = '', s, width
    else:
        left, right, w = m.group(1), m.group(2), width - len(m.group(1))

    items = right.split()

    # add required space to each words, exclude last item
    for i in range(len(items) - 1):
        items[i] += ' '

    if not last_paragraph_line:
        # number of spaces to add
        left_count = w - items_len(items)
        while left_count > 0 and len(items) > 1:
            for i in range(len(items) - 1):
                items[i] += ' '
                left_count -= 1
                if left_count < 1:  
                    break

    res = left + ''.join(items)
    return res

def align_paragraph(paragraph, width, debug=0):
    '''
    align paragraph to specified width,
    returns list of paragraph lines
    '''
    lines = list()
    if type(paragraph) == type(lines):
        lines.extend(paragraph)
    elif type(paragraph) == type(''):
        lines.append(paragraph)
    elif type(paragraph) == type(tuple()):
        lines.extend(list(paragraph))
    else:
        raise TypeError, 'Unsopported paragraph type: %r' % type(paragraph)

    flatten_para = ' '.join(lines)

    splitted = textwrap.wrap(flatten_para, width) 
    if debug:
        print 'textwrap:\n%s\n' % '\n'.join(splitted)

    wrapped = list()
    while len(splitted) > 0:
        line = splitted.pop(0)
        if len(splitted) == 0:
            last_paragraph_line = 1
        else:
            last_paragraph_line = 0
        aligned = align_string(line, width, last_paragraph_line)
        wrapped.append(aligned)

    if debug:
        print 'textwrap & align_string:\n%s\n' % '\n'.join(wrapped)

    return wrapped

def align_pipe(width, inpipe=None, outpipe=None):
    '''
    read paragraphs from one pipe, 
    align them and puts to second pipe
    '''
    if inpipe is None:
        inpipe = sys.stdin
    if outpipe is None:
        outpipe = sys.stdout

    paragraph_lines = list()

    def flush_paragraph(paragraph_lines):
        '''
        align paragraph, put to output, 
        reset state
        '''
        if len(paragraph_lines) > 0:
            aligned_paragraph = align_paragraph(paragraph_lines, width)
            for line in aligned_paragraph:
                outpipe.write(line + '\n')
            paragraph_lines = list()

        return paragraph_lines

    for rawline in inpipe:
        line = rawline.splitlines()[0]

        # paragraph closed by empty line
        if line.strip() == '':
            paragraph_lines = flush_paragraph(paragraph_lines)
            outpipe.write('\n')
            continue

        # paragraph started with space
        if line[0].isspace():
            paragraph_lines = flush_paragraph(paragraph_lines)

        paragraph_lines.append(line)

    paragraph_lines = flush_paragraph(paragraph_lines)

if __name__ == '__main__':
    width = 75
    # or 
    # width = int(sys.argv[1])
    align_pipe(width)

Call this script on *nixes:

chmod +x align_pipe.py
cat text_file | align_pipe.py

or on Windows:

type text_file | python align_pipe.py

See also: http://docs.python.org/using/index.html

1 comment

Zaq 14 years, 1 month ago  # | flag

Thank you very much!

Your script will help me a lot. I am too impressed that fast way you code.

Thanks a lot from Brazil!