diff --git a/extras/nilmtool-bash-completion.sh b/extras/nilmtool-bash-completion.sh new file mode 100644 index 0000000..f4b63a6 --- /dev/null +++ b/extras/nilmtool-bash-completion.sh @@ -0,0 +1,20 @@ +# To enable bash completion: +# +# 1. Ensure python-argcomplete is installed: +# pip install argcomplete +# 2. Source this file: +# . nilmtool-bash-completion.sh + +_nilmtool_argcomplete() { + local IFS=$(printf "\013") + COMPREPLY=( $(IFS="$IFS" \ + COMP_LINE="$COMP_LINE" \ + COMP_WORDBREAKS="$COMP_WORDBREAKS" \ + COMP_POINT="$COMP_POINT" \ + _ARGCOMPLETE=1 \ + "$1" 8>&1 9>&2 1>/dev/null 2>/dev/null) ) + if [[ $? != 0 ]]; then + unset COMPREPLY + fi +} +complete -o nospace -o default -F _nilmtool_argcomplete nilmtool diff --git a/nilmdb/cmdline/cmdline.py b/nilmdb/cmdline/cmdline.py index 77f7805..64c708d 100644 --- a/nilmdb/cmdline/cmdline.py +++ b/nilmdb/cmdline/cmdline.py @@ -11,6 +11,11 @@ import os import argparse from argparse import ArgumentDefaultsHelpFormatter as def_form +try: # pragma: no cover + import argcomplete +except ImportError: # pragma: no cover + argcomplete = None + # Valid subcommands. Defined in separate files just to break # things up -- they're still called with Cmdline as self. subcommands = [ "help", "info", "create", "list", "metadata", @@ -27,6 +32,50 @@ class JimArgumentParser(argparse.ArgumentParser): self.print_usage(sys.stderr) self.exit(2, sprintf("error: %s\n", message)) +class Complete(object): # pragma: no cover + # Completion helpers, for using argcomplete (see + # extras/nilmtool-bash-completion.sh) + def escape(self, s): + quote_chars = [ "\\", "\"", "'", " " ] + for char in quote_chars: + s = s.replace(char, "\\" + char) + return s + + def none(self, prefix, parsed_args, **kwargs): + return [] + rate = none + time = none + url = none + + def path(self, prefix, parsed_args, **kwargs): + client = nilmdb.client.Client(parsed_args.url) + return ( self.escape(s[0]) + for s in client.stream_list() + if s[0].startswith(prefix) ) + + def layout(self, prefix, parsed_args, **kwargs): + types = [ "int8", "int16", "int32", "int64", + "uint8", "uint16", "uint32", "uint64", + "float32", "float64" ] + layouts = [] + for i in range(1,10): + layouts.extend([(t + "_" + str(i)) for t in types]) + return ( l for l in layouts if l.startswith(prefix) ) + + def meta_key(self, prefix, parsed_args, **kwargs): + return (kv.split('=')[0] for kv + in self.meta_keyval(prefix, parsed_args, **kwargs)) + + def meta_keyval(self, prefix, parsed_args, **kwargs): + client = nilmdb.client.Client(parsed_args.url) + path = parsed_args.path + if not path: + return [] + return ( self.escape(k + '=' + v) + for (k,v) in client.stream_get_metadata(path).iteritems() + if k.startswith(prefix) ) + + class Cmdline(object): def __init__(self, argv = None): @@ -34,6 +83,7 @@ class Cmdline(object): self.client = None self.def_url = os.environ.get("NILMDB_URL", "http://localhost:12380") self.subcmd = {} + self.complete = Complete() def arg_time(self, toparse): """Parse a time string argument""" @@ -43,6 +93,7 @@ class Cmdline(object): raise argparse.ArgumentTypeError(sprintf("%s \"%s\"", str(e), toparse)) + # Set up the parser def parser_setup(self): self.parser = JimArgumentParser(add_help = False, formatter_class = def_form) @@ -56,7 +107,8 @@ class Cmdline(object): group = self.parser.add_argument_group("Server") group.add_argument("-u", "--url", action="store", default=self.def_url, - help="NilmDB server URL (default: %(default)s)") + help="NilmDB server URL (default: %(default)s)" + ).completer = self.complete.url sub = self.parser.add_subparsers( title="Commands", dest="command", @@ -80,6 +132,8 @@ class Cmdline(object): # Run parser self.parser_setup() + if argcomplete: # pragma: no cover + argcomplete.autocomplete(self.parser) self.args = self.parser.parse_args(self.argv) # Run arg verify handler if there is one diff --git a/nilmdb/cmdline/create.py b/nilmdb/cmdline/create.py index 9970462..e230da2 100644 --- a/nilmdb/cmdline/create.py +++ b/nilmdb/cmdline/create.py @@ -22,9 +22,11 @@ Layout types are of the format: type_count cmd.set_defaults(handler = cmd_create) group = cmd.add_argument_group("Required arguments") group.add_argument("path", - help="Path (in database) of new stream, e.g. /foo/bar") + help="Path (in database) of new stream, e.g. /foo/bar", + ).completer = self.complete.path group.add_argument("layout", - help="Layout type for new stream, e.g. float32_8") + help="Layout type for new stream, e.g. float32_8", + ).completer = self.complete.layout return cmd def cmd_create(self): diff --git a/nilmdb/cmdline/destroy.py b/nilmdb/cmdline/destroy.py index 70df00c..949ed38 100644 --- a/nilmdb/cmdline/destroy.py +++ b/nilmdb/cmdline/destroy.py @@ -14,7 +14,8 @@ def setup(self, sub): cmd.set_defaults(handler = cmd_destroy) group = cmd.add_argument_group("Required arguments") group.add_argument("path", - help="Path of the stream to delete, e.g. /foo/bar") + help="Path of the stream to delete, e.g. /foo/bar", + ).completer = self.complete.path return cmd def cmd_destroy(self): diff --git a/nilmdb/cmdline/extract.py b/nilmdb/cmdline/extract.py index e14d912..61015df 100644 --- a/nilmdb/cmdline/extract.py +++ b/nilmdb/cmdline/extract.py @@ -12,13 +12,16 @@ def setup(self, sub): group = cmd.add_argument_group("Data selection") group.add_argument("path", - help="Path of stream, e.g. /foo/bar") + help="Path of stream, e.g. /foo/bar", + ).completer = self.complete.path group.add_argument("-s", "--start", required=True, metavar="TIME", type=self.arg_time, - help="Starting timestamp (free-form, inclusive)") + help="Starting timestamp (free-form, inclusive)", + ).completer = self.complete.time group.add_argument("-e", "--end", required=True, metavar="TIME", type=self.arg_time, - help="Ending timestamp (free-form, noninclusive)") + help="Ending timestamp (free-form, noninclusive)", + ).completer = self.complete.time group = cmd.add_argument_group("Output format") group.add_argument("-b", "--bare", action="store_true", diff --git a/nilmdb/cmdline/insert.py b/nilmdb/cmdline/insert.py index 5798c33..95590fe 100644 --- a/nilmdb/cmdline/insert.py +++ b/nilmdb/cmdline/insert.py @@ -25,7 +25,8 @@ def setup(self, sub): group.add_argument("-t", "--timestamp", action="store_true", help="Add timestamps to each line") group.add_argument("-r", "--rate", type=float, - help="Data rate, in Hz") + help="Data rate, in Hz", + ).completer = self.complete.rate group = cmd.add_argument_group("Start time", description=""" @@ -39,7 +40,8 @@ def setup(self, sub): exc = group.add_mutually_exclusive_group() exc.add_argument("-s", "--start", metavar="TIME", type=self.arg_time, - help="Starting timestamp (free-form)") + help="Starting timestamp (free-form)", + ).completer = self.complete.time exc.add_argument("-f", "--filename", action="store_true", help="Use filename to determine start time") @@ -52,11 +54,13 @@ def setup(self, sub): timezone.""") group.add_argument("-e", "--end", metavar="TIME", type=self.arg_time, - help="Ending timestamp (free-form)") + help="Ending timestamp (free-form)", + ).completer = self.complete.time group = cmd.add_argument_group("Required parameters") group.add_argument("path", - help="Path of stream, e.g. /foo/bar") + help="Path of stream, e.g. /foo/bar", + ).completer = self.complete.path group.add_argument("file", nargs = '?', default='-', help="File to insert (default: - (stdin))") return cmd diff --git a/nilmdb/cmdline/intervals.py b/nilmdb/cmdline/intervals.py index 30c3d0e..e9ef9fb 100644 --- a/nilmdb/cmdline/intervals.py +++ b/nilmdb/cmdline/intervals.py @@ -21,19 +21,23 @@ def setup(self, sub): group = cmd.add_argument_group("Stream selection") group.add_argument("path", metavar="PATH", - help="List intervals for this path") + help="List intervals for this path", + ).completer = self.complete.path group.add_argument("-d", "--diff", metavar="PATH", - help="Subtract intervals from this path") + help="Subtract intervals from this path", + ).completer = self.complete.path group = cmd.add_argument_group("Interval details") group.add_argument("-s", "--start", metavar="TIME", type=self.arg_time, help="Starting timestamp for intervals " - "(free-form, inclusive)") + "(free-form, inclusive)", + ).completer = self.complete.time group.add_argument("-e", "--end", metavar="TIME", type=self.arg_time, help="Ending timestamp for intervals " - "(free-form, noninclusive)") + "(free-form, noninclusive)", + ).completer = self.complete.time group = cmd.add_argument_group("Misc options") group.add_argument("-T", "--timestamp-raw", action="store_true", diff --git a/nilmdb/cmdline/list.py b/nilmdb/cmdline/list.py index 9fd7537..ebffdb8 100644 --- a/nilmdb/cmdline/list.py +++ b/nilmdb/cmdline/list.py @@ -18,11 +18,14 @@ def setup(self, sub): group = cmd.add_argument_group("Stream filtering") group.add_argument("-p", "--path", metavar="PATH", default="*", - help="Match only this path (-p can be omitted)") + help="Match only this path (-p can be omitted)", + ).completer = self.complete.path group.add_argument("path_positional", default="*", - nargs="?", help=argparse.SUPPRESS) + nargs="?", help=argparse.SUPPRESS, + ).completer = self.complete.path group.add_argument("-l", "--layout", default="*", - help="Match only this stream layout") + help="Match only this stream layout", + ).completer = self.complete.layout group = cmd.add_argument_group("Interval info") group.add_argument("-E", "--ext", action="store_true", @@ -35,11 +38,13 @@ def setup(self, sub): group.add_argument("-s", "--start", metavar="TIME", type=self.arg_time, help="Starting timestamp for intervals " - "(free-form, inclusive)") + "(free-form, inclusive)", + ).completer = self.complete.time group.add_argument("-e", "--end", metavar="TIME", type=self.arg_time, help="Ending timestamp for intervals " - "(free-form, noninclusive)") + "(free-form, noninclusive)", + ).completer = self.complete.time group = cmd.add_argument_group("Misc options") group.add_argument("-T", "--timestamp-raw", action="store_true", diff --git a/nilmdb/cmdline/metadata.py b/nilmdb/cmdline/metadata.py index 394a013..4759f35 100644 --- a/nilmdb/cmdline/metadata.py +++ b/nilmdb/cmdline/metadata.py @@ -14,18 +14,22 @@ def setup(self, sub): group = cmd.add_argument_group("Required arguments") group.add_argument("path", - help="Path of stream, e.g. /foo/bar") + help="Path of stream, e.g. /foo/bar", + ).completer = self.complete.path group = cmd.add_argument_group("Actions") exc = group.add_mutually_exclusive_group() exc.add_argument("-g", "--get", nargs="*", metavar="key", - help="Get metadata for specified keys (default all)") + help="Get metadata for specified keys (default all)", + ).completer = self.complete.meta_key exc.add_argument("-s", "--set", nargs="+", metavar="key=value", help="Replace all metadata with provided " - "key=value pairs") + "key=value pairs", + ).completer = self.complete.meta_keyval exc.add_argument("-u", "--update", nargs="+", metavar="key=value", help="Update metadata using provided " - "key=value pairs") + "key=value pairs", + ).completer = self.complete.meta_keyval return cmd def cmd_metadata(self): diff --git a/nilmdb/cmdline/remove.py b/nilmdb/cmdline/remove.py index fe18f3b..dd7a417 100644 --- a/nilmdb/cmdline/remove.py +++ b/nilmdb/cmdline/remove.py @@ -11,13 +11,16 @@ def setup(self, sub): group = cmd.add_argument_group("Data selection") group.add_argument("path", - help="Path of stream, e.g. /foo/bar") + help="Path of stream, e.g. /foo/bar", + ).completer = self.complete.path group.add_argument("-s", "--start", required=True, metavar="TIME", type=self.arg_time, - help="Starting timestamp (free-form, inclusive)") + help="Starting timestamp (free-form, inclusive)", + ).completer = self.complete.time group.add_argument("-e", "--end", required=True, metavar="TIME", type=self.arg_time, - help="Ending timestamp (free-form, noninclusive)") + help="Ending timestamp (free-form, noninclusive)", + ).completer = self.complete.time group = cmd.add_argument_group("Output format") group.add_argument("-c", "--count", action="store_true", diff --git a/nilmdb/cmdline/rename.py b/nilmdb/cmdline/rename.py index 0b31238..71a4588 100644 --- a/nilmdb/cmdline/rename.py +++ b/nilmdb/cmdline/rename.py @@ -15,9 +15,12 @@ def setup(self, sub): cmd.set_defaults(handler = cmd_rename) group = cmd.add_argument_group("Required arguments") group.add_argument("oldpath", - help="Old path, e.g. /foo/old") + help="Old path, e.g. /foo/old", + ).completer = self.complete.path group.add_argument("newpath", - help="New path, e.g. /foo/bar/new") + help="New path, e.g. /foo/bar/new", + ).completer = self.complete.path + return cmd def cmd_rename(self): diff --git a/setup.py b/setup.py index 33ac221..1759bef 100755 --- a/setup.py +++ b/setup.py @@ -91,6 +91,9 @@ include tests/test.order # Docs recursive-include docs Makefile *.md + +# Extras +recursive-include extras * """) # Run setup