[tor-commits] [stem/master] tor-prompt --run argument for running commands

atagar at torproject.org atagar at torproject.org
Mon Apr 17 19:59:32 UTC 2017


commit d14fc24d02bd73c45963d0a6edfabdb93812b0a8
Author: Damian Johnson <atagar at torproject.org>
Date:   Mon Apr 17 12:49:06 2017 -0700

    tor-prompt --run argument for running commands
    
    Neat idea from adrelanos on...
    
      https://trac.torproject.org/projects/tor/ticket/21541
    
    Adding a --run argument that can either be used to run a command...
    
      tor-prompt --run 'GETINFO version'
    
    ... or a file with a series of commands...
    
      tor-prompt --run /home/atagar/tor_commands_to_run
---
 docs/change_log.rst                |  4 ++++
 stem/interpreter/__init__.py       | 44 +++++++++++++++++++++-----------------
 stem/interpreter/arguments.py      | 10 ++++++++-
 stem/interpreter/commands.py       |  6 +++++-
 stem/interpreter/settings.cfg      |  2 ++
 stem/util/system.py                |  2 +-
 test/integ/installation.py         |  4 ++++
 test/integ/interpreter.py          | 35 ++++++++++++++++++++++++++++++
 test/settings.cfg                  |  1 +
 test/unit/interpreter/arguments.py |  6 ++++++
 10 files changed, 91 insertions(+), 23 deletions(-)

diff --git a/docs/change_log.rst b/docs/change_log.rst
index e9b6323..e988c61 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -65,6 +65,10 @@ The following are only available within Stem's `git repository
   * Added timeout argument to :func:`~stem.util.system.call`
   * Added :class:`~stem.util.test_tools.TimedTestRunner` and :func:`~stem.util.test_tools.test_runtimes`
 
+ * **Interpreter**
+
+  * Added a '--run [command or path]' argument to invoke specific commands (:trac:`21541`)
+
 .. _version_1.5:
 
 Version 1.5 (November 20th, 2016)
diff --git a/stem/interpreter/__init__.py b/stem/interpreter/__init__.py
index 237a781..8cdbb3c 100644
--- a/stem/interpreter/__init__.py
+++ b/stem/interpreter/__init__.py
@@ -60,7 +60,7 @@ def main():
     print(stem.interpreter.arguments.get_help())
     sys.exit()
 
-  if args.disable_color:
+  if args.disable_color or not sys.stdout.isatty():
     global PROMPT
     stem.util.term.DISABLE_COLOR_SUPPORT = True
     PROMPT = '>>> '
@@ -76,7 +76,8 @@ def main():
         print(format(msg('msg.tor_unavailable'), *ERROR_OUTPUT))
         sys.exit(1)
       else:
-        print(format(msg('msg.starting_tor'), *HEADER_OUTPUT))
+        if not args.run_cmd and not args.run_path:
+          print(format(msg('msg.starting_tor'), *HEADER_OUTPUT))
 
         control_port = '9051' if args.control_port == 'default' else str(args.control_port)
 
@@ -124,25 +125,28 @@ def main():
 
     interpreter = stem.interpreter.commands.ControlInterpreter(controller)
 
-    for line in msg('msg.startup_banner').splitlines():
-      line_format = HEADER_BOLD_OUTPUT if line.startswith('  ') else HEADER_OUTPUT
-      print(format(line, *line_format))
-
-    print('')
-
-    while True:
+    if args.run_cmd:
+      interpreter.run_command(args.run_cmd, print_response = True)
+    elif args.run_path:
       try:
-        prompt = '... ' if interpreter.is_multiline_context else PROMPT
+        for line in open(args.run_path).readlines():
+          interpreter.run_command(line.strip(), print_response = True)
+      except IOError as exc:
+        print(format(msg('msg.unable_to_read_file', path = args.run_path, error = exc), *ERROR_OUTPUT))
+        sys.exit(1)
 
