@@ -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 | |||
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 | |||
@@ -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): | |||
@@ -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): | |||
@@ -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", | |||
@@ -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 | |||
@@ -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", | |||
@@ -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", | |||
@@ -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): | |||
@@ -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", | |||
@@ -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): | |||
@@ -91,6 +91,9 @@ include tests/test.order | |||
# Docs | |||
recursive-include docs Makefile *.md | |||
# Extras | |||
recursive-include extras * | |||
""") | |||
# Run setup | |||