"""Command line client functionality""" import nilmdb from nilmdb.utils.printf import * from nilmdb.utils import datetime_tz import sys import re import argparse from argparse import ArgumentDefaultsHelpFormatter as def_form # Valid subcommands. Defined in separate files just to break # things up -- they're still called with Cmdline as self. subcommands = [ "info", "create", "list", "metadata", "insert", "extract", "remove", "destroy" ] # Import the subcommand modules subcmd_mods = {} for cmd in subcommands: subcmd_mods[cmd] = __import__("nilmdb.cmdline." + cmd, fromlist = [ cmd ]) class JimArgumentParser(argparse.ArgumentParser): def error(self, message): self.print_usage(sys.stderr) self.exit(2, sprintf("error: %s\n", message)) class Cmdline(object): def __init__(self, argv = None): self.argv = argv or sys.argv[1:] self.client = None def arg_time(self, toparse): """Parse a time string argument""" try: return self.parse_time(toparse).totimestamp() except ValueError as e: raise argparse.ArgumentTypeError(sprintf("%s \"%s\"", str(e), toparse)) def parse_time(self, toparse): """ Parse a free-form time string and return a datetime_tz object. If the string doesn't contain a timestamp, the current local timezone is assumed (e.g. from the TZ env var). """ # If string isn't "now" and doesn't contain at least 4 digits, # consider it invalid. smartparse might otherwise accept # empty strings and strings with just separators. if toparse != "now" and len(re.findall(r"\d", toparse)) < 4: raise ValueError("not enough digits for a timestamp") # Try to just parse the time as given try: return datetime_tz.datetime_tz.smartparse(toparse) except ValueError: pass # Try to extract a substring in a condensed format that we expect # to see in a filename or header comment res = re.search(r"(^|[^\d])(" # non-numeric or SOL r"(199\d|2\d\d\d)" # year r"[-/]?" # separator r"(0[1-9]|1[012])" # month r"[-/]?" # separator r"([012]\d|3[01])" # day r"[-T ]?" # separator r"([01]\d|2[0-3])" # hour r"[:]?" # separator r"([0-5]\d)" # minute r"[:]?" # separator r"([0-5]\d)?" # second r"([-+]\d\d\d\d)?" # timezone r")", toparse) if res is not None: try: return datetime_tz.datetime_tz.smartparse(res.group(2)) except ValueError: pass # Could also try to successively parse substrings, but let's # just give up for now. raise ValueError("unable to parse timestamp") def time_string(self, timestamp): """ Convert a Unix timestamp to a string for printing, using the local timezone for display (e.g. from the TZ env var). """ dt = datetime_tz.datetime_tz.fromtimestamp(timestamp) return dt.strftime("%a, %d %b %Y %H:%M:%S.%f %z") def parser_setup(self): self.parser = JimArgumentParser(add_help = False, formatter_class = def_form) group = self.parser.add_argument_group("General options") group.add_argument("-h", "--help", action='help', help='show this help message and exit') group.add_argument("-V", "--version", action="version", version = nilmdb.__version__) group = self.parser.add_argument_group("Server") group.add_argument("-u", "--url", action="store", default="http://localhost:12380/", help="NilmDB server URL (default: %(default)s)") sub = self.parser.add_subparsers(title="Commands", dest="command", description="Specify --help after " "the command for command-specific " "options.") # Set up subcommands (defined in separate files) for cmd in subcommands: subcmd_mods[cmd].setup(self, sub) def die(self, formatstr, *args): fprintf(sys.stderr, formatstr + "\n", *args) if self.client: self.client.close() sys.exit(-1) def run(self): # Clear cached timezone, so that we can pick up timezone changes # while running this from the test suite. datetime_tz._localtz = None # Run parser self.parser_setup() self.args = self.parser.parse_args(self.argv) # Run arg verify handler if there is one if "verify" in self.args: self.args.verify(self) self.client = nilmdb.Client(self.args.url) # Make a test connection to make sure things work try: server_version = self.client.version() except nilmdb.client.Error as e: self.die("error connecting to server: %s", str(e)) # Now dispatch client request to appropriate function. Parser # should have ensured that we don't have any unknown commands # here. retval = self.args.handler(self) or 0 self.client.close() sys.exit(retval)