Mercurial > public > weighmail
changeset 3:e0a6a02b3213
Can now get labels from command-line. Config file is optional.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Fri, 18 May 2012 20:32:21 -0500 |
parents | 3c5c1269e037 |
children | ce84c480ffec |
files | weighmail/config.py weighmail/main.py weighmail/utils.py |
diffstat | 3 files changed, 113 insertions(+), 69 deletions(-) [+] |
line wrap: on
line diff
--- a/weighmail/config.py Thu May 17 20:34:38 2012 -0500 +++ b/weighmail/config.py Fri May 18 20:32:21 2012 -0500 @@ -1,10 +1,6 @@ -import collections -import re from ConfigParser import SafeConfigParser - -class ConfigError(Exception): - pass +from utils import make_label DEFAULTS = dict( @@ -13,23 +9,9 @@ host='imap.gmail.com', ssl='True', port='993', + labels=[], ) -LIMIT_RE = re.compile(r'^(\d+)(GB|MB|KB)?$', re.IGNORECASE) - -KB = 1024 -MB = KB * KB -GB = MB * MB - -SUFFIX_SIZES = { - None: 1, - 'KB': KB, - 'MB': MB, - 'GB': GB -} - -Label = collections.namedtuple('Label', 'name min max') - def parse_config_file(path): """Parse INI file containing configuration details. @@ -38,28 +20,16 @@ # Parse options file parser = SafeConfigParser(defaults=DEFAULTS) - try: - with open(path, 'r') as fp: - parser.readfp(fp) - except IOError, ex: - raise ConfigError(ex) + with open(path, 'r') as fp: + parser.readfp(fp) # Build a list of label named tuples - label_set = set(parser.sections()) - {'auth', 'connection'} - if not label_set: - raise ConfigError("please specify at least 1 label section") + sections = [s for s in parser.sections() if s not in ('auth', 'connection')] - labels = [] - for label in label_set: - min_val = get_limit(parser.get(label, 'min')) - max_val = get_limit(parser.get(label, 'max')) - - if (min_val is not None and max_val is not None and - min_val > max_val): - raise ConfigError("min is > max for label %s" % label) - - labels.append(Label(name=label, min=min_val, max=max_val)) + labels = [make_label(sec, + parser.get(sec, 'min'), + parser.get(sec, 'max')) for sec in sections] # Build an options object and return it @@ -72,18 +42,3 @@ labels=labels, ) return opts - - -def get_limit(val): - """Turns a string limit (e.g. 3MB) into an integer""" - - # An empty string is OK, it means no limit, which we translate to None - if val == '': - return None - - match = LIMIT_RE.match(val) - if match is None: - raise ConfigError("invalid min/max value %s" % val) - - return int(match.group(1)) * SUFFIX_SIZES[match.group(2).upper()] -
--- a/weighmail/main.py Thu May 17 20:34:38 2012 -0500 +++ b/weighmail/main.py Fri May 18 20:32:21 2012 -0500 @@ -1,35 +1,53 @@ -from argparse import ArgumentParser +import argparse import getpass -import os.path import sys -from config import parse_config_file, ConfigError +import config +from utils import make_label, AppError PROG_DESC = "Adds labels to your Gmail according to message size" +class LabelAction(argparse.Action): + + def __call__(self, parser, namespace, values, option_string=None): + + labels = getattr(namespace, self.dest) + for value in values: + try: + label = self.parse_label(value) + except ValueError, ex: + parser.error(ex) + else: + labels.append(label) + + def parse_label(self, value): + name, size_range = value.split(':') + min_size, max_size = size_range.split('-') + return make_label(name, min_size, max_size) + + def parse_args(): - parser = ArgumentParser(description=PROG_DESC, + parser = argparse.ArgumentParser(description=PROG_DESC, epilog="Command-line arguments override config file settings.") - default_config_file = os.path.expanduser(os.path.join('~', - '.weighmail.ini')) - parser.add_argument('-c', '--config', default=default_config_file, - help="path to configuration file [default=%(default)s]") + parser.add_argument('-c', '--config', help="path to configuration file") parser.add_argument('-u', '--user', help="user name") parser.add_argument('-p', '--password', help="password") parser.add_argument('-H', '--host', help="server name") parser.add_argument('-P', '--port', type=int, help="server port") parser.add_argument('-n', '--nossl', default=None, action='store_true', help="do not use SSL") + parser.add_argument('-l', '--labels', default=[], nargs='+', + action=LabelAction, help="label specification: name:min-max") args = parser.parse_args() - # Remove items with a value of None, which indicates the user didn't specify + # Remove items that eval to False which indicates the user didn't specify # the option; this makes updating options from the config file easier: - args = { k : v for k, v in vars(args).items() if v is not None } + args = { k : v for k, v in vars(args).items() if v } return args @@ -37,11 +55,14 @@ # Parse command-line arguments args = parse_args() - config_file = args.pop('config') + config_file = args.pop('config', None) no_ssl = args.pop('nossl', False) - # Read config file: - opts = parse_config_file(config_file) + # Read config file if the option was specified + if config_file is not None: + opts = config.parse_config_file(config_file) + else: + opts = config.DEFAULTS # Command-line arguments override config file settings opts.update(args) @@ -51,14 +72,17 @@ # If the user or password is not specified, prompt for them now for opt in ('user', 'password'): - if opts[opt] is None: + if opt not in opts or opts[opt] is None: opts[opt] = getpass.getpass(opt + ': ') print opts + if 'labels' not in opts or not opts['labels']: + raise AppError("Please specify some label definitions") + if __name__ == '__main__': try: main() - except ConfigError, ex: - sys.stderr.write("Configuration error: %s\n" % ex) + except (IOError, AppError), ex: + sys.stderr.write("%s\n" % ex)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/weighmail/utils.py Fri May 18 20:32:21 2012 -0500 @@ -0,0 +1,65 @@ +"""Common utilities for weighmail. + +""" +import collections +import re + + +class AppError(Exception): + pass + + +LIMIT_RE = re.compile(r'^(\d+)(GB|MB|KB)?$', re.IGNORECASE) + +KB = 1024 +MB = KB * KB +GB = MB * MB + +SUFFIX_SIZES = { + None: 1, + 'KB': KB, + 'MB': MB, + 'GB': GB +} + +Label = collections.namedtuple('Label', 'name min max') + + +def make_label(name, min_str, max_str): + """Make a Label object from 3 string parameters: + + name - label name + min_str - minimum value, e.g. '5MB' + max_str - maximum value, e.g. '10GB' + + Either min_str or max_str can be empty or None, in which + case None will be used in the Label object. + + It is not valid to have None for both min and max. + + """ + min_val = get_limit(min_str) + max_val = get_limit(max_str) + + if (min_val is not None and max_val is not None and + min_val > max_val) or (min_val is None and max_val is None): + raise ValueError("invalid label range: %s" % name) + + return Label(name, min_val, max_val) + + +def get_limit(val): + """Turns a string limit (e.g. 3MB) into an integer + + An empty string or None will be translated to None. + + """ + if val is None or val == '': + return None + + match = LIMIT_RE.match(val) + if match is None: + raise ValueError("invalid min/max value %s" % val) + + suffix = match.group(2).upper() if match.group(2) else None + return int(match.group(1)) * SUFFIX_SIZES[suffix]