summaryrefslogtreecommitdiff
path: root/cesar
diff options
context:
space:
mode:
authorschodet2009-10-06 21:55:14 +0000
committerschodet2009-10-06 21:55:14 +0000
commit2346d0d6647f144aa921f9715d3d5e817434d3da (patch)
tree9c7d8f260fdf014aa6ea0bedd176e33d0c47658e /cesar
parent3e1b66ec737f6598a8a4c6f66ae05f3cadde8f46 (diff)
cesar/tools/dissect: add dissection python tool
This is to be used with test_phy for example to decode sniffer data. git-svn-id: svn+ssh://pessac/svn/cesar/trunk@5971 017c9cb6-072f-447c-8318-d5b54f68fe89
Diffstat (limited to 'cesar')
-rw-r--r--cesar/tools/dissect/dissect/__init__.py0
-rw-r--r--cesar/tools/dissect/dissect/bucket.py142
-rw-r--r--cesar/tools/dissect/dissect/desc.py56
-rw-r--r--cesar/tools/dissect/dissect/fc.py270
-rw-r--r--cesar/tools/dissect/dissect/format.py104
-rw-r--r--cesar/tools/dissect/dissect/formater.py124
6 files changed, 696 insertions, 0 deletions
diff --git a/cesar/tools/dissect/dissect/__init__.py b/cesar/tools/dissect/dissect/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/cesar/tools/dissect/dissect/__init__.py
diff --git a/cesar/tools/dissect/dissect/bucket.py b/cesar/tools/dissect/dissect/bucket.py
new file mode 100644
index 0000000000..a5b19028fc
--- /dev/null
+++ b/cesar/tools/dissect/dissect/bucket.py
@@ -0,0 +1,142 @@
+"""Bits bucket."""
+
+def word (size, v):
+ """Return a binary list out of a word.
+
+ >>> word (8, 0x00)
+ [0, 0, 0, 0, 0, 0, 0, 0]
+ >>> word (8, 0xff)
+ [1, 1, 1, 1, 1, 1, 1, 1]
+ >>> word (8, 0x35)
+ [1, 0, 1, 0, 1, 1, 0, 0]
+
+ """
+ return [ (v >> i) & 1 for i in range (0, size) ]
+
+def words (size, list):
+ """Return a binary list out of a list of words.
+
+ >>> words (8, (0x00, 0xff, 0x35))
+ [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0]
+
+ """
+ return sum ((word (size, i) for i in list), [])
+
+def to_word (data):
+ """Make a word from bit data.
+
+ >>> to_word ([1, 0, 1, 0, 1, 1, 0, 0])
+ 53
+
+ """
+ v = 0
+ p = 1
+ for b in data:
+ v += b * p
+ p *= 2
+ return v
+
+class Bucket:
+
+ def __init__ (self, data = None):
+ """Initialise a bit bucket.
+
+ >>> b = Bucket (words (2, (0, 1, 2, 3)))
+ >>> b.data
+ [0, 0, 1, 0, 0, 1, 1, 1]
+
+ """
+ self.data = data
+
+ def __getitem__ (self, index):
+ """Return bit at given index.
+
+ >>> b = Bucket (words (2, (0, 1, 2, 3)))
+ >>> b[0]
+ 0
+ >>> b[2]
+ 1
+
+ """
+ return self.data[index]
+
+ def __getslice__ (self, start, end):
+ """Return number at given slice index.
+
+ >>> b = Bucket (words (2, (0, 1, 2, 3)))
+ >>> b[0:2]
+ 0
+ >>> b[2:6]
+ 9
+
+ """
+ return to_word (self.data[start:end])
+
+ def reader (self):
+ """Return a bit bucket reader.
+
+ >>> b = Bucket (words (2, (0, 1, 2, 3)))
+ >>> r = b.reader ()
+
+ """
+ return BucketReader (self)
+
+class BucketReader:
+
+ def __init__ (self, bucket):
+ self.data = bucket.data
+
+ def read (self, bits):
+ """Read bits from the reader.
+
+ >>> r = Bucket (words (2, (0, 1, 2, 3))).reader ()
+ >>> r.read (3)
+ 4
+ >>> r.read (4)
+ 12
+ >>> r.read (10)
+ Traceback (most recent call last):
+ ...
+ IndexError
+
+ """
+ if bits > len (self.data):
+ raise IndexError
+ ex = self.data[0:bits]
+ self.data[0:bits] = []
+ return to_word (ex)
+
+ def __getitem__ (self, index):
+ """Shortcut for read.
+
+ >>> r = Bucket (words (2, (0, 1, 2, 3))).reader ()
+ >>> r[3]
+ 4
+ >>> r[4]
+ 12
+ >>> r[10]
+ Traceback (most recent call last):
+ ...
+ IndexError
+
+ """
+ return self.read (index)
+
+ def __len__ (self):
+ """Get remaining bits number.
+
+ >>> r = Bucket (words (2, (0, 1, 2, 3))).reader ()
+ >>> r.read (3)
+ 4
+ >>> len (r)
+ 5
+
+ """
+ return len (self.data)
+
+def _test ():
+ import doctest
+ doctest.testmod ()
+
+if __name__ == '__main__':
+ _test ()
diff --git a/cesar/tools/dissect/dissect/desc.py b/cesar/tools/dissect/dissect/desc.py
new file mode 100644
index 0000000000..d603623a21
--- /dev/null
+++ b/cesar/tools/dissect/dissect/desc.py
@@ -0,0 +1,56 @@
+"""Data descriptor, include name, size and format."""
+
+class desc:
+ """Data descriptor.
+
+ >>> import format
+ >>> d = desc ('afield', 2, format.enum, 'zero', 'one', 'two', 'three')
+ >>> d.name
+ 'afield'
+ >>> d.short
+ 'afield'
+ >>> d.bits
+ 2
+ >>> d.format (1)
+ 'one'
+ >>> d = desc ('bfield', 8, format.hex)
+ >>> d.format (66)
+ '0x42'
+ >>> e = format.enum (1, 'false', 'true')
+ >>> d = desc ('cfield', 1, e)
+ >>> d.format (0)
+ 'false'
+ >>> d = desc ('dfield', 2, e)
+ Traceback (most recent call last):
+ ...
+ TypeError: incompatible number of bits
+ >>> d = desc ('efield', 8, format.hex, short = 'e')
+ >>> d.short
+ 'e'
+
+ """
+
+ def __init__ (self, name, bits, format, *format_args, **options):
+ self.name = name
+ self.short = name
+ self.bits = bits
+ # format can be a format instance or a format constructor.
+ if format_args or not hasattr (format, 'bits'):
+ self.format = format (bits, *format_args)
+ else:
+ if bits != format.bits:
+ raise TypeError ("incompatible number of bits")
+ self.format = format
+ # Options.
+ for k, v in options.iteritems ():
+ if k == 'short':
+ self.short = v
+ else:
+ raise KeyError, "unknown option %s" % k
+
+def _test ():
+ import doctest
+ doctest.testmod ()
+
+if __name__ == '__main__':
+ _test ()
diff --git a/cesar/tools/dissect/dissect/fc.py b/cesar/tools/dissect/dissect/fc.py
new file mode 100644
index 0000000000..c4d717b13c
--- /dev/null
+++ b/cesar/tools/dissect/dissect/fc.py
@@ -0,0 +1,270 @@
+from desc import desc
+import format
+
+format_dt_av = format.enum (3, 'BEACON', 'SOF', 'SACK', 'RTSCTS', 'SOUND',
+ 'RSOF', 'DT6', 'DT7')
+format_mfs_cmd_data = format.enum (3, 'INIT', 'IN_SYNC', 'RE_SYNC',
+ 'RELEASE', 'NOP', 'RES=5', 'RES=6', 'RES=7')
+format_mfs_cmd_mgnt = format.enum (3, 'mINIT', 'mIN_SYNC', 'mRE_SYNC',
+ 'mRELEASE', 'mNOP', 'mRES=5', 'mRES=6', 'mRES=7')
+format_mfs_rsp_data = format.enum (2, "ACK", "NACK", "FAIL", "HOLD")
+format_mfs_rsp_mgnt = format.enum (2, "mACK", "mNACK", "mFAIL", "mHOLD")
+format_sackt = format.enum (2, "m", "mc", "nr", "u")
+
+class FC:
+ """Base class for other FC."""
+
+ def __init__ (self, bucket):
+ r = bucket.reader ()
+ self.fields_list = [ ]
+ self.fields_dict = { }
+ for d in self.desc:
+ v = r.read (d.bits)
+ self.fields_list.append (v)
+ self.fields_dict[d.name] = v
+
+ def __getitem__ (self, index):
+ try:
+ return self.fields_list[index]
+ except TypeError:
+ return self.fields_dict[index]
+
+ def __getattr__ (self, name):
+ try:
+ return self.fields_dict[name]
+ except KeyError:
+ raise AttributeError
+
+ def __len__ (self):
+ return len (self.desc)
+
+ def __repr__ (self):
+ return '<' + self.name + '>'
+
+class BEACON (FC):
+
+ desc = (
+ desc ('dt_av', 3, format.unsigned, short = 'dt'),
+ desc ('access', 1, format.unsigned, short = 'a'),
+ desc ('snid', 4, format.unsigned),
+ desc ('bts', 32, format.hex),
+ desc ('bto0', 16, format.signed),
+ desc ('bto1', 16, format.signed),
+ desc ('bto2', 16, format.signed),
+ desc ('bto3', 16, format.signed),
+ desc ('fccs_av', 24, format.hex),
+ )
+
+ name = 'BEACON'
+
+ def __init__ (self, bucket):
+ FC.__init__ (self, bucket)
+
+class SOF (FC):
+
+ desc = (
+ desc ('dt_av', 3, format.unsigned, short = 'dt'),
+ desc ('access', 1, format.unsigned, short = 'a'),
+ desc ('snid', 4, format.unsigned),
+ desc ('stei', 8, format.hex),
+ desc ('dtei', 8, format.hex),
+ desc ('lid', 8, format.hex),
+ desc ('cfs', 1, format.unsigned),
+ desc ('bdf', 1, format.unsigned),
+ desc ('hp10df', 1, format.unsigned),
+ desc ('hp11df', 1, format.unsigned),
+ desc ('eks', 4, format.unsigned),
+ desc ('ppb', 8, format.hex),
+ desc ('ble', 8, format.hex),
+ desc ('pbsz', 1, format.unsigned),
+ desc ('num_sym', 2, format.unsigned),
+ desc ('tmi_av', 5, format.unsigned),
+ desc ('fl_av', 12, format.hex),
+ desc ('mpdu_cnt', 2, format.unsigned),
+ desc ('burst_cnt', 2, format.unsigned),
+ desc ('bbf', 1, format.unsigned),
+ desc ('mrtfl', 4, format.hex),
+ desc ('dcppcf', 1, format.unsigned),
+ desc ('mcf', 1, format.unsigned),
+ desc ('mnbf', 1, format.unsigned),
+ desc ('rsr', 1, format.unsigned),
+ desc ('clst', 1, format.unsigned),
+ desc ('mfs_cmd_mgmt', 3, format_mfs_cmd_mgnt, short = 'mcmd'),
+ desc ('mfs_cmd_data', 3, format_mfs_cmd_data, short = 'cmd'),
+ desc ('mfs_rsp_mgmt', 2, format_mfs_rsp_mgnt, short = 'mrsp'),
+ desc ('mfs_rsp_data', 2, format_mfs_rsp_data, short = 'rsp'),
+ desc ('bm_sacki', 4, format.hex),
+ desc ('fccs_av', 24, format.hex),
+ )
+
+ name = 'SOF'
+
+ def __init__ (self, bucket):
+ FC.__init__ (self, bucket)
+
+class SACK (FC):
+
+ desc = (
+ desc ('dt_av', 3, format.unsigned, short = 'dt'),
+ desc ('access', 1, format.unsigned, short = 'a'),
+ desc ('snid', 4, format.unsigned),
+ desc ('dtei', 8, format.hex),
+ desc ('cfs', 1, format.unsigned),
+ desc ('bdf', 1, format.unsigned),
+ desc ('svn', 1, format.unsigned),
+ desc ('rrtf', 1, format.unsigned),
+ desc ('mfs_rsp_data', 2, format_mfs_rsp_data, short = 'rsp'),
+ desc ('mfs_rsp_mgmt', 2, format_mfs_rsp_mgnt, short = 'mrsp'),
+ desc ('sackt3', 2, format_sackt, short = 'st3'),
+ desc ('sackt2', 2, format_sackt, short = 'st2'),
+ desc ('sackt1', 2, format_sackt, short = 'st1'),
+ desc ('sackt0', 2, format_sackt, short = 'st0'),
+ desc ('sacki0', 32, format.hex),
+ desc ('sacki1', 32, format.hex),
+ desc ('sacki2', 8, format.hex),
+ desc ('fccs_av', 24, format.hex),
+ )
+
+ name = 'SACK'
+
+ def __init__ (self, bucket):
+ FC.__init__ (self, bucket)
+
+class RTSCTS (FC):
+
+ desc = (
+ desc ('dt_av', 3, format.unsigned, short = 'dt'),
+ desc ('access', 1, format.unsigned, short = 'a'),
+ desc ('snid', 4, format.unsigned),
+ desc ('stei', 8, format.hex),
+ desc ('dtei', 8, format.hex),
+ desc ('lid', 8, format.hex),
+ desc ('cfs', 1, format.unsigned),
+ desc ('bdf', 1, format.unsigned),
+ desc ('hp10df', 1, format.unsigned),
+ desc ('hp11df', 1, format.unsigned),
+ desc ('rtsf', 1, format.unsigned),
+ desc ('igf', 1, format.unsigned),
+ desc ('mnbf', 1, format.unsigned),
+ desc ('mcf', 1, format.unsigned),
+ desc ('dur', 14, format.hex),
+ desc ('pad0', 10, format.hex),
+ desc ('pad1', 32, format.hex),
+ desc ('pad2', 8, format.hex),
+ desc ('fccs_av', 24, format.hex),
+ )
+
+ name = 'RTSCTS'
+
+ def __init__ (self, bucket):
+ FC.__init__ (self, bucket)
+
+class SOUND (FC):
+
+ desc = (
+ desc ('dt_av', 3, format.unsigned, short = 'dt'),
+ desc ('access', 1, format.unsigned, short = 'a'),
+ desc ('snid', 4, format.unsigned),
+ desc ('stei', 8, format.hex),
+ desc ('dtei', 8, format.hex),
+ desc ('lid', 8, format.hex),
+ desc ('cfs', 1, format.unsigned),
+ desc ('pbsz', 1, format.unsigned),
+ desc ('bdf', 1, format.unsigned),
+ desc ('saf', 1, format.unsigned),
+ desc ('scf', 1, format.unsigned),
+ desc ('req_tm', 3, format.unsigned),
+ desc ('fl_av', 12, format.hex),
+ desc ('mpdu_cnt', 2, format.unsigned),
+ desc ('pad0', 2, format.hex),
+ desc ('ppb', 8, format.hex),
+ desc ('src', 8, format.unsigned),
+ desc ('pad1', 24, format.hex),
+ desc ('pad2', 8, format.hex),
+ desc ('fccs_av', 24, format.hex),
+ )
+
+ name = 'SOUND'
+
+ def __init__ (self, bucket):
+ FC.__init__ (self, bucket)
+
+class RSOF (FC):
+
+ desc = (
+ desc ('dt_av', 3, format.unsigned, short = 'dt'),
+ desc ('access', 1, format.unsigned, short = 'a'),
+ desc ('snid', 4, format.unsigned),
+ desc ('dtei', 8, format.hex),
+ desc ('cfs', 1, format.unsigned),
+ desc ('bdf', 1, format.unsigned),
+ desc ('svn', 1, format.unsigned),
+ desc ('rrtf', 1, format.unsigned),
+ desc ('mfs_rsp_data', 2, format_mfs_rsp_data, short = 'rsp'),
+ desc ('mfs_rsp_mgmt', 2, format_mfs_rsp_mgnt, short = 'mrsp'),
+ desc ('sackt3', 2, format_sackt, short = 's3'),
+ desc ('sackt2', 2, format_sackt, short = 's2'),
+ desc ('sackt1', 2, format_sackt, short = 's1'),
+ desc ('sackt0', 2, format_sackt, short = 's0'),
+ desc ('sacki0', 32, format.hex),
+ desc ('sacki1', 16, format.hex),
+ desc ('rsof_fl_av', 10, format.hex),
+ desc ('tmi_av', 5, format.unsigned),
+ desc ('pbsz', 1, format.unsigned),
+ desc ('num_sym', 2, format.unsigned),
+ desc ('mfs_cmd_mgmt', 3, format_mfs_cmd_mgnt, short = 'mcmd'),
+ desc ('mfs_cmd_data', 3, format_mfs_cmd_data, short = 'cmd'),
+ desc ('fccs_av', 24, format.hex),
+ )
+
+ name = 'RSOF'
+
+ def __init__ (self, bucket):
+ FC.__init__ (self, bucket)
+
+class GENERIC (FC):
+
+ desc = (
+ desc ('dt_av', 3, format_dt_av, short = 'dt'),
+ desc ('access', 1, format.unsigned, short = 'a'),
+ desc ('snid', 4, format.unsigned),
+ desc ('pad0', 24, format.hex),
+ desc ('pad1', 32, format.hex),
+ desc ('pad2', 32, format.hex),
+ desc ('pad3', 8, format.hex),
+ desc ('fccs_av', 24, format.hex),
+ )
+
+ name = 'GENERIC'
+
+ def __init__ (self, bucket):
+ FC.__init__ (self, bucket)
+
+def fc (bucket):
+ """Instantiate the delimiter corresponding to the delimiter type.
+
+ >>> f = fc (Bucket (words (32, (0x56341221, 0xbbab78a5, 0x21472d05,
+ ... 0xdeadde54))))
+ >>> f
+ <SOF>
+ >>> f.dt_av
+ 1
+ >>> f['dt_av']
+ 1
+ >>> f[0]
+ 1
+
+ """
+ dt_mux = (BEACON, SOF, SACK, RTSCTS,
+ SOUND, RSOF, GENERIC, GENERIC)
+ dt = bucket[0:3]
+ return dt_mux[dt] (bucket)
+
+def _test ():
+ import doctest
+ from bucket import Bucket, words
+ g = dict (Bucket = Bucket, words = words)
+ doctest.testmod (extraglobs = g)
+
+if __name__ == '__main__':
+ _test ()
diff --git a/cesar/tools/dissect/dissect/format.py b/cesar/tools/dissect/dissect/format.py
new file mode 100644
index 0000000000..f4e1c469b5
--- /dev/null
+++ b/cesar/tools/dissect/dissect/format.py
@@ -0,0 +1,104 @@
+"""Define print formats."""
+
+class signed:
+ """Signed integer format.
+
+ >>> f = signed (8)
+ >>> f.width
+ 4
+ >>> f (0)
+ '0'
+ >>> f (255)
+ '-1'
+ >>> f (127)
+ '127'
+ >>> f (-127)
+ '-127'
+ >>> f (128)
+ '-128'
+
+ """
+
+ def __init__ (self, bits):
+ self.bits = bits
+ biggest = -(1 << (bits - 1))
+ self.width = len ('%d' % biggest)
+
+ def __call__ (self, v):
+ big = 1 << self.bits
+ if v >= big / 2:
+ v = - (big - v)
+ return '%d' % v
+
+class unsigned:
+ """Unsigned integer format.
+
+ >>> f = unsigned (8)
+ >>> f.width
+ 3
+ >>> f (0)
+ '0'
+ >>> f (255)
+ '255'
+ >>> f (128)
+ '128'
+
+ """
+
+ def __init__ (self, bits):
+ self.bits = bits
+ biggest = (1 << bits) - 1
+ self.width = len ('%u' % biggest)
+
+ def __call__ (self, v):
+ return '%u' % v
+
+class hex:
+ """Hexadecimal format.
+
+ >>> f = hex (8)
+ >>> f.width
+ 4
+ >>> f (0)
+ '0x00'
+ >>> f (255)
+ '0xff'
+ >>> f (66)
+ '0x42'
+
+ """
+
+ def __init__ (self, bits):
+ self.bits = bits
+ self.width = 2 + (bits + 3) / 4
+
+ def __call__ (self, v):
+ return '%#0*x' % (self.width, v)
+
+class enum:
+ """Enumeration format.
+
+ >>> f = enum (2, 'zero', 'one', 'two', 'three')
+ >>> f.width
+ 5
+ >>> f (0)
+ 'zero'
+ >>> f (3)
+ 'three'
+
+ """
+
+ def __init__ (self, bits, *values):
+ self.bits = bits
+ self.values = values
+ self.width = max (len (v) for v in values)
+
+ def __call__ (self, v):
+ return self.values[v]
+
+def _test ():
+ import doctest
+ doctest.testmod ()
+
+if __name__ == '__main__':
+ _test ()
diff --git a/cesar/tools/dissect/dissect/formater.py b/cesar/tools/dissect/dissect/formater.py
new file mode 100644
index 0000000000..bfa8166a9b
--- /dev/null
+++ b/cesar/tools/dissect/dissect/formater.py
@@ -0,0 +1,124 @@
+
+class FormaterList:
+ """Format using a "field name: field value" list."""
+
+ def format (self, o):
+ """Format:
+
+ >>> f = fc (Bucket (words (32, (0xe4451232, 0x01234567, 0x89abcdef,
+ ... 0xdeadde01))))
+ >>> print FormaterList ().format (f)
+ SACK:
+ dt_av: 2
+ access: 0
+ snid: 3
+ dtei: 0x12
+ cfs: 1
+ bdf: 0
+ svn: 1
+ rrtf: 0
+ mfs_rsp_data: ACK
+ mfs_rsp_mgmt: mNACK
+ sackt3: m
+ sackt2: mc
+ sackt1: nr
+ sackt0: u
+ sacki0: 0x01234567
+ sacki1: 0x89abcdef
+ sacki2: 0x01
+ fccs_av: 0xdeadde
+
+ """
+ s = [ o.name + ':' ]
+ for i in range (len (o)):
+ s.append (' %s: %s' % (o.desc[i].name, o.desc[i].format (o[i])))
+ return '\n'.join (s)
+
+class FormaterColumn:
+ """Format using space separated columns."""
+
+ def format (self, o):
+ """Format:
+
+ >>> f = fc (Bucket (words (32, (0xe4451232, 0x01234567, 0x89abcdef,
+ ... 0xdeadde01))))
+ >>> print FormaterColumn ().format (f)
+ SACK 2 0 3 0x12 1 0 1 0 ACK mNACK m mc nr u 0x01234567 0x89abcdef 0x01 0xdeadde
+
+ """
+ s = [ o.name ]
+ for i in range (len (o)):
+ s.append (o.desc[i].format (o[i]))
+ return ' '.join (s)
+
+class FormaterHeaded:
+ """Format using space separated columns, with column headers."""
+
+ def __init__ (self):
+ self.columns = None
+
+ def format (self, o):
+ """Format:
+
+ >>> fh = FormaterHeaded ()
+ >>> f = [ ]
+ >>> f.append (fc (Bucket (words (32, (0xe4451232, 0x01234567,
+ ... 0x89abcdef, 0xdeadde01)))))
+ >>> f.append (fc (Bucket (words (32, (0x1bbaedca, 0xfedcba98,
+ ... 0x76543210, 0xdeaddefe)))))
+ >>> f.append (fc (Bucket (words (32, (0x78563454, 0x9a0d05b5,
+ ... 0x000000bc, 0xdeadde00)))))
+ >>> for i in f: print fh.format (i)
+ dt a snid dtei cfs bdf svn rrtf rsp mrsp st3 st2 st1 st0 sacki0 sacki1 sacki2 fccs_av
+ SACK 2 0 3 0x12 1 0 1 0 ACK mNACK m mc nr u 0x01234567 0x89abcdef 0x01 0xdeadde
+ SACK 2 1 12 0xed 0 1 0 1 HOLD mFAIL u nr mc m 0xfedcba98 0x76543210 0xfe 0xdeadde
+ dt a snid stei dtei lid cfs pbsz bdf saf scf req_tm fl_av mpdu_cnt pad0 ppb src pad1 pad2 fccs_av
+ SOUND 4 0 5 0x34 0x56 0x78 1 0 1 0 1 5 0xd05 0 0x0 0x9a 188 0x000000 0x00 0xdeadde
+
+ """
+ h = self._headers (o)
+ s = ' '.join ('%-*s' % (w, desc.format (v))
+ for w, v, desc in zip (self.columns[1:], o, o.desc))
+ return '%s%-*s %s' % (h, self.columns[0], o.name, s)
+
+ def _headers (self, o):
+ if self.columns is None or self.name != o.name:
+ self.name = o.name
+ # Compute columns widths.
+ self.columns = [ len (o.name) ]
+ headers = [ '' ]
+ for d in o.desc:
+ headers.append (d.short)
+ self.columns.append (max (len (d.short), d.format.width))
+ # Output headers.
+ return ' '.join ('%-*s' % (width, name)
+ for name, width in zip (headers, self.columns)
+ ).rstrip () + '\n'
+ else:
+ return ''
+
+formaters = dict (
+ list = FormaterList,
+ column = FormaterColumn,
+ headed = FormaterHeaded,
+ )
+
+def formater (name):
+ """Select a formater by name.
+
+ >>> f = formater ('list')
+ >>> f.__name__
+ 'FormaterList'
+
+ """
+ return formaters[name]
+
+def _test ():
+ import doctest
+ from bucket import Bucket, words
+ from fc import fc
+ g = dict (Bucket = Bucket, words = words, fc = fc)
+ doctest.testmod (extraglobs = g)
+
+if __name__ == '__main__':
+ _test ()