You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

187 lines
6.6 KiB

  1. """Command line client functionality"""
  2. import nilmdb.client
  3. from nilmdb.utils.printf import *
  4. from nilmdb.utils import datetime_tz
  5. import nilmdb.utils.time
  6. import sys
  7. import os
  8. import argparse
  9. from argparse import ArgumentDefaultsHelpFormatter as def_form
  10. import signal
  11. try: # pragma: no cover
  12. import argcomplete
  13. except ImportError: # pragma: no cover
  14. argcomplete = None
  15. # Valid subcommands. Defined in separate files just to break
  16. # things up -- they're still called with Cmdline as self.
  17. subcommands = [ "help", "info", "create", "rename", "list", "intervals",
  18. "metadata", "insert", "extract", "remove", "destroy" ]
  19. # Import the subcommand modules
  20. subcmd_mods = {}
  21. for cmd in subcommands:
  22. subcmd_mods[cmd] = __import__("nilmdb.cmdline." + cmd, fromlist = [ cmd ])
  23. class JimArgumentParser(argparse.ArgumentParser):
  24. def parse_args(self, args=None, namespace=None):
  25. # Look for --version anywhere and change it to just "nilmtool
  26. # --version". This makes "nilmtool cmd --version" work, which
  27. # is needed by help2man.
  28. if "--version" in (args or sys.argv[1:]):
  29. args = [ "--version" ]
  30. return argparse.ArgumentParser.parse_args(self, args, namespace)
  31. def error(self, message):
  32. self.print_usage(sys.stderr)
  33. self.exit(2, sprintf("error: %s\n", message))
  34. class Complete(object): # pragma: no cover
  35. # Completion helpers, for using argcomplete (see
  36. # extras/nilmtool-bash-completion.sh)
  37. def escape(self, s):
  38. quote_chars = [ "\\", "\"", "'", " " ]
  39. for char in quote_chars:
  40. s = s.replace(char, "\\" + char)
  41. return s
  42. def none(self, prefix, parsed_args, **kwargs):
  43. return []
  44. rate = none
  45. time = none
  46. url = none
  47. def path(self, prefix, parsed_args, **kwargs):
  48. client = nilmdb.client.Client(parsed_args.url)
  49. return ( self.escape(s[0])
  50. for s in client.stream_list()
  51. if s[0].startswith(prefix) )
  52. def layout(self, prefix, parsed_args, **kwargs):
  53. types = [ "int8", "int16", "int32", "int64",
  54. "uint8", "uint16", "uint32", "uint64",
  55. "float32", "float64" ]
  56. layouts = []
  57. for i in range(1,10):
  58. layouts.extend([(t + "_" + str(i)) for t in types])
  59. return ( l for l in layouts if l.startswith(prefix) )
  60. def meta_key(self, prefix, parsed_args, **kwargs):
  61. return (kv.split('=')[0] for kv
  62. in self.meta_keyval(prefix, parsed_args, **kwargs))
  63. def meta_keyval(self, prefix, parsed_args, **kwargs):
  64. client = nilmdb.client.Client(parsed_args.url)
  65. path = parsed_args.path
  66. if not path:
  67. return []
  68. results = []
  69. # prefix comes in as UTF-8, but results need to be Unicode,
  70. # weird. Still doesn't work in all cases, but that's bugs in
  71. # argcomplete.
  72. prefix = nilmdb.utils.str.decode(prefix)
  73. for (k,v) in client.stream_get_metadata(path).items():
  74. kv = self.escape(k + '=' + v)
  75. if kv.startswith(prefix):
  76. results.append(kv)
  77. return results
  78. class Cmdline(object):
  79. def __init__(self, argv = None):
  80. self.argv = argv or sys.argv[1:]
  81. try:
  82. # Assume command line arguments are encoded with stdin's encoding,
  83. # and reverse it. Won't be needed in Python 3, but for now..
  84. self.argv = [ x.decode(sys.stdin.encoding) for x in self.argv ]
  85. except Exception: # pragma: no cover
  86. pass
  87. self.client = None
  88. self.def_url = os.environ.get("NILMDB_URL", "http://localhost/nilmdb/")
  89. self.subcmd = {}
  90. self.complete = Complete()
  91. def arg_time(self, toparse):
  92. """Parse a time string argument"""
  93. try:
  94. return nilmdb.utils.time.parse_time(toparse)
  95. except ValueError as e:
  96. raise argparse.ArgumentTypeError(sprintf("%s \"%s\"",
  97. str(e), toparse))
  98. # Set up the parser
  99. def parser_setup(self):
  100. self.parser = JimArgumentParser(add_help = False,
  101. formatter_class = def_form)
  102. group = self.parser.add_argument_group("General options")
  103. group.add_argument("-h", "--help", action='help',
  104. help='show this help message and exit')
  105. group.add_argument("-v", "--version", action="version",
  106. version = nilmdb.__version__)
  107. group = self.parser.add_argument_group("Server")
  108. group.add_argument("-u", "--url", action="store",
  109. default=self.def_url,
  110. help="NilmDB server URL (default: %(default)s)"
  111. ).completer = self.complete.url
  112. sub = self.parser.add_subparsers(
  113. title="Commands", dest="command",
  114. description="Use 'help command' or 'command --help' for more "
  115. "details on a particular command.")
  116. # Set up subcommands (defined in separate files)
  117. for cmd in subcommands:
  118. self.subcmd[cmd] = subcmd_mods[cmd].setup(self, sub)
  119. def die(self, formatstr, *args):
  120. fprintf(sys.stderr, formatstr + "\n", *args)
  121. if self.client:
  122. self.client.close()
  123. sys.exit(-1)
  124. def run(self):
  125. # Set SIGPIPE to its default handler -- we don't need Python
  126. # to catch it for us.
  127. try:
  128. signal.signal(signal.SIGPIPE, signal.SIG_DFL)
  129. except ValueError: # pragma: no cover
  130. pass
  131. # Clear cached timezone, so that we can pick up timezone changes
  132. # while running this from the test suite.
  133. datetime_tz._localtz = None
  134. # Run parser
  135. self.parser_setup()
  136. if argcomplete: # pragma: no cover
  137. argcomplete.autocomplete(self.parser)
  138. self.args = self.parser.parse_args(self.argv)
  139. # Run arg verify handler if there is one
  140. if "verify" in self.args:
  141. self.args.verify(self)
  142. self.client = nilmdb.client.Client(self.args.url)
  143. # Make a test connection to make sure things work,
  144. # unless the particular command requests that we don't.
  145. if "no_test_connect" not in self.args:
  146. try:
  147. server_version = self.client.version()
  148. except nilmdb.client.Error as e:
  149. self.die("error connecting to server: %s", str(e))
  150. # Now dispatch client request to appropriate function. Parser
  151. # should have ensured that we don't have any unknown commands
  152. # here.
  153. retval = self.args.handler(self) or 0
  154. self.client.close()
  155. sys.exit(retval)