-        if stem.prereq.is_python_3():
-          user_input = input(prompt)
-        else:
-          user_input = raw_input(prompt)
+    else:
+      for line in msg('msg.startup_banner').splitlines():
+        line_format = HEADER_BOLD_OUTPUT if line.startswith('  ') else HEADER_OUTPUT
+        print(format(line, *line_format))
 
-        response = interpreter.run_command(user_input)
+      print('')
 
-        if response is not None:
-          print(response)
-      except (KeyboardInterrupt, EOFError, stem.SocketClosed) as exc:
-        print('')  # move cursor to the following line
-        break
+      while True:
+        try:
+          prompt = '... ' if interpreter.is_multiline_context else PROMPT
+          user_input = input(prompt) if stem.prereq.is_python_3() else raw_input(prompt)
+          interpreter.run_command(user_input, print_response = True)
+        except (KeyboardInterrupt, EOFError, stem.SocketClosed) as exc:
+          print('')  # move cursor to the following line
+          break
diff --git a/stem/interpreter/arguments.py b/stem/interpreter/arguments.py
index 010aa3f..3160183 100644
--- a/stem/interpreter/arguments.py
+++ b/stem/interpreter/arguments.py
@@ -7,6 +7,7 @@ Commandline argument parsing for our interpreter prompt.
 
 import collections
 import getopt
+import os
 
 import stem.interpreter
 import stem.util.connection
@@ -18,12 +19,14 @@ DEFAULT_ARGS = {
   'control_socket': '/var/run/tor/control',
   'user_provided_socket': False,
   'tor_path': 'tor',
+  'run_cmd': None,
+  'run_path': None,
   'disable_color': False,
   'print_help': False,
 }
 
 OPT = 'i:s:h'
-OPT_EXPANDED = ['interface=', 'socket=', 'tor=', 'no-color', 'help']
+OPT_EXPANDED = ['interface=', 'socket=', 'tor=', 'run=', 'no-color', 'help']
 
 
 def parse(argv):
@@ -71,6 +74,11 @@ def parse(argv):
       args['user_provided_socket'] = True
     elif opt in ('--tor'):
       args['tor_path'] = arg
+    elif opt in ('--run'):
+      if os.path.exists(arg):
+        args['run_path'] = arg
+      else:
+        args['run_cmd'] = arg
     elif opt == '--no-color':
       args['disable_color'] = True
     elif opt in ('-h', '--help'):
diff --git a/stem/interpreter/commands.py b/stem/interpreter/commands.py
index 366f256..4d845a0 100644
--- a/stem/interpreter/commands.py
+++ b/stem/interpreter/commands.py
@@ -294,13 +294,14 @@ class ControlInterpreter(code.InteractiveConsole):
     return format(response, *STANDARD_OUTPUT)
 
   @uses_settings
