## {{{ 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 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 ' #__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)