]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add per-option callbacks, and implement --help with them.
authorBen Darnell <ben@bendarnell.com>
Sun, 30 Sep 2012 22:28:33 +0000 (15:28 -0700)
committerBen Darnell <ben@bendarnell.com>
Sun, 30 Sep 2012 22:33:06 +0000 (15:33 -0700)
tornado/options.py
tornado/test/options_test.py
website/sphinx/releases/next.rst

index c5677c290557e88c72c64518b6d4ca7aecab3f33..22d3837cb63bcac9c26920185fa10980f5fedaf9 100644 (file)
@@ -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)
index e9d65a60e7d354946164415f52cbaa14fe404c36..acf898b06c17b9ea051401da6b80fa96473a7a2b 100644 (file)
@@ -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)
index da9ba30f9b2f74da9180288cdb424fd2dc67c887..6e5cd4f743c4c1faf394fa0f19c5d48e365b04c6 100644 (file)
@@ -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``.