[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