From f5594dd489317dc406d20f897fc720e0cf89c9d2 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 13 Nov 2008 23:29:35 -0700 Subject: Started work on cleaning up how exceptions are caught and sys.exit() is called in ipalib.cli.CLI --- ipalib/cli.py | 97 ++++++++++++++++++++++++------------------- ipalib/errors.py | 11 +++++ tests/test_ipalib/test_cli.py | 6 +-- 3 files changed, 68 insertions(+), 46 deletions(-) diff --git a/ipalib/cli.py b/ipalib/cli.py index febf399d9..5659cfc0a 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -236,10 +236,6 @@ class textui(backend.Backend): return plural % n -def exit_error(error): - sys.exit('ipa: ERROR: %s' % error) - - class help(frontend.Application): '''Display help on a command.''' @@ -252,8 +248,7 @@ class help(frontend.Application): return key = str(command) if key not in self.application: - print 'help: no such command %r' % key - sys.exit(2) + raise errors.UnknownHelpError(key) cmd = self.application[key] print 'Purpose: %s' % cmd.doc self.application.build_parser(cmd).print_help() @@ -398,7 +393,22 @@ class CLI(object): self.argv = tuple(argv) self.__done = set() - def run(self, init_only=False): + def run(self): + """ + Call `CLI.run_real` in a try/except. + """ + self.bootstrap() + try: + self.run_real() + except KeyboardInterrupt: + print '' + self.api.log.info('operation aborted') + sys.exit() + except errors.IPAError, e: + self.api.log.error(unicode(e)) + sys.exit(e.faultCode) + + def run_real(self): """ Parse ``argv`` and potentially run a command. @@ -423,17 +433,42 @@ class CLI(object): remaining initialization needed to use the `plugable.API` instance. """ - self.__doing('run') + self.__doing('run_real') self.finalize() if self.api.env.mode == 'unit_test': return if len(self.cmd_argv) < 1: - sys.exit(self.api.Command.help()) + self.api.Command.help() + return key = self.cmd_argv[0] if key not in self: - print 'ipa: ERROR: unknown command %r' % key - sys.exit(2) - return self.run_cmd(self[key]) + raise errors.UnknownCommandError(key) + self.run_cmd(self[key]) + + # FIXME: Stuff that might need special handling still: +# # Now run the command +# try: +# ret = cmd(**kw) +# if callable(cmd.output_for_cli): +# (args, options) = cmd.params_2_args_options(kw) +# cmd.output_for_cli(self.api.Backend.textui, ret, *args, **options) +# return 0 +# except socket.error, e: +# print e[1] +# return 1 +# except errors.GenericError, err: +# code = getattr(err,'faultCode',None) +# faultString = getattr(err,'faultString',None) +# if not code: +# raise err +# if code < errors.IPA_ERROR_BASE: +# print "%s: %s" % (code, faultString) +# else: +# print "%s: %s" % (code, getattr(err,'__doc__','')) +# return 1 +# except StandardError, e: +# print e +# return 2 def finalize(self): """ @@ -466,7 +501,8 @@ class CLI(object): Finally, all the CLI-specific plugins are registered. """ self.__doing('load_plugins') - self.bootstrap() + if 'bootstrap' not in self.__done: + self.bootstrap() self.api.load_plugins() for klass in cli_application_commands: self.api.register(klass) @@ -524,39 +560,14 @@ class CLI(object): ) self.__done.add(name) - - def run_cmd(self, cmd): kw = self.parse(cmd) - # If options.interactive, interactively validate params: if self.options.interactive: - try: - kw = self.prompt_interactively(cmd, kw) - except KeyboardInterrupt: - return 0 - # Now run the command - try: - ret = cmd(**kw) - if callable(cmd.output_for_cli): - (args, options) = cmd.params_2_args_options(kw) - cmd.output_for_cli(self.api.Backend.textui, ret, *args, **options) - return 0 - except socket.error, e: - print e[1] - return 1 - except errors.GenericError, err: - code = getattr(err,'faultCode',None) - faultString = getattr(err,'faultString',None) - if not code: - raise err - if code < errors.IPA_ERROR_BASE: - print "%s: %s" % (code, faultString) - else: - print "%s: %s" % (code, getattr(err,'__doc__','')) - return 1 - except StandardError, e: - print e - return 2 + kw = self.prompt_interactively(cmd, kw) + result = cmd(**kw) + if callable(cmd.output_for_cli): + (args, options) = cmd.params_2_args_options(kw) + cmd.output_for_cli(self.api.Backend.textui, result, *args, **options) def prompt_interactively(self, cmd, kw): """ diff --git a/ipalib/errors.py b/ipalib/errors.py index c2d83e73b..71a837e9c 100644 --- a/ipalib/errors.py +++ b/ipalib/errors.py @@ -98,6 +98,7 @@ class IPAError(StandardError): """ format = None + faultCode = 1 def __init__(self, *args): self.args = args @@ -109,6 +110,16 @@ class IPAError(StandardError): return self.format % self.args +class InvocationError(IPAError): + pass + +class UnknownCommandError(InvocationError): + format = 'unknown command "%s"' + +class UnknownHelpError(InvocationError): + format = 'no command nor topic "%s"' + + class ArgumentError(IPAError): """ Raised when a command is called with wrong number of arguments. diff --git a/tests/test_ipalib/test_cli.py b/tests/test_ipalib/test_cli.py index 8cedd0881..56297fdf7 100644 --- a/tests/test_ipalib/test_cli.py +++ b/tests/test_ipalib/test_cli.py @@ -148,12 +148,12 @@ class test_CLI(ClassChecker): assert o.api is api assert o.argv == tuple(argv) - def test_run(self): + def test_run_real(self): """ - Test the `ipalib.cli.CLI.run` method. + Test the `ipalib.cli.CLI.run_real` method. """ self.check_cascade( - 'run', + 'run_real', 'finalize', 'load_plugins', 'bootstrap', -- cgit