diff options
author | Lars Wirzenius <liw@liw.fi> | 2011-08-08 15:41:56 +0100 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2011-08-08 15:41:56 +0100 |
commit | a22c2dccaf10e610f9e34c724446fffe2465588c (patch) | |
tree | 9d991ff8a7d8321d8be16fbb2998e2b063d2ba70 | |
parent | d0aa60910d20bf6129f9c7cba5ea54c0f4806218 (diff) | |
parent | 9aaf91edeb34c585c765c8663965b61b0db6ad65 (diff) | |
download | summain-a22c2dccaf10e610f9e34c724446fffe2465588c.tar.gz |
Add JSON and CSV output formats.
-rw-r--r-- | NEWS | 6 | ||||
-rwxr-xr-x | summain | 137 | ||||
-rw-r--r-- | summainlib.py | 39 | ||||
-rw-r--r-- | summainlib_tests.py | 34 |
4 files changed, 134 insertions, 82 deletions
@@ -2,6 +2,12 @@ NEWS file for summain ===================== +Version 0.10, released UNRELEASED +--------------------------------- + +* Added support for JSON and CSV output formats, in addition to the default + RFC822-like one. (See `--output-format` option.) + Version 0.9, released 2011-07-24 -------------------------------- @@ -16,12 +16,90 @@ import cliapp +import csv +import json import os import sys import summainlib +class OutputFormat(object): + + keys = ['Mtime', 'Mode', 'Ino', 'Dev', 'Nlink', 'Size', + 'Uid', 'Username', 'Gid', 'Group', 'Target'] + + def __init__(self, output, checksums, objects): + self.output = output + self.checksums = checksums + self.objects = objects + + def write(self): + for name, o in self.objects: + self.write_object(name, o) + + def write_object(self, name, o): + raise NotImplemented() + + +class Rfc822(OutputFormat): + + def write_object(self, name, o): + keys = self.keys + self.checksums + values = [('Name', name)] + values += [(k, o[k]) for k in keys if o[k] != ''] + record = ''.join('%s: %s\n' % (k, v) for k, v in values if v != '') + self.output.write('%s\n' % record) + + +class CSV(OutputFormat): + + def __init__(self, output, checksums, objects): + OutputFormat.__init__(self, output, checksums, objects) + self.writer = csv.writer(output) + self.wrote_headings = False + + def write_object(self, name, o): + keys = self.keys + self.checksums + + if not self.wrote_headings: + self.writer.writerow(['Name'] + keys) + self.wrote_headings = True + + values = [name] + [o[k] or '' for k in keys] + self.writer.writerow(values) + + +class GeneratorList(list): + + def __init__(self, gen): + self.gen = gen + + def __len__(self): + return 1 + + def __iter__(self): + return iter(self.gen) + + +class Json(OutputFormat): + + def write(self): + gen = GeneratorList(self.dictify(name, o) for name, o in self.objects) + json.dump(gen, self.output, sort_keys=True, indent=1) + self.output.write('\n') + + def dictify(self, name, o): + keys = self.keys + self.checksums + + values = { 'Name': name } + for k in keys: + if o[k] != '': + values[k] = o[k] + + return values + + class Summain(cliapp.Application): def add_settings(self): @@ -39,6 +117,9 @@ class Summain(cliapp.Application): 'MD5, SHA1, SHA224, SHA256, SHA384, SHA512; ' 'use once per checksum type ' '(default is SHA1)') + self.settings.choice(['output-format', 'f'], + ['rfc822', 'csv', 'json'], + 'choose output format (rfc822, csv, json)') def files(self, root): if os.path.isdir(root): @@ -51,6 +132,12 @@ class Summain(cliapp.Application): yield root def process_args(self, args): + checksums = [x.upper() + for x in self.settings['checksum'] or ['SHA1']] + fmt = self.new_formatter(checksums, self.find_roots(args)) + fmt.write() + + def find_roots(self, roots): relative = self.settings['relative-paths'] exclude = self.settings['exclude'] nn = summainlib.NumberNormalizer() @@ -58,20 +145,44 @@ class Summain(cliapp.Application): pn = summainlib.PathNormalizer(self.settings['secret']) else: pn = summainlib.SamePath() - checksums = [x.upper() - for x in self.settings['checksum'] or ['SHA1']] - o = summainlib.FilesystemObject('.', nn, pn, exclude, checksums) - for checksum in checksums: - try: - o[checksum] - except KeyError: - raise cliapp.AppException('Unknown checksum %s' % checksum) - for root in args: + + for root in roots: for filename in self.files(root): - o = summainlib.FilesystemObject(filename, nn, pn, exclude, - checksums) - self.output.write(o.format(root if relative else None)) - self.output.write('\n') + o = summainlib.FilesystemObject(filename, nn, pn, exclude) + if relative: + name = self.relative_path(root, o) + else: + name = o['Name'] + yield name, o + + def relative_path(self, root, o): + '''Return a path that is relative to root, if possible. + + If pathname does not start with root, then return it + unmodified. + + ''' + + if root.endswith(os.sep): + root2 = root + else: + root2 = root + os.sep + pathname = o['Name'] + if pathname.startswith(root2): + return pathname[len(root2):] + elif pathname == root and o.isdir(): + return '.' + else: + return pathname + + def new_formatter(self, checksums, objects): + table = { + 'rfc822': Rfc822, + 'csv': CSV, + 'json': Json, + } + return table[self.settings['output-format']](self.output, checksums, + objects) Summain(version=summainlib.__version__).run() diff --git a/summainlib.py b/summainlib.py index 5376e00..34dad47 100644 --- a/summainlib.py +++ b/summainlib.py @@ -112,13 +112,12 @@ class FilesystemObject(object): ''' - def __init__(self, filename, nn, pn, exclude, checksums, + def __init__(self, filename, nn, pn, exclude, stat_result=None, sha1=None, sha224=None, sha256=None, sha384=None, sha512=None, md5=None, open_file=None, readlink=None): self._filename = filename self._exclude = set(self._normalize_key(k) for k in exclude) - self._checksums = checksums self._pn = pn self._nn = nn self._md5 = md5 or hashlib.md5() @@ -232,41 +231,7 @@ class FilesystemObject(object): raise KeyError(key) return self.values.get(key, '') - def _isdir(self): + def isdir(self): # pragma: no cover '''Is this a directory?''' - return stat.S_ISDIR(int(self['Mode'], 8)) - def relative_path(self, root): - '''Return a path that is relative to root, if possible. - - If pathname does not start with root, then return it - unmodified. - - ''' - - if root.endswith(os.sep): - root2 = root - else: - root2 = root + os.sep - pathname = self['Name'] - if pathname.startswith(root2): - return pathname[len(root2):] - elif pathname == root and self._isdir(): - return '.' - else: - return pathname - - def format(self, root=None): # pragma: no cover - if root is None: - name = self['Name'] - else: - name = self.relative_path(root) - - keys = (['Mtime', 'Mode', 'Ino', 'Dev', 'Nlink', 'Size', - 'Uid', 'Username', 'Gid', 'Group', 'Target'] + - self._checksums) - values = [('Name', name)] - values += [(k, self[k]) for k in keys if self[k] != ''] - return ''.join('%s: %s\n' % (k, v) for k, v in values if v != '') - diff --git a/summainlib_tests.py b/summainlib_tests.py index 6015541..4ec3da5 100644 --- a/summainlib_tests.py +++ b/summainlib_tests.py @@ -85,13 +85,12 @@ class FilesystemObjectTests(unittest.TestCase): self.nn = summainlib.NumberNormalizer() self.pn = summainlib.SamePath() self.exclude = [] - self.checksums = ['SHA1'] def new(self, name, mode=None): if mode is not None: self.st.st_mode = mode return summainlib.FilesystemObject(name, self.nn, self.pn, - self.exclude, self.checksums, + self.exclude, stat_result=self.st, sha1=FakeChecksummer(), sha224=FakeChecksummer(), @@ -168,35 +167,6 @@ class FilesystemObjectTests(unittest.TestCase): def test_formats_target_correctly_for_regular_file(self): self.assertEqual(self.new('foo')['Target'], '') - - def test_does_not_output_size_for_directory(self): - fso = self.new('foo', mode=stat.S_IFDIR | 0777) - output = fso.format() - self.assert_('Size:' not in output) - - def test_relative_path_returns_path_if_not_starting_with_root(self): - fso = self.new('/foo/bar') - self.assertEqual(fso.relative_path('/yo'), '/foo/bar') - - def test_relative_path_returns_partial_path_if_starting_with_root(self): - fso = self.new('/foo/bar') - self.assertEqual(fso.relative_path('/foo'), 'bar') - - def test_relative_path_returns_dot_if_same_as_root_and_dir(self): - fso = self.new('/foo/bar', mode=stat.S_IFDIR) - self.assertEqual(fso.relative_path('/foo/bar'), '.') - - def test_relative_path_returns_path_if_same_as_root_and_not_dir(self): - fso = self.new('/foo/bar', mode=stat.S_IFREG) - self.assertEqual(fso.relative_path('/foo/bar'), '/foo/bar') - - def test_relative_path_returns_path_if_root_is_slashless_prefix(self): - fso = self.new('/foobar', mode=stat.S_IFREG) - self.assertEqual(fso.relative_path('/foo'), '/foobar') - - def test_relative_path_returns_partial_path_if_root_ends_in_slash(self): - fso = self.new('/foo/bar', mode=stat.S_IFREG) - self.assertEqual(fso.relative_path('/foo/'), 'bar') def test_excludes_unwanted_fields_from_output(self): self.exclude = ['mtime'] @@ -224,7 +194,7 @@ class FilesystemObjectNormalizedNumbersTests(unittest.TestCase): st_uid=0, st_gid=0) self.ino += 1 return summainlib.FilesystemObject(name, self.nn, self.pn, - self.exclude, self.checksums, + self.exclude, stat_result=st, sha1=FakeChecksummer(), sha224=FakeChecksummer(), |