commit d61e330a715fbf266f7c9471f1a5d35073480a65 Author: Damian Johnson atagar@torproject.org Date: Sat Dec 21 16:38:12 2013 -0800
Moving argument parsing to its own module
Our starter is way too large. Argument parsing is a good candidate for its own submodule. --- arm/arguments.py | 117 ++++++++++++++++++++++++++++++++++++++++ arm/starter.py | 104 ++++------------------------------- test/arguments.py | 66 +++++++++++++++++++++++ test/starter/arg_parsing.py | 66 ----------------------- test/starter/get_controller.py | 13 ++--- 5 files changed, 200 insertions(+), 166 deletions(-)
diff --git a/arm/arguments.py b/arm/arguments.py new file mode 100644 index 0000000..4f796cc --- /dev/null +++ b/arm/arguments.py @@ -0,0 +1,117 @@ +""" +Commandline argument parsing for arm. +""" + +import collections +import getopt +import os + +import arm.logPanel + +import stem.connection +import stem.util.conf + +CONFIG = stem.util.conf.config_dict("arm", { + 'attribute.debug_log_path': '', + 'msg.help': '', +}) + +DEFAULT_ARGS = { + 'control_address': '127.0.0.1', + 'control_port': 9051, + 'user_provided_port': False, + 'control_socket': '/var/run/tor/control', + 'user_provided_socket': False, + 'config': os.path.expanduser("~/.arm/armrc"), + 'debug': False, + 'blind': False, + 'logged_events': 'N3', + 'print_version': False, + 'print_help': False, +} + +OPT = "i:s:c:dbe:vh" + +OPT_EXPANDED = [ + "interface=", + "socket=", + "config=", + "debug", + "blind", + "event=", + "version", + "help", +] + + +def parse(argv): + """ + Parses our arguments, providing a named tuple with their values. + + :param list argv: input arguments to be parsed + + :returns: a **named tuple** with our parsed arguments + + :raises: **ValueError** if we got an invalid argument + :raises: **getopt.GetoptError** if the arguments don't conform with what we + accept + """ + + args = dict(DEFAULT_ARGS) + + for opt, arg in getopt.getopt(argv, OPT, OPT_EXPANDED)[0]: + if opt in ("-i", "--interface"): + if ':' in arg: + address, port = arg.split(':', 1) + else: + address, port = None, arg + + if address is not None: + if not stem.util.connection.is_valid_ipv4_address(address): + raise ValueError("'%s' isn't a valid IPv4 address" % address) + + args['control_address'] = address + + if not stem.util.connection.is_valid_port(port): + raise ValueError("'%s' isn't a valid port number" % port) + + args['control_port'] = int(port) + args['user_provided_port'] = True + elif opt in ("-s", "--socket"): + args['control_socket'] = arg + args['user_provided_socket'] = True + elif opt in ("-c", "--config"): + args['config'] = arg + elif opt in ("-d", "--debug"): + args['debug'] = True + elif opt in ("-b", "--blind"): + args['blind'] = True + elif opt in ("-e", "--event"): + args['logged_events'] = arg + elif opt in ("-v", "--version"): + args['print_version'] = True + elif opt in ("-h", "--help"): + args['print_help'] = True + + # translates our args dict into a named tuple + + Args = collections.namedtuple('Args', args.keys()) + return Args(**args) + + +def get_help(): + """ + Provides our --help usage information. + + :returns: **str** with our usage information + """ + + return CONFIG['msg.help'].format( + address = DEFAULT_ARGS['control_address'], + port = DEFAULT_ARGS['control_port'], + socket = DEFAULT_ARGS['control_socket'], + config = DEFAULT_ARGS['config'], + debug_path = CONFIG['attribute.debug_log_path'], + events = DEFAULT_ARGS['logged_events'], + event_flags = arm.logPanel.EVENT_LISTING, + ) diff --git a/arm/starter.py b/arm/starter.py index e497c72..fd96236 100644 --- a/arm/starter.py +++ b/arm/starter.py @@ -4,7 +4,6 @@ information. This starts the applicatin, getting a tor connection and parsing arguments. """
-import collections import curses import getopt import getpass @@ -17,6 +16,7 @@ import time import threading
import arm +import arm.arguments import arm.controller import arm.logPanel import arm.util.panel @@ -33,13 +33,12 @@ import stem.util.connection import stem.util.log import stem.util.system
-LOG_DUMP_PATH = os.path.expanduser("~/.arm/log") SETTINGS_PATH = os.path.join(os.path.dirname(__file__), 'settings.cfg')
CONFIG = stem.util.conf.config_dict("arm", { + 'attribute.debug_log_path': '', 'tor.password': None, 'startup.events': 'N3', - 'msg.help': '', 'msg.debug_header': '', 'msg.wrong_port_type': '', 'msg.wrong_socket_type': '', @@ -54,26 +53,6 @@ CONFIG = stem.util.conf.config_dict("arm", { 'msg.unknown_term': '', })
-# Our default arguments. The _get_args() function provides a named tuple of -# this merged with our argv. - -ARGS = { - 'control_address': '127.0.0.1', - 'control_port': 9051, - 'user_provided_port': False, - 'control_socket': '/var/run/tor/control', - 'user_provided_socket': False, - 'config': os.path.expanduser("~/.arm/armrc"), - 'debug': False, - 'blind': False, - 'logged_events': 'N3', - 'print_version': False, - 'print_help': False, -} - -OPT = "i:s:c:dbe:vh" -OPT_EXPANDED = ["interface=", "socket=", "config=", "debug", "blind", "event=", "version", "help"] -
def _load_settings(config = 'arm'): """ @@ -99,61 +78,6 @@ def _load_settings(config = 'arm'): return config
-def _get_args(argv): - """ - Parses our arguments, providing a named tuple with their values. - - :param list argv: input arguments to be parsed - - :returns: a **named tuple** with our parsed arguments - - :raises: **ValueError** if we got an invalid argument - :raises: **getopt.GetoptError** if the arguments don't conform with what we - accept - """ - - args = dict(ARGS) - - for opt, arg in getopt.getopt(argv, OPT, OPT_EXPANDED)[0]: - if opt in ("-i", "--interface"): - if ':' in arg: - address, port = arg.split(':', 1) - else: - address, port = None, arg - - if address is not None: - if not stem.util.connection.is_valid_ipv4_address(address): - raise ValueError("'%s' isn't a valid IPv4 address" % address) - - args['control_address'] = address - - if not stem.util.connection.is_valid_port(port): - raise ValueError("'%s' isn't a valid port number" % port) - - args['control_port'] = int(port) - args['user_provided_port'] = True - elif opt in ("-s", "--socket"): - args['control_socket'] = arg - args['user_provided_socket'] = True - elif opt in ("-c", "--config"): - args['config'] = arg - elif opt in ("-d", "--debug"): - args['debug'] = True - elif opt in ("-b", "--blind"): - args['blind'] = True - elif opt in ("-e", "--event"): - args['logged_events'] = arg - elif opt in ("-v", "--version"): - args['print_version'] = True - elif opt in ("-h", "--help"): - args['print_help'] = True - - # translates our args dict into a named tuple - - Args = collections.namedtuple('Args', args.keys()) - return Args(**args) - - def _get_controller(args): """ Provides a Controller for the endpoint specified in the given arguments. @@ -225,17 +149,17 @@ def _authenticate(controller, password):
def _setup_debug_logging(): """ - Configures us to log at stem's trace level to LOG_DUMP_PATH. + Configures us to log at stem's trace level to 'attribute.debug_log_path'.
:raises: **IOError** if we can't log to this location """
- debug_dir = os.path.dirname(LOG_DUMP_PATH) + debug_dir = os.path.dirname(CONFIG['attribute.debug_log_path'])
if not os.path.exists(debug_dir): os.makedirs(debug_dir)
- debug_handler = logging.FileHandler(LOG_DUMP_PATH, mode = 'w') + debug_handler = logging.FileHandler(CONFIG['attribute.debug_log_path'], mode = 'w') debug_handler.setLevel(stem.util.log.logging_level(stem.util.log.TRACE)) debug_handler.setFormatter(logging.Formatter( fmt = '%(asctime)s [%(levelname)s] %(message)s', @@ -285,10 +209,11 @@ def _shutdown_daemons(): def main(): config = stem.util.conf.get_config("arm") config.set('attribute.start_time', str(int(time.time()))) + config.set('attribute.debug_log_path', os.path.expanduser("~/.arm/log"))
try: _load_settings() - args = _get_args(sys.argv[1:]) + args = arm.arguments.parse(sys.argv[1:]) except getopt.GetoptError as exc: print "%s (for usage provide --help)" % exc sys.exit(1) @@ -297,16 +222,7 @@ def main(): sys.exit(1)
if args.print_help: - print CONFIG['msg.help'].format( - address = ARGS['control_address'], - port = ARGS['control_port'], - socket = ARGS['control_socket'], - config = ARGS['config'], - debug_path = LOG_DUMP_PATH, - events = ARGS['logged_events'], - event_flags = arm.logPanel.EVENT_LISTING, - ) - + print arm.arguments.get_help() sys.exit()
if args.print_version: @@ -317,7 +233,7 @@ def main(): try: _setup_debug_logging() except IOError as exc: - print "Unable to write to our debug log file (%s): %s" % (LOG_DUMP_PATH, exc.strerror) + print "Unable to write to our debug log file (%s): %s" % (CONFIG['attribute.debug_log_path'], exc.strerror) sys.exit(1)
stem.util.log.trace(CONFIG['msg.debug_header'].format( @@ -330,7 +246,7 @@ def main(): armrc_content = _armrc_dump(args.config), ))
- print "Saving a debug log to %s, please check it for sensitive information before sharing" % LOG_DUMP_PATH + print "Saving a debug log to %s, please check it for sensitive information before sharing" % CONFIG['attribute.debug_log_path']
# loads user's personal armrc if available
diff --git a/test/arguments.py b/test/arguments.py new file mode 100644 index 0000000..c7e1168 --- /dev/null +++ b/test/arguments.py @@ -0,0 +1,66 @@ +import getopt +import unittest + +from arm.arguments import parse, DEFAULT_ARGS + + +class TestArgumentParsing(unittest.TestCase): + def test_that_we_get_default_values(self): + args = parse([]) + + for attr in DEFAULT_ARGS: + self.assertEqual(DEFAULT_ARGS[attr], getattr(args, attr)) + + def test_that_we_load_arguments(self): + args = parse(['--interface', '10.0.0.25:80']) + self.assertEqual('10.0.0.25', args.control_address) + self.assertEqual(80, args.control_port) + + args = parse(['--interface', '80']) + self.assertEqual(DEFAULT_ARGS['control_address'], args.control_address) + self.assertEqual(80, args.control_port) + + args = parse(['--socket', '/tmp/my_socket', '--config', '/tmp/my_config']) + self.assertEqual('/tmp/my_socket', args.control_socket) + self.assertEqual('/tmp/my_config', args.config) + + args = parse(['--debug', '--blind']) + self.assertEqual(True, args.debug) + self.assertEqual(True, args.blind) + + args = parse(['--event', 'D1']) + self.assertEqual('D1', args.logged_events) + + args = parse(['--version']) + self.assertEqual(True, args.print_version) + + args = parse(['--help']) + self.assertEqual(True, args.print_help) + + def test_examples(self): + args = parse(['-b', '-i', '1643']) + self.assertEqual(True, args.blind) + self.assertEqual(1643, args.control_port) + + args = parse(['-e', 'we', '-c', '/tmp/cfg']) + self.assertEqual('we', args.logged_events) + self.assertEqual('/tmp/cfg', args.config) + + def test_that_we_reject_unrecognized_arguments(self): + self.assertRaises(getopt.GetoptError, parse, ['--blarg', 'stuff']) + + def test_that_we_reject_invalid_interfaces(self): + invalid_inputs = ( + '', + ' ', + 'blarg', + '127.0.0.1', + '127.0.0.1:', + ':80', + '400.0.0.1:80', + '127.0.0.1:-5', + '127.0.0.1:500000', + ) + + for invalid_input in invalid_inputs: + self.assertRaises(ValueError, parse, ['--interface', invalid_input]) diff --git a/test/starter/arg_parsing.py b/test/starter/arg_parsing.py deleted file mode 100644 index f6c60f5..0000000 --- a/test/starter/arg_parsing.py +++ /dev/null @@ -1,66 +0,0 @@ -import getopt -import unittest - -from arm.starter import _get_args, ARGS - - -class TestArgumentParsing(unittest.TestCase): - def test_that_we_get_default_values(self): - args = _get_args([]) - - for attr in ARGS: - self.assertEqual(ARGS[attr], getattr(args, attr)) - - def test_that_we_load_arguments(self): - args = _get_args(['--interface', '10.0.0.25:80']) - self.assertEqual('10.0.0.25', args.control_address) - self.assertEqual(80, args.control_port) - - args = _get_args(['--interface', '80']) - self.assertEqual(ARGS['control_address'], args.control_address) - self.assertEqual(80, args.control_port) - - args = _get_args(['--socket', '/tmp/my_socket', '--config', '/tmp/my_config']) - self.assertEqual('/tmp/my_socket', args.control_socket) - self.assertEqual('/tmp/my_config', args.config) - - args = _get_args(['--debug', '--blind']) - self.assertEqual(True, args.debug) - self.assertEqual(True, args.blind) - - args = _get_args(['--event', 'D1']) - self.assertEqual('D1', args.logged_events) - - args = _get_args(['--version']) - self.assertEqual(True, args.print_version) - - args = _get_args(['--help']) - self.assertEqual(True, args.print_help) - - def test_examples(self): - args = _get_args(['-b', '-i', '1643']) - self.assertEqual(True, args.blind) - self.assertEqual(1643, args.control_port) - - args = _get_args(['-e', 'we', '-c', '/tmp/cfg']) - self.assertEqual('we', args.logged_events) - self.assertEqual('/tmp/cfg', args.config) - - def test_that_we_reject_unrecognized_arguments(self): - self.assertRaises(getopt.GetoptError, _get_args, ['--blarg', 'stuff']) - - def test_that_we_reject_invalid_interfaces(self): - invalid_inputs = ( - '', - ' ', - 'blarg', - '127.0.0.1', - '127.0.0.1:', - ':80', - '400.0.0.1:80', - '127.0.0.1:-5', - '127.0.0.1:500000', - ) - - for invalid_input in invalid_inputs: - self.assertRaises(ValueError, _get_args, ['--interface', invalid_input]) diff --git a/test/starter/get_controller.py b/test/starter/get_controller.py index 974b4b5..dcd5f0c 100644 --- a/test/starter/get_controller.py +++ b/test/starter/get_controller.py @@ -2,7 +2,8 @@ import unittest
from mock import Mock, patch
-from arm.starter import _get_args, _get_controller +from arm.arguments import parse +from arm.starter import _get_controller
import stem import stem.connection @@ -39,11 +40,11 @@ class TestGetController(unittest.TestCase): def test_getting_a_control_port(self, from_port_mock): from_port_mock.return_value = 'success'
- self.assertEqual('success', _get_controller(_get_args([]))) + self.assertEqual('success', _get_controller(parse([]))) from_port_mock.assert_called_once_with('127.0.0.1', 9051) from_port_mock.reset_mock()
- self.assertEqual('success', _get_controller(_get_args(['--interface', '255.0.0.10:80']))) + self.assertEqual('success', _get_controller(parse(['--interface', '255.0.0.10:80']))) from_port_mock.assert_called_once_with('255.0.0.10', 80)
@patch('os.path.exists', Mock(return_value = True)) @@ -51,16 +52,16 @@ class TestGetController(unittest.TestCase): def test_getting_a_control_socket(self, from_socket_file_mock): from_socket_file_mock.return_value = 'success'
- self.assertEqual('success', _get_controller(_get_args([]))) + self.assertEqual('success', _get_controller(parse([]))) from_socket_file_mock.assert_called_once_with('/var/run/tor/control') from_socket_file_mock.reset_mock()
- self.assertEqual('success', _get_controller(_get_args(['--socket', '/tmp/my_socket']))) + self.assertEqual('success', _get_controller(parse(['--socket', '/tmp/my_socket']))) from_socket_file_mock.assert_called_once_with('/tmp/my_socket')
def _assert_get_controller_fails_with(self, args, msg): try: - _get_controller(_get_args(args)) + _get_controller(parse(args)) self.fail() except ValueError, exc: self.assertEqual(msg, str(exc))
tor-commits@lists.torproject.org