summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipalib/cli.py246
-rw-r--r--ipalib/constants.py1
-rw-r--r--tests/test_ipalib/test_cli.py44
3 files changed, 210 insertions, 81 deletions
diff --git a/ipalib/cli.py b/ipalib/cli.py
index e15e2ff09..7141ae4b4 100644
--- a/ipalib/cli.py
+++ b/ipalib/cli.py
@@ -84,23 +84,7 @@ class help(frontend.Application):
print 'Purpose: %s' % cmd.doc
self.application.build_parser(cmd).print_help()
- def print_commands(self):
- std = set(self.api.Command) - set(self.api.Application)
- print '\nStandard IPA commands:'
- for key in sorted(std):
- cmd = self.api.Command[key]
- self.print_cmd(cmd)
- print '\nSpecial CLI commands:'
- for cmd in self.api.Application():
- self.print_cmd(cmd)
- print '\nUse the --help option to see all the global options'
- print ''
- def print_cmd(self, cmd):
- print ' %s %s' % (
- to_cli(cmd.name).ljust(self.mcl),
- cmd.doc,
- )
class console(frontend.Application):
@@ -222,6 +206,10 @@ class KWCollector(object):
class CLI(object):
+ """
+ All logic for dispatching over command line interface.
+ """
+
__d = None
__mcl = None
@@ -230,6 +218,144 @@ class CLI(object):
self.argv = tuple(argv)
self.__done = set()
+ def run(self):
+ """
+ Run a command (or attempt to at least).
+
+ This method requires several initialization steps to be completed
+ first, all of which all automatically called with a single call to
+ `CLI.finalize()`. The initialization steps are broken into separate
+ methods simply to make it easy to write unit tests.
+
+ The initialization involves these steps:
+
+ 1. `CLI.parse_globals` parses the global options, which get stored
+ in ``CLI.options``, and stores the remaining args in
+ ``CLI.cmd_argv``.
+
+ 2. `CLI.bootstrap` initializes the environment information in
+ ``CLI.api.env``.
+
+ 3. `CLI.load_plugins` registers all plugins, including the
+ CLI-specific plugins.
+
+ 4. `CLI.finalize` instantiates all plugins and performs the
+ remaining initialization needed to use the `plugable.API`
+ instance.
+ """
+ self.__doing('run')
+ self.finalize()
+ return
+ if len(self.cmd_argv) < 1:
+ self.print_commands()
+ print 'Usage: ipa [global-options] COMMAND'
+ sys.exit(2)
+ key = self.cmd_argv[0]
+ if key not in self:
+ self.print_commands()
+ print 'ipa: ERROR: unknown command %r' % key
+ sys.exit(2)
+ return self.run_cmd(
+ self[key],
+ list(s.decode('utf-8') for s in args[1:])
+ )
+
+ def finalize(self):
+ """
+ Fully initialize ``CLI.api`` `plugable.API` instance.
+
+ This method first calls `CLI.load_plugins` to perform some dependant
+ initialization steps, after which `plugable.API.finalize` is called.
+
+ Finally, the CLI-specific commands are passed a reference to this
+ `CLI` instance by calling `frontend.Application.set_application`.
+ """
+ self.__doing('finalize')
+ self.load_plugins()
+ self.api.finalize()
+ for a in self.api.Application():
+ a.set_application(self)
+ assert self.__d is None
+ self.__d = dict(
+ (c.name.replace('_', '-'), c) for c in self.api.Command()
+ )
+
+ def load_plugins(self):
+ """
+ Load all standard plugins plus the CLI-specific plugins.
+
+ This method first calls `CLI.bootstrap` to preform some dependant
+ initialization steps, after which `plugable.API.load_plugins` is
+ called.
+
+ Finally, all the CLI-specific plugins are registered.
+ """
+ self.__doing('load_plugins')
+ self.bootstrap()
+ self.api.load_plugins()
+ for klass in cli_application_commands:
+ self.api.register(klass)
+
+ def bootstrap(self):
+ """
+ Initialize the ``CLI.api.env`` environment variables.
+
+ This method first calls `CLI.parse_globals` to perform some dependant
+ initialization steps. Then, using environment variables that may have
+ been passed in the global options, the ``overrides`` are constructed
+ and `plugable.API.bootstrap` is called.
+ """
+ self.__doing('bootstrap')
+ self.parse_globals()
+ self.api.env.verbose = self.options.verbose
+ if self.options.config_file:
+ self.api.env.conf = self.options.config_file
+ overrides = {}
+ if self.options.environment:
+ for a in self.options.environment.split(','):
+ a = a.split('=', 1)
+ if len(a) < 2:
+ parser.error('badly specified environment string,'\
+ 'use var1=val1[,var2=val2]..')
+ overrides[a[0].strip()] = a[1].strip()
+ overrides['context'] = 'cli'
+ self.api.bootstrap(**overrides)
+
+ def parse_globals(self):
+ """
+ Parse out the global options.
+
+ This method parses the global options out of the ``CLI.argv`` instance
+ attribute, after which two new instance attributes are available:
+
+ 1. ``CLI.options`` - an ``optparse.Values`` instance containing
+ the global options.
+
+ 2. ``CLI.cmd_argv`` - a tuple containing the remainder of
+ ``CLI.argv`` after the global options have been consumed.
+ """
+ self.__doing('parse_globals')
+ parser = optparse.OptionParser()
+ parser.disable_interspersed_args()
+ parser.add_option('-a', dest='prompt_all', action='store_true',
+ help='Prompt for all missing options interactively')
+ parser.add_option('-n', dest='interactive', action='store_false',
+ help='Don\'t prompt for any options interactively')
+ parser.add_option('-c', dest='config_file',
+ help='Specify different configuration file')
+ parser.add_option('-e', dest='environment',
+ help='Specify or override environment variables')
+ parser.add_option('-v', dest='verbose', action='store_true',
+ help='Verbose output')
+ parser.set_defaults(
+ prompt_all=False,
+ interactive=True,
+ verbose=False,
+ )
+ (options, args) = parser.parse_args(list(self.argv))
+ self.options = options
+ self.cmd_argv = tuple(args)
+
def __doing(self, name):
if name in self.__done:
raise StandardError(
@@ -237,11 +363,28 @@ class CLI(object):
)
self.__done.add(name)
- def __do_if_not_done(self, name):
- if name not in self.__done:
- getattr(self, name)()
+ def print_commands(self):
+ std = set(self.api.Command) - set(self.api.Application)
+ print '\nStandard IPA commands:'
+ for key in sorted(std):
+ cmd = self.api.Command[key]
+ self.print_cmd(cmd)
+ print '\nSpecial CLI commands:'
+ for cmd in self.api.Application():
+ self.print_cmd(cmd)
+ print '\nUse the --help option to see all the global options'
+ print ''
+
+ def print_cmd(self, cmd):
+ print ' %s %s' % (
+ to_cli(cmd.name).ljust(self.mcl),
+ cmd.doc,
+ )
def isdone(self, name):
+ """
+ Return True in method named ``name`` has already been called.
+ """
return name in self.__done
def __contains__(self, key):
@@ -252,7 +395,7 @@ class CLI(object):
assert self.__d is not None, 'you must call finalize() first'
return self.__d[key]
- def finalize(self):
+ def old_finalize(self):
api = self.api
for klass in cli_application_commands:
api.register(klass)
@@ -261,29 +404,8 @@ class CLI(object):
a.set_application(self)
self.build_map()
- def build_map(self):
- assert self.__d is None
- self.__d = dict(
- (c.name.replace('_', '-'), c) for c in self.api.Command()
- )
- def run(self):
- self.finalize()
- set_default_env(self.api.env)
- args = self.parse_globals()
- if len(args) < 1:
- self.print_commands()
- print 'Usage: ipa [global-options] COMMAND'
- sys.exit(2)
- key = args[0]
- if key not in self:
- self.print_commands()
- print 'ipa: ERROR: unknown command %r' % key
- sys.exit(2)
- return self.run_cmd(
- self[key],
- list(s.decode('utf-8') for s in args[1:])
- )
+
def run_cmd(self, cmd, argv):
kw = self.parse(cmd, argv)
@@ -370,45 +492,9 @@ class CLI(object):
parser.add_option(o)
return parser
- def parse_globals(self):
- self.__doing('parse_globals')
- parser = optparse.OptionParser()
- parser.disable_interspersed_args()
- parser.add_option('-a', dest='prompt_all', action='store_true',
- help='Prompt for all missing options interactively')
- parser.add_option('-n', dest='interactive', action='store_false',
- help='Don\'t prompt for any options interactively')
- parser.add_option('-c', dest='config_file',
- help='Specify different configuration file')
- parser.add_option('-e', dest='environment',
- help='Specify or override environment variables')
- parser.add_option('-v', dest='verbose', action='store_true',
- help='Verbose output')
- parser.set_defaults(
- prompt_all=False,
- interactive=True,
- verbose=False,
- )
- (options, args) = parser.parse_args(list(self.argv))
- self.options = options
- self.cmd_argv = tuple(args)
- def bootstrap(self):
- self.__doing('bootstrap')
- self.parse_globals()
- self.api.env.verbose = self.options.verbose
- if self.options.config_file:
- self.api.env.conf = self.options.config_file
- overrides = {}
- if self.options.environment:
- for a in self.options.environment.split(','):
- a = a.split('=', 1)
- if len(a) < 2:
- parser.error('badly specified environment string,'\
- 'use var1=val1[,var2=val2]..')
- overrides[a[0].strip()] = a[1].strip()
- overrides['context'] = 'cli'
- self.api.bootstrap(**overrides)
+
+
def get_usage(self, cmd):
return ' '.join(self.get_usage_iter(cmd))
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 4942cc9b7..9da93e006 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -54,7 +54,6 @@ DEFAULT_CONFIG = (
('verbose', False),
('debug', False),
-
# ********************************************************
# The remaining keys are never set from the values here!
# ********************************************************
diff --git a/tests/test_ipalib/test_cli.py b/tests/test_ipalib/test_cli.py
index 389bb52c1..7bcbfb0c6 100644
--- a/tests/test_ipalib/test_cli.py
+++ b/tests/test_ipalib/test_cli.py
@@ -115,6 +115,17 @@ class test_CLI(ClassChecker):
assert o.api is api
return (o, api, home)
+ def check_cascade(self, *names):
+ (o, api, home) = self.new()
+ method = getattr(o, names[0])
+ for name in names:
+ assert o.isdone(name) is False
+ method()
+ for name in names:
+ assert o.isdone(name) is True
+ e = raises(StandardError, method)
+ assert str(e) == 'CLI.%s() already called' % names[0]
+
def test_init(self):
"""
Test the `ipalib.cli.CLI.__init__` method.
@@ -201,3 +212,36 @@ class test_CLI(ClassChecker):
assert api.env.from_default_conf == 'set in default.conf'
assert api.env.from_cli_conf == 'set in cli.conf'
assert list(api.env) == sorted(keys + added)
+
+ def test_load_plugins(self):
+ """
+ Test the `ipalib.cli.CLI.load_plugins` method.
+ """
+ self.check_cascade(
+ 'load_plugins',
+ 'bootstrap',
+ 'parse_globals'
+ )
+ (o, api, home) = self.new()
+ assert api.isdone('load_plugins') is False
+ o.load_plugins()
+ assert api.isdone('load_plugins') is True
+
+ def test_finalize(self):
+ """
+ Test the `ipalib.cli.CLI.finalize` method.
+ """
+ self.check_cascade(
+ 'finalize',
+ 'load_plugins',
+ 'bootstrap',
+ 'parse_globals'
+ )
+
+ (o, api, home) = self.new()
+ assert api.isdone('finalize') is False
+ assert 'Command' not in api
+ o.finalize()
+ assert api.isdone('finalize') is True
+ assert list(api.Command) == \
+ sorted(k.__name__ for k in cli.cli_application_commands)