-  def run_command(self, command, config):
+  def run_command(self, command, config, print_response = False):
     """
     Runs the given command. Requests starting with a '/' are special commands
     to the interpreter, and anything else is sent to the control port.
 
     :param stem.control.Controller controller: tor control connection
     :param str command: command to be processed
+    :param bool print_response: prints the response to stdout if true
 
     :returns: **list** out output lines, each line being a list of
       (msg, format) tuples
@@ -374,4 +375,7 @@ class ControlInterpreter(code.InteractiveConsole):
 
     output += '\n'  # give ourselves an extra line before the next prompt
 
+    if print_response and output is not None:
+      print(output)
+
     return output
diff --git a/stem/interpreter/settings.cfg b/stem/interpreter/settings.cfg
index 1c6b27f..cc8da37 100644
--- a/stem/interpreter/settings.cfg
+++ b/stem/interpreter/settings.cfg
@@ -18,6 +18,7 @@ msg.help
 |  -s, --socket SOCKET_PATH        attach using unix domain socket if present,
 |                                    SOCKET_PATH defaults to: {socket}
 |      --tor PATH                  tor binary if tor isn't already running
+|      --run                       executes the given command or file of commands
 |  --no-color                      disables colorized output
 |  -h, --help                      presents this help
 |
@@ -43,6 +44,7 @@ msg.startup_banner
 
 msg.tor_unavailable Tor isn't running and the command currently isn't in your PATH.
 msg.unable_to_start_tor Unable to start tor: {error}
+msg.unable_to_read_file Unable to read {path}: {error}
 
 msg.starting_tor
 |Tor isn't running. Starting a temporary Tor instance for our interpreter to
diff --git a/stem/util/system.py b/stem/util/system.py
index a3ec756..4b705f3 100644
--- a/stem/util/system.py
+++ b/stem/util/system.py
@@ -1072,7 +1072,7 @@ def call(command, default = UNDEFINED, ignore_exit_status = False, timeout = Non
   if isinstance(command, str):
     command_list = command.split(' ')
   else:
-    command_list = command
+    command_list = map(str, command)
 
   exit_status, runtime, stdout, stderr = None, None, None, None
   start_time = time.time()
diff --git a/test/integ/installation.py b/test/integ/installation.py
index ad21fd0..4d68988 100644
--- a/test/integ/installation.py
+++ b/test/integ/installation.py
@@ -1,3 +1,7 @@
+"""
+Tests installation of our library.
+"""
+
 import glob
 import os
 import shutil
diff --git a/test/integ/interpreter.py b/test/integ/interpreter.py
new file mode 100644
index 0000000..4681c57
--- /dev/null
+++ b/test/integ/interpreter.py
@@ -0,0 +1,35 @@
+"""
+Tests invocation of our interpreter.
+"""
+
+import os
+import tempfile
+import unittest
+
+import stem.util.system
+
+import test.runner
+import test.util
+
+PROMPT_CMD = os.path.join(test.util.STEM_BASE, 'tor-prompt')
+
+
+class TestInterpreter(unittest.TestCase):
+  def test_running_command(self):
+    expected = ['250-config-file=%s' % test.runner.get_runner().get_torrc_path(), '250 OK']
+    self.assertEqual(expected, stem.util.system.call([PROMPT_CMD, '--interface', test.runner.CONTROL_PORT, '--run', 'GETINFO config-file']))
+
+  def test_running_file(self):
+    expected = [
+      '250-config-file=%s' % test.runner.get_runner().get_torrc_path(),
+      '250 OK',
+      '',
+      '250-version=%s' % test.util.tor_version(),
+      '250 OK',
+    ]
+
+    with tempfile.NamedTemporaryFile(prefix = 'test_commands.') as tmp:
+      tmp.write('GETINFO config-file\nGETINFO version')
+      tmp.flush()
+
+      self.assertEqual(expected, stem.util.system.call([PROMPT_CMD, '--interface', test.runner.CONTROL_PORT, '--run', tmp.name]))
diff --git a/test/settings.cfg b/test/settings.cfg
index 1d011f9..11137b4 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -226,6 +226,7 @@ test.integ_tests
 |test.integ.util.proc.TestProc
 |test.integ.util.system.TestSystem
 |test.integ.installation.TestInstallation
+|test.integ.interpreter.TestInterpreter
 |test.integ.descriptor.remote.TestDescriptorDownloader
 |test.integ.descriptor.server_descriptor.TestServerDescriptor
 |test.integ.descriptor.extrainfo_descriptor.TestExtraInfoDescriptor
diff --git a/test/unit/interpreter/arguments.py b/test/unit/interpreter/arguments.py
index 6a765b1..df81e7e 100644
--- a/test/unit/interpreter/arguments.py
+++ b/test/unit/interpreter/arguments.py
@@ -51,6 +51,12 @@ class TestArgumentParsing(unittest.TestCase):
     for invalid_input in invalid_inputs:
       self.assertRaises(ValueError, parse, ['--interface', invalid_input])
 
+  def test_run_with_command(self):
+    self.assertEqual('GETINFO version', parse(['--run', 'GETINFO version']).run_cmd)
+
+  def test_run_with_path(self):
+    self.assertEqual(__file__, parse(['--run', __file__]).run_path)
+
   def test_get_help(self):
     help_text = get_help()
     self.assertTrue('Interactive interpreter for Tor.' in help_text)



More information about the tor-commits mailing list