pythonterminalformattingapt

apt like column output - python library


Debian's apt tool outputs results in uniform width columns. For instance, try running "aptitude search svn" .. and all names appear in the first column of the same width.

Now if you resize the terminal, the column width is adjusted accordingly.

Is there a Python library that enables one to do this? Note that the library has to be aware of the terminal width and take a table as input - which could be, for instance, [('rapidsvn', 'A GUI client for subversion'), ...] .. and you may also specify a max-width for the first column (or any column). Also note how the string in the second column below is trimmed if exceeds the terminal width .. thus not introducing the undesired second line.

$ aptitude search svn
[...]
p   python-svn-dbg                    - A(nother) Python interface to Subversion (d
v   python2.5-svn                     -                                            
v   python2.6-svn                     -                                            
p   rapidsvn                          - A GUI client for subversion                
p   statsvn                           - SVN repository statistics                  
p   svn-arch-mirror                   - one-way mirroring from Subversion to Arch r
p   svn-autoreleasedeb                - Automatically release/upload debian package
p   svn-buildpackage                  - helper programs to maintain Debian packages
p   svn-load                          - An enhanced import facility for Subversion 
p   svn-workbench                     - A Workbench for Subversion                 
p   svnmailer                         - extensible Subversion commit notification t
p   websvn                            - interface for subversion repositories writt
$

EDIT: (in response to Alex's answer below) ... the output will be similar to 'aptitude search' in that 1) only the last column (which is the only column with the longest string in a row) is to be trimmed, 2) there are typically 2-4 columns only, but the last column ("description") is expected to take at least half the terminal width. 3) all rows contain equal number of columns, 4) all entries are strings only


Solution

  • I don't think there's a general, cross-platform way to "get the width of the terminal" -- most definitely NOT "look at the COLUMNS environment variable" (see my comment on the question). On Linux and Mac OS X (and I expect all modern Unix versions),

    curses.wrapper(lambda _: curses.tigetnum('cols'))
    

    returns the number of columns; but I don't know if wcurses supports this in Windows.

    Once you do have (from os.environ['COLUMNS'] if you insist, or via curses, or from an oracle, or defaulted to 80, or any other way you like) the desired output width, the rest is quite feasible. It's finnicky work, with many chances for off-by-one kinds of errors, and very vulnerable to a lot of detailed specs that you don't make entirely clear, such as: which column gets cut to avoid wrapping -- it it always the last one, or...? How come you're showing 3 columns in the sample output when according to your question only two are passed in...? what is supposed to happen if not all rows have the same number of columns? must all entries in table be strings? and many, many other mysteries of this ilk.

    So, taking somewhat-arbitrary guesses for all the specs that you don't express, one approach might be something like...:

    import sys
    
    def colprint(totwidth, table):
      numcols = max(len(row) for row in table)
      # ensure all rows have >= numcols columns, maybe empty
      padded = [row+numcols*('',) for row in table]
      # compute col widths, including separating space (except for last one)
      widths = [ 1 + max(len(x) for x in column) for column in zip(*padded)]
      widths[-1] -= 1
      # drop or truncate columns from the right in order to fit
      while sum(widths) > totwidth:
        mustlose = sum(widths) - totwidth
        if widths[-1] <= mustlose:
          del widths[-1]
        else:
          widths[-1] -= mustlose
          break
      # and finally, the output phase!
      for row in padded:
        for w, i in zip(widths, row):
          sys.stdout.write('%*s' % (-w, i[:w]))
        sys.stdout.write('\n')