#!/usr/bin/python # Copyright (C) 2010, 2011 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import cliapp import csv import json import os import summainlib class OutputFormat(object): keys = ['Mtime', 'Mode', 'Ino', 'Dev', 'Nlink', 'Size', 'Uid', 'Username', 'Gid', 'Group', 'Target', 'Xattrs'] 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 NotImplementedError() 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): list.__init__(self) 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 write_object(self, name, o): pass 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): self.settings.boolean( ['relative-paths', 'r'], 'print paths relative to arguments') self.settings.boolean( ['mangle-paths', 'm'], 'mangle (obfuscate) paths') self.settings.string( ['secret'], 'use SECRET to make mangled paths unguessable') self.settings.string_list( ['exclude'], 'do not output or compute FIELD', metavar='FIELD') self.settings.string_list( ['checksum', 'c'], 'which checksums to compute: ' '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) and not os.path.islink(root): for dirname, dirnames, filenames in os.walk(root): yield dirname dirnames.sort() for filename in sorted(filenames): yield os.path.join(dirname, filename) elif os.path.islink(root): yield root elif not os.path.exists(root): raise cliapp.AppException('Does not exist: %s' % root) else: 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() if self.settings['mangle-paths']: pn = summainlib.PathNormalizer(self.settings['secret']) else: pn = summainlib.SamePath() for root in roots: for filename in self.files(root): o = summainlib.FilesystemObject(filename, nn, pn, exclude) if relative: o.relative = self.relative_path(root, o) yield o['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 if o.filename.startswith(root2): return o.filename[len(root2):] elif o.filename == root and o.isdir(): return '.' else: return o.filename def new_formatter(self, checksums, objects): table = { 'rfc822': Rfc822, 'csv': CSV, 'json': Json, } formatter = table[self.settings['output-format']] return formatter(self.output, checksums, objects) Summain(version=summainlib.__version__).run()