From: Ben Darnell Date: Sun, 30 Sep 2012 22:28:33 +0000 (-0700) Subject: Add per-option callbacks, and implement --help with them. X-Git-Tag: v3.0.0~256 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f61352c87fe46dac9482ca28876e6c8bbf49d3b8;p=thirdparty%2Ftornado.git Add per-option callbacks, and implement --help with them. --- diff --git a/tornado/options.py b/tornado/options.py index c5677c290..22d3837cb 100644 --- a/tornado/options.py +++ b/tornado/options.py @@ -75,6 +75,8 @@ class _Options(dict): def __init__(self): super(_Options, self).__init__() self.__dict__['_parse_callbacks'] = [] + self.define("help", type=bool, help="show this help information", + callback=self._help_callback) def __getattr__(self, name): if isinstance(self.get(name), _Option): @@ -87,7 +89,7 @@ class _Options(dict): raise AttributeError("Unrecognized option %r" % name) def define(self, name, default=None, type=None, help=None, metavar=None, - multiple=False, group=None): + multiple=False, group=None, callback=None): if name in self: raise Error("Option %r already defined in %s", name, self[name].file_name) @@ -107,7 +109,8 @@ class _Options(dict): group_name = file_name self[name] = _Option(name, file_name=file_name, default=default, type=type, help=help, metavar=metavar, - multiple=multiple, group_name=group_name) + multiple=multiple, group_name=group_name, + callback=callback) def parse_command_line(self, args=None, final=True): if args is None: @@ -134,9 +137,6 @@ class _Options(dict): else: raise Error('Option %r requires a value' % name) option.parse(value) - if self.help: - print_help() - sys.exit(0) if final: self.run_parse_callbacks() @@ -153,8 +153,10 @@ class _Options(dict): if final: self.run_parse_callbacks() - def print_help(self, file=sys.stdout): - """Prints all the command line options to stdout.""" + def print_help(self, file=None): + """Prints all the command line options to stderr (or another file).""" + if file is None: + file = sys.stderr print >> file, "Usage: %s [OPTIONS]" % sys.argv[0] print >> file, "\nOptions:\n" by_group = {} @@ -180,6 +182,11 @@ class _Options(dict): print >> file, "%-34s %s" % (' ', line) print >> file + def _help_callback(self, value): + if value: + self.print_help() + sys.exit(0) + def add_parse_callback(self, callback): self._parse_callbacks.append(stack_context.wrap(callback)) @@ -189,8 +196,9 @@ class _Options(dict): class _Option(object): - def __init__(self, name, default=None, type=basestring, help=None, metavar=None, - multiple=False, file_name=None, group_name=None): + def __init__(self, name, default=None, type=basestring, help=None, + metavar=None, multiple=False, file_name=None, group_name=None, + callback=None): if default is None and multiple: default = [] self.name = name @@ -200,6 +208,7 @@ class _Option(object): self.multiple = multiple self.file_name = file_name self.group_name = group_name + self.callback = callback self.default = default self._value = None @@ -226,6 +235,8 @@ class _Option(object): self._value.append(_parse(part)) else: self._value = _parse(value) + if self.callback is not None: + self.callback(self._value) return self.value() def set(self, value): @@ -242,6 +253,8 @@ class _Option(object): raise Error("Option %r is required to be a %s (%s given)" % (self.name, self.type.__name__, type(value))) self._value = value + if self.callback is not None: + self.callback(self._value) # Supported date/time formats in our options _DATETIME_FORMATS = [ @@ -316,7 +329,7 @@ Supports both attribute-style and dict-style access. def define(name, default=None, type=None, help=None, metavar=None, - multiple=False, group=None): + multiple=False, group=None, callback=None): """Defines a new command line option. If type is given (one of str, float, int, datetime, or timedelta) @@ -338,9 +351,21 @@ def define(name, default=None, type=None, help=None, metavar=None, Command line option names must be unique globally. They can be parsed from the command line with parse_command_line() or parsed from a config file with parse_config_file. + + If a callback is given, it will be run with the new value whenever + the option is changed. This can be used to combine command-line + and file-based options:: + + define("config", type=str, help="path to config file", + callback=lambda path: parse_config_file(path, final=False)) + + With this definition, options in the file specified by ``--config`` will + override options set earlier on the command line, but can be overridden + by later flags. """ return options.define(name, default=default, type=type, help=help, - metavar=metavar, multiple=multiple, group=group) + metavar=metavar, multiple=multiple, group=group, + callback=callback) def parse_command_line(args=None, final=True): @@ -377,5 +402,4 @@ def add_parse_callback(callback): # Default options -define("help", type=bool, help="show this help information") define_logging_options(options) diff --git a/tornado/test/options_test.py b/tornado/test/options_test.py index e9d65a60e..acf898b06 100644 --- a/tornado/test/options_test.py +++ b/tornado/test/options_test.py @@ -1,38 +1,51 @@ from __future__ import absolute_import, division, with_statement +import sys + from tornado.options import _Options from tornado.test.util import unittest +try: + from cStringIO import StringIO # python 2 +except ImportError: + from io import StringIO # python 3 class OptionsTest(unittest.TestCase): - def setUp(self): - self.options = _Options() - define = self.options.define - # these are currently required - define("help", default=False) - - define("port", default=80) - def test_parse_command_line(self): - self.options.parse_command_line(["main.py", "--port=443"]) - self.assertEqual(self.options.port, 443) + options = _Options() + options.define("port", default=80) + options.parse_command_line(["main.py", "--port=443"]) + self.assertEqual(options.port, 443) def test_parse_callbacks(self): + options = _Options() self.called = False def callback(): self.called = True - self.options.add_parse_callback(callback) + options.add_parse_callback(callback) # non-final parse doesn't run callbacks - self.options.parse_command_line(["main.py"], final=False) + options.parse_command_line(["main.py"], final=False) self.assertFalse(self.called) # final parse does - self.options.parse_command_line(["main.py"]) + options.parse_command_line(["main.py"]) self.assertTrue(self.called) # callbacks can be run more than once on the same options # object if there are multiple final parses self.called = False - self.options.parse_command_line(["main.py"]) + options.parse_command_line(["main.py"]) self.assertTrue(self.called) + + def test_help(self): + options = _Options() + try: + orig_stderr = sys.stderr + sys.stderr = StringIO() + with self.assertRaises(SystemExit): + options.parse_command_line(["main.py", "--help"]) + usage = sys.stderr.getvalue() + finally: + sys.stderr = orig_stderr + self.assertIn("Usage:", usage) diff --git a/website/sphinx/releases/next.rst b/website/sphinx/releases/next.rst index da9ba30f9..6e5cd4f74 100644 --- a/website/sphinx/releases/next.rst +++ b/website/sphinx/releases/next.rst @@ -89,3 +89,9 @@ In progress to supress these callbacks. * Function `tornado.options.enable_pretty_logging` has been moved to the `tornado.log` module. +* `tornado.options.define` now takes a ``callback`` argument. This callback + will be run with the new value whenever the option is changed. This is + especially useful for options that set other options, such as by reading + from a config file. +* `tornado.option.parse_command_line` ``--help`` output now goes to ``stderr`` + rather than ``stdout``.