#!/usr/bin/python # # Copyright 2013 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 logging import os import sys import yaml __version__ = '0.2' class Entry(object): def __init__(self, parsed_yaml): assert type(parsed_yaml) is dict, repr(parsed_yaml) self._dict = parsed_yaml def as_yaml(self): return yaml.dump(self._dict, default_flow_style=False) def get_single(self, key, default): names = self.get_subdict(key) if not names: return default keys = sorted(names.keys()) return names[keys[0]] def get_subdict(self, key): if key in self._dict: v = self._dict[key] if type(v) is dict: return v return {'': v} return {} class AddressBook(object): def __init__(self): self.entries = [] def find_yaml_files(self, database_root): for dirname, subdirs, basenames in os.walk(database_root): subdirs.sort() for basename in sorted(basenames): if basename.endswith('.yaml'): yield os.path.join(dirname, basename) def add_from_database(self, database_root): logging.info('Adding from database %s' % database_root) for yaml_filename in self.find_yaml_files(database_root): self.add_from_file(yaml_filename) def add_from_file(self, filename): logging.info('Adding from file %s' % filename) with open(filename) as f: parsed_yaml = yaml.safe_load(f) if type(parsed_yaml) is list: for parsed_entry in parsed_yaml: entry = Entry(parsed_entry) self.entries.append(entry) else: entry = Entry(parsed_yaml) self.entries.append(entry) def find(self, patterns): return [e for e in self.entries if self.matches(e, patterns)] def matches(self, entry, patterns): s = entry.as_yaml().lower() return any(p.lower() in s for p in patterns) class CommandLineAddressBook(cliapp.Application): cmd_synopsis = { 'list': '', 'find': '[PATTERN...]', 'mutt-query': '[PATTERN...]', } def add_settings(self): self.settings.string_list( ['database', 'db', 'd'], 'add DIR to list of databases to use', metavar='DIR', default=[os.path.expanduser('~/.local/share/clab')]) def setup(self): # Configure yaml.dump (but not yaml.safe_dump) to print multiline # strings prettier. def text_representer(dumper, data): if '\n' in data: return dumper.represent_scalar( u'tag:yaml.org,2002:str', data, style='|') return dumper.represent_scalar( u'tag:yaml.org,2002:str', data, style='') yaml.add_representer(str, text_representer) yaml.add_representer(unicode, text_representer) def load_address_book(self): book = AddressBook() for database in self.settings['database']: book.add_from_database(database) return book def cmd_list(self, args): '''List all entries in the database. This lists all the entries. See the find subcommand for searching specific entries. ''' book = self.load_address_book() for entry in book.entries: self.output.write(entry.as_yaml() + '\n') def cmd_find(self, args): '''List entries that match patterns. Each pattern is a fixed string (not a regular expression). Matching is for any text in the entry. ''' book = self.load_address_book() for entry in book.find(args): self.output.write(entry.as_yaml() + '\n') def cmd_mutt_query(self, args): '''Find entries for use with mutt. This is like the find subcommand, but output is formatted so it's suitable for the mutt query hook. ''' if len(args) != 1: raise cliapp.AppException( 'mutt-query requires exactly one argument') book = self.load_address_book() entries = book.find(args) if not entries: self.output.write('No matches\n') sys.exit(1) self.output.write('clab found matches:\n') name_addr_pairs = set() for entry in entries: name = entry.get_single('name', '') emails = entry.get_subdict('email') for email in emails: name_addr_pairs.add((name, emails[email])) for name, addr in sorted(name_addr_pairs): n = name.encode('utf-8') a = addr.encode('utf-8') self.output.write('%s\t%s\n' % (a, n)) CommandLineAddressBook().run()