@@ -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 |
@@ -11,6 +11,11 @@ import os | |||||
import argparse | import argparse | ||||
from argparse import ArgumentDefaultsHelpFormatter as def_form | 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 | # Valid subcommands. Defined in separate files just to break | ||||
# things up -- they're still called with Cmdline as self. | # things up -- they're still called with Cmdline as self. | ||||
subcommands = [ "help", "info", "create", "list", "metadata", | subcommands = [ "help", "info", "create", "list", "metadata", | ||||
@@ -27,6 +32,50 @@ class JimArgumentParser(argparse.ArgumentParser): | |||||
self.print_usage(sys.stderr) | self.print_usage(sys.stderr) | ||||
self.exit(2, sprintf("error: %s\n", message)) | 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): | class Cmdline(object): | ||||
def __init__(self, argv = None): | def __init__(self, argv = None): | ||||
@@ -34,6 +83,7 @@ class Cmdline(object): | |||||
self.client = None | self.client = None | ||||
self.def_url = os.environ.get("NILMDB_URL", "http://localhost:12380") | self.def_url = os.environ.get("NILMDB_URL", "http://localhost:12380") | ||||
self.subcmd = {} | self.subcmd = {} | ||||
self.complete = Complete() | |||||
def arg_time(self, toparse): | def arg_time(self, toparse): | ||||
"""Parse a time string argument""" | """Parse a time string argument""" | ||||
@@ -43,6 +93,7 @@ class Cmdline(object): | |||||
raise argparse.ArgumentTypeError(sprintf("%s \"%s\"", | raise argparse.ArgumentTypeError(sprintf("%s \"%s\"", | ||||
str(e), toparse)) | str(e), toparse)) | ||||
# Set up the parser | |||||
def parser_setup(self): | def parser_setup(self): | ||||
self.parser = JimArgumentParser(add_help = False, | self.parser = JimArgumentParser(add_help = False, | ||||
formatter_class = def_form) | formatter_class = def_form) | ||||
@@ -56,7 +107,8 @@ class Cmdline(object): | |||||
group = self.parser.add_argument_group("Server") | group = self.parser.add_argument_group("Server") | ||||
group.add_argument("-u", "--url", action="store", | group.add_argument("-u", "--url", action="store", | ||||
default=self.def_url, | 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( | sub = self.parser.add_subparsers( | ||||
title="Commands", dest="command", | title="Commands", dest="command", | ||||
@@ -80,6 +132,8 @@ class Cmdline(object): | |||||
# Run parser | # Run parser | ||||
self.parser_setup() | self.parser_setup() | ||||
if argcomplete: # pragma: no cover | |||||
argcomplete.autocomplete(self.parser) | |||||
self.args = self.parser.parse_args(self.argv) | self.args = self.parser.parse_args(self.argv) | ||||
# Run arg verify handler if there is one | # Run arg verify handler if there is one | ||||
@@ -22,9 +22,11 @@ Layout types are of the format: type_count | |||||
cmd.set_defaults(handler = cmd_create) | cmd.set_defaults(handler = cmd_create) | ||||
group = cmd.add_argument_group("Required arguments") | group = cmd.add_argument_group("Required arguments") | ||||
group.add_argument("path", | 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", | 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 | return cmd | ||||
def cmd_create(self): | def cmd_create(self): | ||||
@@ -14,7 +14,8 @@ def setup(self, sub): | |||||
cmd.set_defaults(handler = cmd_destroy) | cmd.set_defaults(handler = cmd_destroy) | ||||
group = cmd.add_argument_group("Required arguments") | group = cmd.add_argument_group("Required arguments") | ||||
group.add_argument("path", | 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 | return cmd | ||||
def cmd_destroy(self): | def cmd_destroy(self): | ||||
@@ -12,13 +12,16 @@ def setup(self, sub): | |||||
group = cmd.add_argument_group("Data selection") | group = cmd.add_argument_group("Data selection") | ||||
group.add_argument("path", | 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, | group.add_argument("-s", "--start", required=True, | ||||
metavar="TIME", type=self.arg_time, | 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, | group.add_argument("-e", "--end", required=True, | ||||
metavar="TIME", type=self.arg_time, | 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 = cmd.add_argument_group("Output format") | ||||
group.add_argument("-b", "--bare", action="store_true", | group.add_argument("-b", "--bare", action="store_true", | ||||
@@ -25,7 +25,8 @@ def setup(self, sub): | |||||
group.add_argument("-t", "--timestamp", action="store_true", | group.add_argument("-t", "--timestamp", action="store_true", | ||||
help="Add timestamps to each line") | help="Add timestamps to each line") | ||||
group.add_argument("-r", "--rate", type=float, | 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", | group = cmd.add_argument_group("Start time", | ||||
description=""" | description=""" | ||||
@@ -39,7 +40,8 @@ def setup(self, sub): | |||||
exc = group.add_mutually_exclusive_group() | exc = group.add_mutually_exclusive_group() | ||||
exc.add_argument("-s", "--start", | exc.add_argument("-s", "--start", | ||||
metavar="TIME", type=self.arg_time, | 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", | exc.add_argument("-f", "--filename", action="store_true", | ||||
help="Use filename to determine start time") | help="Use filename to determine start time") | ||||
@@ -52,11 +54,13 @@ def setup(self, sub): | |||||
timezone.""") | timezone.""") | ||||
group.add_argument("-e", "--end", | group.add_argument("-e", "--end", | ||||
metavar="TIME", type=self.arg_time, | 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 = cmd.add_argument_group("Required parameters") | ||||
group.add_argument("path", | 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='-', | group.add_argument("file", nargs = '?', default='-', | ||||
help="File to insert (default: - (stdin))") | help="File to insert (default: - (stdin))") | ||||
return cmd | return cmd | ||||
@@ -21,19 +21,23 @@ def setup(self, sub): | |||||
group = cmd.add_argument_group("Stream selection") | group = cmd.add_argument_group("Stream selection") | ||||
group.add_argument("path", metavar="PATH", | 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", | 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 = cmd.add_argument_group("Interval details") | ||||
group.add_argument("-s", "--start", | group.add_argument("-s", "--start", | ||||
metavar="TIME", type=self.arg_time, | metavar="TIME", type=self.arg_time, | ||||
help="Starting timestamp for intervals " | help="Starting timestamp for intervals " | ||||
"(free-form, inclusive)") | |||||
"(free-form, inclusive)", | |||||
).completer = self.complete.time | |||||
group.add_argument("-e", "--end", | group.add_argument("-e", "--end", | ||||
metavar="TIME", type=self.arg_time, | metavar="TIME", type=self.arg_time, | ||||
help="Ending timestamp for intervals " | help="Ending timestamp for intervals " | ||||
"(free-form, noninclusive)") | |||||
"(free-form, noninclusive)", | |||||
).completer = self.complete.time | |||||
group = cmd.add_argument_group("Misc options") | group = cmd.add_argument_group("Misc options") | ||||
group.add_argument("-T", "--timestamp-raw", action="store_true", | group.add_argument("-T", "--timestamp-raw", action="store_true", | ||||
@@ -18,11 +18,14 @@ def setup(self, sub): | |||||
group = cmd.add_argument_group("Stream filtering") | group = cmd.add_argument_group("Stream filtering") | ||||
group.add_argument("-p", "--path", metavar="PATH", default="*", | 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="*", | group.add_argument("path_positional", default="*", | ||||
nargs="?", help=argparse.SUPPRESS) | |||||
nargs="?", help=argparse.SUPPRESS, | |||||
).completer = self.complete.path | |||||
group.add_argument("-l", "--layout", default="*", | 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 = cmd.add_argument_group("Interval info") | ||||
group.add_argument("-E", "--ext", action="store_true", | group.add_argument("-E", "--ext", action="store_true", | ||||
@@ -35,11 +38,13 @@ def setup(self, sub): | |||||
group.add_argument("-s", "--start", | group.add_argument("-s", "--start", | ||||
metavar="TIME", type=self.arg_time, | metavar="TIME", type=self.arg_time, | ||||
help="Starting timestamp for intervals " | help="Starting timestamp for intervals " | ||||
"(free-form, inclusive)") | |||||
"(free-form, inclusive)", | |||||
).completer = self.complete.time | |||||
group.add_argument("-e", "--end", | group.add_argument("-e", "--end", | ||||
metavar="TIME", type=self.arg_time, | metavar="TIME", type=self.arg_time, | ||||
help="Ending timestamp for intervals " | help="Ending timestamp for intervals " | ||||
"(free-form, noninclusive)") | |||||
"(free-form, noninclusive)", | |||||
).completer = self.complete.time | |||||
group = cmd.add_argument_group("Misc options") | group = cmd.add_argument_group("Misc options") | ||||
group.add_argument("-T", "--timestamp-raw", action="store_true", | group.add_argument("-T", "--timestamp-raw", action="store_true", | ||||
@@ -14,18 +14,22 @@ def setup(self, sub): | |||||
group = cmd.add_argument_group("Required arguments") | group = cmd.add_argument_group("Required arguments") | ||||
group.add_argument("path", | 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") | group = cmd.add_argument_group("Actions") | ||||
exc = group.add_mutually_exclusive_group() | exc = group.add_mutually_exclusive_group() | ||||
exc.add_argument("-g", "--get", nargs="*", metavar="key", | 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", | exc.add_argument("-s", "--set", nargs="+", metavar="key=value", | ||||
help="Replace all metadata with provided " | 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", | exc.add_argument("-u", "--update", nargs="+", metavar="key=value", | ||||
help="Update metadata using provided " | help="Update metadata using provided " | ||||
"key=value pairs") | |||||
"key=value pairs", | |||||
).completer = self.complete.meta_keyval | |||||
return cmd | return cmd | ||||
def cmd_metadata(self): | def cmd_metadata(self): | ||||
@@ -11,13 +11,16 @@ def setup(self, sub): | |||||
group = cmd.add_argument_group("Data selection") | group = cmd.add_argument_group("Data selection") | ||||
group.add_argument("path", | 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, | group.add_argument("-s", "--start", required=True, | ||||
metavar="TIME", type=self.arg_time, | 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, | group.add_argument("-e", "--end", required=True, | ||||
metavar="TIME", type=self.arg_time, | 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 = cmd.add_argument_group("Output format") | ||||
group.add_argument("-c", "--count", action="store_true", | group.add_argument("-c", "--count", action="store_true", | ||||
@@ -15,9 +15,12 @@ def setup(self, sub): | |||||
cmd.set_defaults(handler = cmd_rename) | cmd.set_defaults(handler = cmd_rename) | ||||
group = cmd.add_argument_group("Required arguments") | group = cmd.add_argument_group("Required arguments") | ||||
group.add_argument("oldpath", | 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", | 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 | return cmd | ||||
def cmd_rename(self): | def cmd_rename(self): | ||||
@@ -91,6 +91,9 @@ include tests/test.order | |||||
# Docs | # Docs | ||||
recursive-include docs Makefile *.md | recursive-include docs Makefile *.md | ||||
# Extras | |||||
recursive-include extras * | |||||
""") | """) | ||||
# Run setup | # Run setup | ||||