From 3fe13d5945df224643374da477f68e04d4f443e5 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 8 Aug 2008 21:46:23 +0000 Subject: 87: Moved to_cli(), from_cli() functions from plugable.py into new cli.py file; moved corresponding unit tests into new test_cli.py file --- ipalib/cli.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 ipalib/cli.py (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py new file mode 100644 index 00000000..5e257f70 --- /dev/null +++ b/ipalib/cli.py @@ -0,0 +1,40 @@ +# Authors: +# Jason Gerard DeRose +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Functionality for Command Line Inteface. +""" + + +def to_cli(name): + """ + Takes a Python identifier and transforms it into form suitable for the + Command Line Interface. + """ + assert isinstance(name, str) + return name.replace('_', '-') + + +def from_cli(cli_name): + """ + Takes a string from the Command Line Interface and transforms it into a + Python identifier. + """ + assert isinstance(cli_name, basestring) + return cli_name.replace('-', '_') -- cgit From 92824182911007ce3e9cf4f858f70434594ee5dd Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 11 Aug 2008 19:35:57 +0000 Subject: 110: Started fleshing out more in cli.py --- ipalib/cli.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 5e257f70..63988337 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -21,6 +21,8 @@ Functionality for Command Line Inteface. """ +import sys +import re def to_cli(name): """ @@ -38,3 +40,32 @@ def from_cli(cli_name): """ assert isinstance(cli_name, basestring) return cli_name.replace('-', '_') + + +class CLI(object): + def __init__(self, api): + self.__api = api + + def __get_api(self): + return self.__api + api = property(__get_api) + + def print_commands(self): + for cmd in self.api.cmd: + print to_cli(cmd.name) + + def run(self): + if len(sys.argv) < 2: + self.print_commands() + print 'Usage: ipa COMMAND [OPTIONS]' + sys.exit(2) + return + name= sys.argv[1] + if name == '_api_': + print_api() + sys.exit() + elif name not in api.cmd: + print_commands() + print 'ipa: ERROR: unknown command %r' % name + sys.exit(2) + api.cmd[name]() -- cgit From afdbc42b2e721012daf7020430353c0686fcc5c3 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 11 Aug 2008 21:38:30 +0000 Subject: 112: More work on cli.py --- ipalib/cli.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 63988337..e0ba11f8 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -24,6 +24,7 @@ Functionality for Command Line Inteface. import sys import re + def to_cli(name): """ Takes a Python identifier and transforms it into form suitable for the @@ -54,18 +55,23 @@ class CLI(object): for cmd in self.api.cmd: print to_cli(cmd.name) + def __contains__(self, key): + return from_cli(key) in self.api.cmd + + def __getitem__(self, key): + return self.api.cmd[from_cli(key)] + def run(self): if len(sys.argv) < 2: self.print_commands() print 'Usage: ipa COMMAND [OPTIONS]' sys.exit(2) - return - name= sys.argv[1] - if name == '_api_': - print_api() - sys.exit() - elif name not in api.cmd: - print_commands() - print 'ipa: ERROR: unknown command %r' % name + cmd = sys.argv[1] + if cmd not in self: + self.print_commands() + print 'ipa: ERROR: unknown command %r' % cmd sys.exit(2) - api.cmd[name]() + self.run_cmd(cmd) + + def run_cmd(self, cmd): + print self[cmd] -- cgit From bc4b26ffca7b48db70006c99ddb6084542b1df88 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 12 Aug 2008 02:03:47 +0000 Subject: 114: Fixed cmd.__get_options(); more work on CLI --- ipalib/cli.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index e0ba11f8..40edc890 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -21,8 +21,9 @@ Functionality for Command Line Inteface. """ -import sys import re +import sys +import optparse def to_cli(name): @@ -43,6 +44,10 @@ def from_cli(cli_name): return cli_name.replace('-', '_') +def _(arg): + return arg + + class CLI(object): def __init__(self, api): self.__api = api @@ -74,4 +79,14 @@ class CLI(object): self.run_cmd(cmd) def run_cmd(self, cmd): - print self[cmd] + (options, args) = self.build_parser(cmd) + print options + + def build_parser(self, cmd): + parser = optparse.OptionParser() + for option in self[cmd].options: + parser.add_option('--%s' % to_cli(option.name), + help=option.get_doc(_), + ) + + (options, args) parser.parse_args() -- cgit From 99d7638ff5c5cddb4f23d25ad13ef122476d5679 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 12 Aug 2008 16:49:23 +0000 Subject: 115: CLI now parses out kw args; cmd.__call__() now uses print_n_call() to give feedback on the calling --- ipalib/cli.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 40edc890..ad54d77a 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -76,17 +76,17 @@ class CLI(object): self.print_commands() print 'ipa: ERROR: unknown command %r' % cmd sys.exit(2) - self.run_cmd(cmd) - - def run_cmd(self, cmd): - (options, args) = self.build_parser(cmd) - print options - - def build_parser(self, cmd): - parser = optparse.OptionParser() - for option in self[cmd].options: - parser.add_option('--%s' % to_cli(option.name), - help=option.get_doc(_), - ) - - (options, args) parser.parse_args() + self.run_cmd(cmd, sys.argv[2:]) + + def run_cmd(self, cmd, args): + kw = dict(self.parse_kw(args)) + self[cmd](**kw) + + def parse_kw(self, args): + for arg in args: + m = re.match(r'^--([a-z][-a-z0-9]*)=(.+)$', arg) + if m is not None: + yield ( + from_cli(m.group(1)), + m.group(2), + ) -- cgit From 64054a673c23b543450741fa11333bc627efeca3 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 12 Aug 2008 23:33:02 +0000 Subject: 122: The dictorary interface to CLI now has keys build using to_cli(), rather than converting at each call --- ipalib/cli.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index ad54d77a..a0b8800f 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -49,6 +49,8 @@ def _(arg): class CLI(object): + __d = None + def __init__(self, api): self.__api = api @@ -61,12 +63,23 @@ class CLI(object): print to_cli(cmd.name) def __contains__(self, key): - return from_cli(key) in self.api.cmd + assert self.__d is not None, 'you must call finalize() first' + return key in self.__d def __getitem__(self, key): - return self.api.cmd[from_cli(key)] + assert self.__d is not None, 'you must call finalize() first' + return self.__d[key] + + def finalize(self): + api = self.api + api.finalize() + def d_iter(): + for cmd in api.cmd: + yield (to_cli(cmd.name), cmd) + self.__d = dict(d_iter()) def run(self): + self.finalize() if len(sys.argv) < 2: self.print_commands() print 'Usage: ipa COMMAND [OPTIONS]' -- cgit From 0b5efa2a62623e09c7e8e5e97e0feafbc5e19823 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 13 Aug 2008 01:52:17 +0000 Subject: 134: Added CLI.mcl (Max Command Length) property; added corresponding unit tests --- ipalib/cli.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index a0b8800f..898f385b 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -50,6 +50,7 @@ def _(arg): class CLI(object): __d = None + __mcl = None def __init__(self, api): self.__api = api @@ -103,3 +104,14 @@ class CLI(object): from_cli(m.group(1)), m.group(2), ) + + def __get_mcl(self): + """ + Returns the Max Command Length. + """ + if self.__mcl is None: + if self.__d is None: + return None + self.__mcl = max(len(k) for k in self.__d) + return self.__mcl + mcl = property(__get_mcl) -- cgit From 05cefc2af69ceb0df6b03c8e3cae4510024f1d02 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 13 Aug 2008 02:10:09 +0000 Subject: 136: CLI.print_commands() now prints cmd.get_doc() as well --- ipalib/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 898f385b..f7b19801 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -61,7 +61,10 @@ class CLI(object): def print_commands(self): for cmd in self.api.cmd: - print to_cli(cmd.name) + print ' %s %s' % ( + to_cli(cmd.name).ljust(self.mcl), + cmd.get_doc(_), + ) def __contains__(self, key): assert self.__d is not None, 'you must call finalize() first' -- cgit From 0fed74b56d1940f84e7b64c3661f21baabcb4616 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 13 Aug 2008 02:34:36 +0000 Subject: 138: Added ProxyTarget.doc property; CLI.print_commands() now uses cmd.doc instead of cmd.get_doc() --- ipalib/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index f7b19801..7ae8ae3b 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -63,7 +63,7 @@ class CLI(object): for cmd in self.api.cmd: print ' %s %s' % ( to_cli(cmd.name).ljust(self.mcl), - cmd.get_doc(_), + cmd.doc, ) def __contains__(self, key): -- cgit From b9fa9dc2403d979f59a9963635392bb895bb8138 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 13 Aug 2008 03:15:00 +0000 Subject: 139: Removed dummy gettext _() func from cli.py; improved CLI.print_commands() --- ipalib/cli.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 7ae8ae3b..d6b2e7c1 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -23,7 +23,7 @@ Functionality for Command Line Inteface. import re import sys -import optparse +import public def to_cli(name): @@ -44,10 +44,6 @@ def from_cli(cli_name): return cli_name.replace('-', '_') -def _(arg): - return arg - - class CLI(object): __d = None __mcl = None @@ -60,8 +56,9 @@ class CLI(object): api = property(__get_api) def print_commands(self): + print 'Available Commands:' for cmd in self.api.cmd: - print ' %s %s' % ( + print ' %s %s' % ( to_cli(cmd.name).ljust(self.mcl), cmd.doc, ) -- cgit From 14cdb57b507c7af188551be23044a905218ab120 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 13 Aug 2008 04:02:39 +0000 Subject: 140: Added a skeleton help command in cli.py --- ipalib/cli.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index d6b2e7c1..6f0305d6 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -44,6 +44,10 @@ def from_cli(cli_name): return cli_name.replace('-', '_') +class help(public.cmd): + 'display help on command' + + class CLI(object): __d = None __mcl = None @@ -73,6 +77,7 @@ class CLI(object): def finalize(self): api = self.api + api.register(help) api.finalize() def d_iter(): for cmd in api.cmd: -- cgit From 337c9964d42066368460da9a7c0d770142e2d1c3 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 13 Aug 2008 06:25:42 +0000 Subject: 145: Added new CLI.parse() method; added corresponding unit tests --- ipalib/cli.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 6f0305d6..bf96d369 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -97,7 +97,9 @@ class CLI(object): sys.exit(2) self.run_cmd(cmd, sys.argv[2:]) - def run_cmd(self, cmd, args): + def run_cmd(self, cmd, given): + print self.parse(given) + sys.exit(0) kw = dict(self.parse_kw(args)) self[cmd](**kw) @@ -110,6 +112,18 @@ class CLI(object): m.group(2), ) + def parse(self, given): + args = [] + kw = {} + for g in given: + m = re.match(r'^--([a-z][-a-z0-9]*)=(.+)$', g) + if m: + kw[from_cli(m.group(1))] = m.group(2) + else: + args.append(g) + return (args, kw) + + def __get_mcl(self): """ Returns the Max Command Length. -- cgit From d422ef1134d81123b059574d13060811534e1d0d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 13 Aug 2008 06:40:25 +0000 Subject: 146: Removed CLI.parse_kw() method and corresponding unit tests --- ipalib/cli.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index bf96d369..d0cf1017 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -98,19 +98,8 @@ class CLI(object): self.run_cmd(cmd, sys.argv[2:]) def run_cmd(self, cmd, given): - print self.parse(given) - sys.exit(0) - kw = dict(self.parse_kw(args)) - self[cmd](**kw) - - def parse_kw(self, args): - for arg in args: - m = re.match(r'^--([a-z][-a-z0-9]*)=(.+)$', arg) - if m is not None: - yield ( - from_cli(m.group(1)), - m.group(2), - ) + (args, kw) = self.parse(given) + self[cmd](*args, **kw) def parse(self, given): args = [] @@ -123,7 +112,6 @@ class CLI(object): args.append(g) return (args, kw) - def __get_mcl(self): """ Returns the Max Command Length. -- cgit From fe7440735d8527b281f642056f11579b428dce0f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 13 Aug 2008 07:20:10 +0000 Subject: 148: Added some basic out put for cli.help.__call__() method --- ipalib/cli.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index d0cf1017..d2583f57 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -46,6 +46,11 @@ def from_cli(cli_name): class help(public.cmd): 'display help on command' + def __call__(self, key): + if from_cli(key) not in self.api.cmd: + print 'help: no such command %r' % key + sys.exit(2) + print 'Help on command %r:' % key class CLI(object): -- cgit From d95133b66f64a2e4f1c8aafa5ff9183c6acfe9a7 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 01:09:11 +0000 Subject: 149: CLI.run() now does an arg.decode('utf-8') for args in sys.argv so that non-ascii characters work --- ipalib/cli.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index d2583f57..d17d12bc 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -40,8 +40,7 @@ def from_cli(cli_name): Takes a string from the Command Line Interface and transforms it into a Python identifier. """ - assert isinstance(cli_name, basestring) - return cli_name.replace('-', '_') + return str(cli_name).replace('-', '_') class help(public.cmd): @@ -100,7 +99,7 @@ class CLI(object): self.print_commands() print 'ipa: ERROR: unknown command %r' % cmd sys.exit(2) - self.run_cmd(cmd, sys.argv[2:]) + self.run_cmd(cmd, (s.decode('utf-8') for s in sys.argv[2:])) def run_cmd(self, cmd, given): (args, kw) = self.parse(given) -- cgit From ba8d32a110f3dc96b091df9a2520f60c99ac26ba Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 05:46:20 +0000 Subject: 150: NameSpace.__iter__() now iterates through the names, not the members; added NameSpace.__call__() method which iterates through the members; NameSpace no longer requires members to be Proxy instances; updated unit tests and affected code; cleaned up NameSpace docstrings and switch to epydoc param docstrings --- ipalib/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index d17d12bc..8f09e90c 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -65,7 +65,7 @@ class CLI(object): def print_commands(self): print 'Available Commands:' - for cmd in self.api.cmd: + for cmd in self.api.cmd(): print ' %s %s' % ( to_cli(cmd.name).ljust(self.mcl), cmd.doc, @@ -84,7 +84,7 @@ class CLI(object): api.register(help) api.finalize() def d_iter(): - for cmd in api.cmd: + for cmd in api.cmd(): yield (to_cli(cmd.name), cmd) self.__d = dict(d_iter()) -- cgit From b0ec8fe551bc5f454aa1babeab31a424fd8c9abe Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 15 Aug 2008 19:49:04 +0000 Subject: 182: Renamed plublic.cmd base class to Command --- ipalib/cli.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 8f09e90c..1ad53058 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -43,10 +43,10 @@ def from_cli(cli_name): return str(cli_name).replace('-', '_') -class help(public.cmd): +class help(public.Command): 'display help on command' def __call__(self, key): - if from_cli(key) not in self.api.cmd: + if from_cli(key) not in self.api.Command: print 'help: no such command %r' % key sys.exit(2) print 'Help on command %r:' % key @@ -65,7 +65,7 @@ class CLI(object): def print_commands(self): print 'Available Commands:' - for cmd in self.api.cmd(): + for cmd in self.api.Command(): print ' %s %s' % ( to_cli(cmd.name).ljust(self.mcl), cmd.doc, @@ -84,7 +84,7 @@ class CLI(object): api.register(help) api.finalize() def d_iter(): - for cmd in api.cmd(): + for cmd in api.Command(): yield (to_cli(cmd.name), cmd) self.__d = dict(d_iter()) -- cgit From 330c17730c056428c70bd4612799cac44306f513 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 27 Aug 2008 00:25:33 +0000 Subject: 201: Added new cli command 'console' that starts a custom interactive Python console --- ipalib/cli.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 1ad53058..989c24f6 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -23,6 +23,7 @@ Functionality for Command Line Inteface. import re import sys +import code import public @@ -44,7 +45,7 @@ def from_cli(cli_name): class help(public.Command): - 'display help on command' + 'Display help on command' def __call__(self, key): if from_cli(key) not in self.api.Command: print 'help: no such command %r' % key @@ -52,6 +53,16 @@ class help(public.Command): print 'Help on command %r:' % key +class console(public.Command): + 'Start IPA Interactive Python Console' + + def __call__(self): + code.interact( + '(Custom IPA Interactive Python Console)', + local=dict(api=self.api) + ) + + class CLI(object): __d = None __mcl = None @@ -82,6 +93,7 @@ class CLI(object): def finalize(self): api = self.api api.register(help) + api.register(console) api.finalize() def d_iter(): for cmd in api.Command(): -- cgit From ab81ca56fd336af4b83ef19a6f97dffe0b1a0923 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 4 Sep 2008 04:39:01 +0000 Subject: 255: CLI help, console commands now subclass from public.Application; other tweeking to make CLI utilize Application --- ipalib/cli.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 989c24f6..e1cbfa78 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -44,7 +44,7 @@ def from_cli(cli_name): return str(cli_name).replace('-', '_') -class help(public.Command): +class help(public.Application): 'Display help on command' def __call__(self, key): if from_cli(key) not in self.api.Command: @@ -53,7 +53,7 @@ class help(public.Command): print 'Help on command %r:' % key -class console(public.Command): +class console(public.Application): 'Start IPA Interactive Python Console' def __call__(self): @@ -95,10 +95,16 @@ class CLI(object): api.register(help) api.register(console) api.finalize() - def d_iter(): - for cmd in api.Command(): - yield (to_cli(cmd.name), cmd) - self.__d = dict(d_iter()) + for a in api.Application(): + 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() -- cgit From b16deabdffd19dcc6f85f3c1f03074484669912c Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 4 Sep 2008 05:18:14 +0000 Subject: 256: Fixed cli.help plugin so it looks up commands in CLI instead of API --- ipalib/cli.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index e1cbfa78..5747fd04 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -45,20 +45,25 @@ def from_cli(cli_name): class help(public.Application): - 'Display help on command' + 'Display help on command.' def __call__(self, key): - if from_cli(key) not in self.api.Command: + key = str(key) + if key not in self.application: print 'help: no such command %r' % key sys.exit(2) - print 'Help on command %r:' % key + cmd = self.application[key] + print 'Purpose: %s' % cmd.doc + if len(cmd.Option) > 0: + print '\nOptions:' + print '' class console(public.Application): - 'Start IPA Interactive Python Console' + 'Start the IPA Python console.' def __call__(self): code.interact( - '(Custom IPA Interactive Python Console)', + '(Custom IPA interactive Python console)', local=dict(api=self.api) ) -- cgit From 01b73e6910cbadd1867256e71fb982209669a8da Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 4 Sep 2008 06:33:57 +0000 Subject: 257: Improved help command, now parsing options with optparse --- ipalib/cli.py | 74 ++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 23 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 5747fd04..3cecace7 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -24,6 +24,7 @@ Functionality for Command Line Inteface. import re import sys import code +import optparse import public @@ -53,9 +54,7 @@ class help(public.Application): sys.exit(2) cmd = self.application[key] print 'Purpose: %s' % cmd.doc - if len(cmd.Option) > 0: - print '\nOptions:' - print '' + self.application.build_parser(cmd).print_help() class console(public.Application): @@ -68,6 +67,25 @@ class console(public.Application): ) +class KWCollector(object): + def __init__(self): + object.__setattr__(self, '_KWCollector__d', {}) + + def __setattr__(self, name, value): + if name in self.__d: + v = self.__d[name] + if type(v) is tuple: + value = v + (value,) + else: + value = (v, value) + self.__d[name] = value + object.__setattr__(self, name, value) + + def __todict__(self): + return dict(self.__d) + + + class CLI(object): __d = None __mcl = None @@ -115,29 +133,39 @@ class CLI(object): self.finalize() if len(sys.argv) < 2: self.print_commands() - print 'Usage: ipa COMMAND [OPTIONS]' + print 'Usage: ipa COMMAND [ARGS]' sys.exit(2) - cmd = sys.argv[1] - if cmd not in self: + key = sys.argv[1] + if key not in self: self.print_commands() - print 'ipa: ERROR: unknown command %r' % cmd + print 'ipa: ERROR: unknown command %r' % key sys.exit(2) - self.run_cmd(cmd, (s.decode('utf-8') for s in sys.argv[2:])) - - def run_cmd(self, cmd, given): - (args, kw) = self.parse(given) - self[cmd](*args, **kw) - - def parse(self, given): - args = [] - kw = {} - for g in given: - m = re.match(r'^--([a-z][-a-z0-9]*)=(.+)$', g) - if m: - kw[from_cli(m.group(1))] = m.group(2) - else: - args.append(g) - return (args, kw) + self.run_cmd( + self[key], + list(s.decode('utf-8') for s in sys.argv[2:]) + ) + + def run_cmd(self, cmd, argv): + (args, kw) = self.parse(cmd, argv) + cmd(*args, **kw) + + def parse(self, cmd, argv): + parser = self.build_parser(cmd) + (kwc, args) = parser.parse_args(argv, KWCollector()) + return (args, kwc.__todict__()) + + def build_parser(self, cmd): + parser = optparse.OptionParser( + usage='Usage: %%prog %s' % to_cli(cmd.name), + ) + for option in cmd.Option(): + parser.add_option('--%s' % to_cli(option.name), + metavar=option.type.name.upper(), + help=option.doc, + ) + return parser + + def __get_mcl(self): """ -- cgit From 71d36aa6a0b9627ae818d116c7240197a62cff74 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 4 Sep 2008 07:18:26 +0000 Subject: 258: Added some experimental features for interactively prompting for values --- ipalib/cli.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 3cecace7..a495924e 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -147,8 +147,32 @@ class CLI(object): def run_cmd(self, cmd, argv): (args, kw) = self.parse(cmd, argv) + self.run_interactive(cmd, args, kw) + + def run_interactive(self, cmd, args, kw): + for option in cmd.smart_option_order(): + if option.name not in kw: + default = option.get_default(**kw) + if default is None: + prompt = '%s: ' % option.name + else: + prompt = '%s [%s]: ' % (option.name, default) + error = None + while True: + if error is not None: + print '>>> %s: %s' % (option.name, error) + value = raw_input(prompt) + if default is not None and len(value) == 0: + value = default + if len(value) == 0: + error = 'Must supply a value' + else: + kw[option.name] = value + break cmd(*args, **kw) + + def parse(self, cmd, argv): parser = self.build_parser(cmd) (kwc, args) = parser.parse_args(argv, KWCollector()) -- cgit From 915486dadc476df4915cefdfeb8d61c43664ca60 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 4 Sep 2008 08:16:12 +0000 Subject: 260: Option.normalize() now does same conversion for multivalue as Option.convert() does --- ipalib/cli.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index a495924e..d199f721 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -26,6 +26,7 @@ import sys import code import optparse import public +import errors def to_cli(name): @@ -161,14 +162,14 @@ class CLI(object): while True: if error is not None: print '>>> %s: %s' % (option.name, error) - value = raw_input(prompt) - if default is not None and len(value) == 0: - value = default - if len(value) == 0: - error = 'Must supply a value' - else: - kw[option.name] = value + raw = raw_input(prompt) + try: + value = option(raw) + if value is not None: + kw[option.name] = value break + except errors.ValidationError, e: + error = e.error cmd(*args, **kw) -- cgit From 6f95249d5274258d0935a439407f38030205bf65 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 4 Sep 2008 08:33:41 +0000 Subject: 261: More work on demo using Option.__call__() for interactive input --- ipalib/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index d199f721..abaef030 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -164,7 +164,7 @@ class CLI(object): print '>>> %s: %s' % (option.name, error) raw = raw_input(prompt) try: - value = option(raw) + value = option(raw, **kw) if value is not None: kw[option.name] = value break -- cgit From 6b9ba734e119cbdc92ebc0a1b28d75b405d46bb0 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 4 Sep 2008 09:04:35 +0000 Subject: 263: CLI.print_commands() now seperates Command subclasses from Application subclasses --- ipalib/cli.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index abaef030..7ab0ae8d 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -99,12 +99,22 @@ class CLI(object): api = property(__get_api) def print_commands(self): - print 'Available Commands:' - for cmd in self.api.Command(): - print ' %s %s' % ( - to_cli(cmd.name).ljust(self.mcl), - cmd.doc, - ) + 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) + + def print_cmd(self, cmd): + print ' %s %s' % ( + to_cli(cmd.name).ljust(self.mcl), + cmd.doc, + ) + + def __contains__(self, key): assert self.__d is not None, 'you must call finalize() first' @@ -134,7 +144,7 @@ class CLI(object): self.finalize() if len(sys.argv) < 2: self.print_commands() - print 'Usage: ipa COMMAND [ARGS]' + print '\nUsage: ipa COMMAND' sys.exit(2) key = sys.argv[1] if key not in self: -- cgit From 553b0c596d9dc1a955aece1fab28bd0cf3c81119 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 4 Sep 2008 09:22:18 +0000 Subject: 264: Cleaned up docstrings on all example plugins --- ipalib/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 7ab0ae8d..25fbec02 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -47,7 +47,7 @@ def from_cli(cli_name): class help(public.Application): - 'Display help on command.' + 'Display help on a command.' def __call__(self, key): key = str(key) if key not in self.application: @@ -59,7 +59,7 @@ class help(public.Application): class console(public.Application): - 'Start the IPA Python console.' + 'Start the IPA interactive Python console.' def __call__(self): code.interact( -- cgit From 22d9b8c078844127b8ea8217a9a9ed8f172afb99 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 4 Sep 2008 09:27:28 +0000 Subject: 265: Fixed small formatting error with use of CLI.print_commands() --- ipalib/cli.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 25fbec02..5ead9837 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -107,6 +107,7 @@ class CLI(object): print '\nSpecial CLI commands:' for cmd in self.api.Application(): self.print_cmd(cmd) + print '' def print_cmd(self, cmd): print ' %s %s' % ( @@ -114,8 +115,6 @@ class CLI(object): cmd.doc, ) - - def __contains__(self, key): assert self.__d is not None, 'you must call finalize() first' return key in self.__d @@ -139,12 +138,11 @@ class CLI(object): (c.name.replace('_', '-'), c) for c in self.api.Command() ) - def run(self): self.finalize() if len(sys.argv) < 2: self.print_commands() - print '\nUsage: ipa COMMAND' + print 'Usage: ipa COMMAND' sys.exit(2) key = sys.argv[1] if key not in self: -- cgit From 641403278e00c30f24d9a6b4938b1e4ab3ecb427 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 4 Sep 2008 18:35:04 +0000 Subject: 266: Started work on new cli.print_api Command --- ipalib/cli.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 5ead9837..05acad92 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -67,6 +67,9 @@ class console(public.Application): local=dict(api=self.api) ) +class print_api(public.Application): + 'Print details on the loaded plugins.' + class KWCollector(object): def __init__(self): @@ -127,6 +130,7 @@ class CLI(object): api = self.api api.register(help) api.register(console) + api.register(print_api) api.finalize() for a in api.Application(): a.set_application(self) -- cgit From e74713a076a72e75d6ca44d12df8500fb5cad8d2 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 8 Sep 2008 21:37:02 +0000 Subject: 267: Finished builtin CLI api command --- ipalib/cli.py | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 05acad92..e4de6031 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -27,6 +27,7 @@ import code import optparse import public import errors +import plugable def to_cli(name): @@ -70,6 +71,37 @@ class console(public.Application): class print_api(public.Application): 'Print details on the loaded plugins.' + def __call__(self): + lines = self.__traverse() + ml = max(len(l[1]) for l in lines) + for line in lines: + if line[0] == 0: + print '' + print '%s%s %r' % ( + ' ' * line[0], + line[1].ljust(ml), + line[2], + ) + + def __traverse(self): + lines = [] + for name in self.api: + namespace = self.api[name] + self.__traverse_namespace(name, namespace, lines) + return lines + + def __traverse_namespace(self, name, namespace, lines, tab=0): + lines.append((tab, name, namespace)) + for member_name in namespace: + member = namespace[member_name] + lines.append((tab + 1, member_name, member)) + if not hasattr(member, '__iter__'): + continue + for n in member: + attr = member[n] + if isinstance(attr, plugable.NameSpace): + self.__traverse_namespace(n, attr, lines, tab + 2) + class KWCollector(object): def __init__(self): @@ -89,7 +121,6 @@ class KWCollector(object): return dict(self.__d) - class CLI(object): __d = None __mcl = None @@ -184,8 +215,6 @@ class CLI(object): error = e.error cmd(*args, **kw) - - def parse(self, cmd, argv): parser = self.build_parser(cmd) (kwc, args) = parser.parse_args(argv, KWCollector()) @@ -202,8 +231,6 @@ class CLI(object): ) return parser - - def __get_mcl(self): """ Returns the Max Command Length. -- cgit From 03fd184e8e7459ac2a0c01c79259054f44721ca2 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 8 Sep 2008 21:42:48 +0000 Subject: 269: Renamed print_api command to show_plugins --- ipalib/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index e4de6031..db3e0137 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -68,7 +68,7 @@ class console(public.Application): local=dict(api=self.api) ) -class print_api(public.Application): +class show_plugins(public.Application): 'Print details on the loaded plugins.' def __call__(self): @@ -161,7 +161,7 @@ class CLI(object): api = self.api api.register(help) api.register(console) - api.register(print_api) + api.register(show_plugins) api.finalize() for a in api.Application(): a.set_application(self) -- cgit From cb9c44270819c080f899fe7678f7f319686e681d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 8 Sep 2008 21:44:53 +0000 Subject: 270: show-plugins now only shows namespaces with at least one member --- ipalib/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index db3e0137..048f49ff 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -99,7 +99,7 @@ class show_plugins(public.Application): continue for n in member: attr = member[n] - if isinstance(attr, plugable.NameSpace): + if isinstance(attr, plugable.NameSpace) and len(attr) > 0: self.__traverse_namespace(n, attr, lines, tab + 2) -- cgit From 21a0bab79ec9cddb98d6d3ab478ea48674eeda06 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 9 Sep 2008 01:41:15 +0000 Subject: 272: Add a quick positional arg experiment --- ipalib/cli.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 048f49ff..b2251432 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -28,6 +28,7 @@ import optparse import public import errors import plugable +import ipa_types def to_cli(name): @@ -49,6 +50,14 @@ def from_cli(cli_name): class help(public.Application): 'Display help on a command.' + + takes_args = ( + public.Option('command', 'The doc', ipa_types.Unicode(), + required=True, + multivalue=True, + ), + ) + def __call__(self, key): key = str(key) if key not in self.application: @@ -222,7 +231,7 @@ class CLI(object): def build_parser(self, cmd): parser = optparse.OptionParser( - usage='Usage: %%prog %s' % to_cli(cmd.name), + usage=self.get_usage(cmd), ) for option in cmd.Option(): parser.add_option('--%s' % to_cli(option.name), @@ -231,6 +240,22 @@ class CLI(object): ) return parser + def get_usage(self, cmd): + return ' '.join(self.get_usage_iter(cmd)) + + def get_usage_iter(self, cmd): + yield 'Usage: %%prog %s' % to_cli(cmd.name) + for arg in cmd.takes_args: + name = to_cli(arg.name).upper() + if arg.multivalue: + name = '%s...' % name + if arg.required: + yield name + else: + yield '[%s]' % name + + + def __get_mcl(self): """ Returns the Max Command Length. -- cgit From 0215bc8009d7e10f884032e4dfa9cece73c14961 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 10 Sep 2008 00:21:40 +0000 Subject: 276: Option.__init__(): doc is now 3rd kwarg instead of 2nd positional arg; updated unit tests and other affected code --- ipalib/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index b2251432..a6bc0f1f 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -52,7 +52,7 @@ class help(public.Application): 'Display help on a command.' takes_args = ( - public.Option('command', 'The doc', ipa_types.Unicode(), + public.Option('command', ipa_types.Unicode(), required=True, multivalue=True, ), -- cgit From 8062075f847199157910114588ea3c27874bdf35 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 10 Sep 2008 02:02:26 +0000 Subject: 279: Fixed cli and public.Method re new Command.args attribute --- ipalib/cli.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index a6bc0f1f..25a0a5b8 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -51,12 +51,7 @@ def from_cli(cli_name): class help(public.Application): 'Display help on a command.' - takes_args = ( - public.Option('command', ipa_types.Unicode(), - required=True, - multivalue=True, - ), - ) + takes_args = ['command'] def __call__(self, key): key = str(key) @@ -245,7 +240,7 @@ class CLI(object): def get_usage_iter(self, cmd): yield 'Usage: %%prog %s' % to_cli(cmd.name) - for arg in cmd.takes_args: + for arg in cmd.args(): name = to_cli(arg.name).upper() if arg.multivalue: name = '%s...' % name @@ -254,8 +249,6 @@ class CLI(object): else: yield '[%s]' % name - - def __get_mcl(self): """ Returns the Max Command Length. -- cgit From 687f60356203a33b7af24842f24570a12d9b2039 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 10 Sep 2008 15:31:34 +0000 Subject: 284: Removed depreciated Command.Option property; removed corresponding unit tests; updated affected code --- ipalib/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 25a0a5b8..54693ffd 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -228,7 +228,7 @@ class CLI(object): parser = optparse.OptionParser( usage=self.get_usage(cmd), ) - for option in cmd.Option(): + for option in cmd.options(): parser.add_option('--%s' % to_cli(option.name), metavar=option.type.name.upper(), help=option.doc, -- cgit From 23e251a605c8e7cc44450411b841141cc0979638 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 10 Sep 2008 23:33:36 +0000 Subject: 288: CLI now uses Command.group_args() to check for required arguments --- ipalib/cli.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 54693ffd..594e2812 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -31,6 +31,10 @@ import plugable import ipa_types +def exit_error(error): + sys.exit('ipa: ERROR: %s' % error) + + def to_cli(name): """ Takes a Python identifier and transforms it into form suitable for the @@ -195,6 +199,10 @@ class CLI(object): def run_cmd(self, cmd, argv): (args, kw) = self.parse(cmd, argv) + try: + args = cmd.group_args(*args) + except errors.ArgumentError, e: + exit_error('%s %s' % (to_cli(cmd.name), e.error)) self.run_interactive(cmd, args, kw) def run_interactive(self, cmd, args, kw): -- cgit From 2d836140064154c461aec1b24ae8d774cbd12444 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 21 Sep 2008 19:00:41 +0000 Subject: 305: Ported cli.py to changes in public.py --- ipalib/cli.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 594e2812..b16fe6b5 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -198,39 +198,42 @@ class CLI(object): ) def run_cmd(self, cmd, argv): - (args, kw) = self.parse(cmd, argv) - try: - args = cmd.group_args(*args) - except errors.ArgumentError, e: - exit_error('%s %s' % (to_cli(cmd.name), e.error)) - self.run_interactive(cmd, args, kw) + kw = self.parse(cmd, argv) + self.run_interactive(cmd, kw) - def run_interactive(self, cmd, args, kw): - for option in cmd.smart_option_order(): - if option.name not in kw: - default = option.get_default(**kw) + def run_interactive(self, cmd, kw): + for param in cmd.params(): + if param.name not in kw: + default = param.get_default(**kw) if default is None: - prompt = '%s: ' % option.name + prompt = '%s: ' % param.name else: - prompt = '%s [%s]: ' % (option.name, default) + prompt = '%s [%s]: ' % (param.name, default) error = None while True: if error is not None: - print '>>> %s: %s' % (option.name, error) + print '>>> %s: %s' % (param.name, error) raw = raw_input(prompt) try: - value = option(raw, **kw) + value = param(raw, **kw) if value is not None: - kw[option.name] = value + kw[param.name] = value break except errors.ValidationError, e: error = e.error - cmd(*args, **kw) + cmd(**kw) def parse(self, cmd, argv): parser = self.build_parser(cmd) (kwc, args) = parser.parse_args(argv, KWCollector()) - return (args, kwc.__todict__()) + kw = kwc.__todict__() + try: + arg_kw = cmd.args_to_kw(*args) + except errors.ArgumentError, e: + exit_error('%s %s' % (to_cli(cmd.name), e.error)) + assert set(arg_kw).intersection(kw) == set() + kw.update(arg_kw) + return kw def build_parser(self, cmd): parser = optparse.OptionParser( -- cgit From 4e8ff5c65675fe7534afa02ce06d6ff73fd024c9 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 24 Sep 2008 00:01:29 +0000 Subject: 318: Renamed all references to 'public' module to 'frontend' --- ipalib/cli.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index b16fe6b5..92c0cbc3 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -18,14 +18,14 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Functionality for Command Line Inteface. +Functionality for Command Line Interface. """ import re import sys import code import optparse -import public +import frontend import errors import plugable import ipa_types @@ -52,7 +52,7 @@ def from_cli(cli_name): return str(cli_name).replace('-', '_') -class help(public.Application): +class help(frontend.Application): 'Display help on a command.' takes_args = ['command'] @@ -67,7 +67,7 @@ class help(public.Application): self.application.build_parser(cmd).print_help() -class console(public.Application): +class console(frontend.Application): 'Start the IPA interactive Python console.' def __call__(self): @@ -76,7 +76,7 @@ class console(public.Application): local=dict(api=self.api) ) -class show_plugins(public.Application): +class show_plugins(frontend.Application): 'Print details on the loaded plugins.' def __call__(self): -- cgit From 3bf2da571488b6f1ef27527fa3bff0133b44c2f5 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 24 Sep 2008 03:10:35 +0000 Subject: 324: Removed 'smart_option_order' from Command.__public__; cli commands help, console, and show_plugins now override Command.run() instead of Command.__call__() --- ipalib/cli.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 92c0cbc3..a76c08bc 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -57,7 +57,7 @@ class help(frontend.Application): takes_args = ['command'] - def __call__(self, key): + def run(self, key): key = str(key) if key not in self.application: print 'help: no such command %r' % key @@ -70,16 +70,17 @@ class help(frontend.Application): class console(frontend.Application): 'Start the IPA interactive Python console.' - def __call__(self): + def run(self): code.interact( '(Custom IPA interactive Python console)', local=dict(api=self.api) ) + class show_plugins(frontend.Application): 'Print details on the loaded plugins.' - def __call__(self): + def run(self): lines = self.__traverse() ml = max(len(l[1]) for l in lines) for line in lines: -- cgit From 3e70c3b56b29dcc9c0f6dd15eee7d4a24945944a Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 24 Sep 2008 04:44:52 +0000 Subject: 325: API.finalize() now creates instance attribtue 'plugins', which is a tuple of PluginInfo objects; renamed show_plugins cli command to namespaces; added new cli command plugins --- ipalib/cli.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index a76c08bc..7912f1b1 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -77,8 +77,8 @@ class console(frontend.Application): ) -class show_plugins(frontend.Application): - 'Print details on the loaded plugins.' +class namespaces(frontend.Application): + 'Show details of plugable namespaces' def run(self): lines = self.__traverse() @@ -112,6 +112,33 @@ class show_plugins(frontend.Application): self.__traverse_namespace(n, attr, lines, tab + 2) +class plugins(frontend.Application): + """Show all loaded plugins""" + + def run(self): + print '%s:\n' % self.name + for p in sorted(self.api.plugins, key=lambda o: o.plugin): + print ' plugin: %s' % p.plugin + print ' in namespaces: %s' % ', '.join(p.bases) + print '' + if len(self.api.plugins) == 1: + print '1 plugin loaded.' + else: + print '%d plugins loaded.' % len(self.api.plugins) + + + + + +cli_application_commands = ( + help, + console, + namespaces, + plugins, + +) + + class KWCollector(object): def __init__(self): object.__setattr__(self, '_KWCollector__d', {}) @@ -168,9 +195,8 @@ class CLI(object): def finalize(self): api = self.api - api.register(help) - api.register(console) - api.register(show_plugins) + for klass in cli_application_commands: + api.register(klass) api.finalize() for a in api.Application(): a.set_application(self) -- cgit From f3ac709922c33425c211b79787f1dedc03bb6508 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 24 Sep 2008 05:03:10 +0000 Subject: 326: Made output of plugins cli command nicer --- ipalib/cli.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 7912f1b1..36a5bd1b 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -52,6 +52,23 @@ def from_cli(cli_name): return str(cli_name).replace('-', '_') +class text_ui(frontend.Application): + """ + Base class for CLI commands with special output needs. + """ + + def print_dashed(self, string, top=True, bottom=True): + dashes = '-' * len(string) + if top: + print dashes + print string + if bottom: + print dashes + + def print_name(self, **kw): + self.print_dashed('%s:' % self.name, **kw) + + class help(frontend.Application): 'Display help on a command.' @@ -112,19 +129,24 @@ class namespaces(frontend.Application): self.__traverse_namespace(n, attr, lines, tab + 2) -class plugins(frontend.Application): +class plugins(text_ui): """Show all loaded plugins""" def run(self): - print '%s:\n' % self.name + self.print_name() + first = True for p in sorted(self.api.plugins, key=lambda o: o.plugin): + if first: + first = False + else: + print '' print ' plugin: %s' % p.plugin print ' in namespaces: %s' % ', '.join(p.bases) - print '' if len(self.api.plugins) == 1: - print '1 plugin loaded.' + s = '1 plugin loaded.' else: - print '%d plugins loaded.' % len(self.api.plugins) + s = '%d plugins loaded.' % len(self.api.plugins) + self.print_dashed(s) -- cgit From eaf15d5a52b8438d1a0a5b59a9ace9660a703dce Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 24 Sep 2008 05:35:40 +0000 Subject: 327: Improved formatting on show-api cli command --- ipalib/cli.py | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 36a5bd1b..8918206f 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -94,26 +94,46 @@ class console(frontend.Application): ) -class namespaces(frontend.Application): - 'Show details of plugable namespaces' - def run(self): - lines = self.__traverse() +class show_api(text_ui): + 'Show attributes on dynamic API object' + + takes_args = ('namespaces*',) + + def run(self, namespaces): + if namespaces is None: + names = tuple(self.api) + else: + for name in namespaces: + if name not in self.api: + exit_error('api has no such namespace: %s' % name) + names = namespaces + lines = self.__traverse(names) ml = max(len(l[1]) for l in lines) + self.print_name() + first = True for line in lines: - if line[0] == 0: + if line[0] == 0 and not first: print '' + if first: + first = False print '%s%s %r' % ( ' ' * line[0], line[1].ljust(ml), line[2], ) + if len(lines) == 1: + s = '1 attribute shown.' + else: + s = '%d attributes show.' % len(lines) + self.print_dashed(s) + - def __traverse(self): + def __traverse(self, names): lines = [] - for name in self.api: + for name in names: namespace = self.api[name] - self.__traverse_namespace(name, namespace, lines) + self.__traverse_namespace('%s' % name, namespace, lines) return lines def __traverse_namespace(self, name, namespace, lines, tab=0): @@ -155,7 +175,7 @@ class plugins(text_ui): cli_application_commands = ( help, console, - namespaces, + show_api, plugins, ) @@ -253,6 +273,8 @@ class CLI(object): def run_interactive(self, cmd, kw): for param in cmd.params(): if param.name not in kw: + if not param.required: + continue default = param.get_default(**kw) if default is None: prompt = '%s: ' % param.name -- cgit From afdc72103847fc27efd00f8cc97a7320909ff6a0 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Mon, 29 Sep 2008 17:41:30 +0200 Subject: Add support for environment variables, change tests accordingly --- ipalib/cli.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 8918206f..1a08cef4 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -30,6 +30,7 @@ import errors import plugable import ipa_types +from ipalib import config def exit_error(error): sys.exit('ipa: ERROR: %s' % error) @@ -256,6 +257,9 @@ class CLI(object): self.print_commands() print 'Usage: ipa COMMAND' sys.exit(2) + # do parsing here, read the conf + conf_dict = config.read_config(self.api.env.conf) + self.api.env.update(conf_dict) key = sys.argv[1] if key not in self: self.print_commands() -- cgit From 149429f3057e3ae934e660e3276c9e8d3c935d17 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Thu, 2 Oct 2008 20:24:05 +0200 Subject: Environment is now subclassed from object, rather then dict. Added tests for Environment and config.py --- ipalib/cli.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 1a08cef4..d66e1e2e 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -29,8 +29,7 @@ import frontend import errors import plugable import ipa_types - -from ipalib import config +import config def exit_error(error): sys.exit('ipa: ERROR: %s' % error) @@ -257,9 +256,7 @@ class CLI(object): self.print_commands() print 'Usage: ipa COMMAND' sys.exit(2) - # do parsing here, read the conf - conf_dict = config.read_config(self.api.env.conf) - self.api.env.update(conf_dict) + self.api.env.update(config.generate_env()) key = sys.argv[1] if key not in self: self.print_commands() -- cgit From 4a68c719f03c176bc63a96007c089d0ac7ae5fc1 Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Fri, 3 Oct 2008 17:08:37 +0200 Subject: Implement config file reading --- ipalib/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index d66e1e2e..fc85dcb0 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -256,7 +256,8 @@ class CLI(object): self.print_commands() print 'Usage: ipa COMMAND' sys.exit(2) - self.api.env.update(config.generate_env()) + env_dict = config.read_config() + self.api.env.update(config.generate_env(env_dict)) key = sys.argv[1] if key not in self: self.print_commands() -- cgit From 4a1c4a3fe3a568c98b6bab1456993c4163721c5d Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Fri, 3 Oct 2008 22:13:50 +0200 Subject: Implement argument parsing for the CLI --- ipalib/cli.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 8 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index fc85dcb0..aae4e31c 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -205,6 +205,9 @@ class CLI(object): def __init__(self, api): self.__api = api + self.__all_interactive = False + self.__not_interactive = False + self.__config = None def __get_api(self): return self.__api @@ -219,6 +222,7 @@ class CLI(object): 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): @@ -252,20 +256,21 @@ class CLI(object): def run(self): self.finalize() - if len(sys.argv) < 2: + (args, env_dict) = self.parse_globals() + env_dict.update(config.read_config(self.__config)) + self.api.env.update(config.generate_env(env_dict)) + if len(args) < 1: self.print_commands() - print 'Usage: ipa COMMAND' + print 'Usage: ipa [global-options] COMMAND' sys.exit(2) - env_dict = config.read_config() - self.api.env.update(config.generate_env(env_dict)) - key = sys.argv[1] + key = args[0] if key not in self: self.print_commands() print 'ipa: ERROR: unknown command %r' % key sys.exit(2) self.run_cmd( self[key], - list(s.decode('utf-8') for s in sys.argv[2:]) + list(s.decode('utf-8') for s in args[1:]) ) def run_cmd(self, cmd, argv): @@ -276,7 +281,10 @@ class CLI(object): for param in cmd.params(): if param.name not in kw: if not param.required: - continue + if not self.__all_interactive: + continue + elif self.__not_interactive: + exit_error('Not enough arguments given') default = param.get_default(**kw) if default is None: prompt = '%s: ' % param.name @@ -319,11 +327,46 @@ class CLI(object): ) return parser + def parse_globals(self, argv=sys.argv[1:]): + env_dict = {} + parser = optparse.OptionParser() + parser.disable_interspersed_args() + parser.add_option('-a', dest='interactive', 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') + (options, args) = parser.parse_args(argv) + + if options.interactive == True: + self.__all_interactive = True + elif options.interactive == False: + self.__not_interactive = True + if options.config_file: + self.__config = options.config_file + if options.environment: + for a in options.environment.split(','): + a = a.split('=', 1) + if len(a) < 2: + parser.error('badly specified environment string,'\ + 'use var1=val1[,var2=val2]..') + env_dict[a[0].strip()] = a[1].strip() + if options.verbose != None: + env_dict.update(verbose=True) + + return (args, env_dict) + + def get_usage(self, cmd): return ' '.join(self.get_usage_iter(cmd)) def get_usage_iter(self, cmd): - yield 'Usage: %%prog %s' % to_cli(cmd.name) + yield 'Usage: %%prog [global-options] %s' % to_cli(cmd.name) for arg in cmd.args(): name = to_cli(arg.name).upper() if arg.multivalue: -- cgit From b6dcd183a66ca6056f9d23637de0f12aee15efcc Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 13 Oct 2008 20:31:10 -0600 Subject: CLI now maps Param.cli_name to Param.name --- ipalib/cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index aae4e31c..378cc4c1 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -321,7 +321,8 @@ class CLI(object): usage=self.get_usage(cmd), ) for option in cmd.options(): - parser.add_option('--%s' % to_cli(option.name), + parser.add_option('--%s' % to_cli(option.cli_name), + dest=option.name, metavar=option.type.name.upper(), help=option.doc, ) @@ -368,7 +369,7 @@ class CLI(object): def get_usage_iter(self, cmd): yield 'Usage: %%prog [global-options] %s' % to_cli(cmd.name) for arg in cmd.args(): - name = to_cli(arg.name).upper() + name = to_cli(arg.cli_name).upper() if arg.multivalue: name = '%s...' % name if arg.required: -- cgit From 22669f1fc2ffb6de8a8d92a64132dd0b31e877b3 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 13 Oct 2008 22:00:18 -0600 Subject: CLI.run_interactive() now uses Param.cli_name instead of Param.name for prompts and errors --- ipalib/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 378cc4c1..5bebc88d 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -287,13 +287,13 @@ class CLI(object): exit_error('Not enough arguments given') default = param.get_default(**kw) if default is None: - prompt = '%s: ' % param.name + prompt = '%s: ' % param.cli_name else: - prompt = '%s [%s]: ' % (param.name, default) + prompt = '%s [%s]: ' % (param.cli_name, default) error = None while True: if error is not None: - print '>>> %s: %s' % (param.name, error) + print '>>> %s: %s' % (param.cli_name, error) raw = raw_input(prompt) try: value = param(raw, **kw) -- cgit From 1480224724864cb7cf34c9be755b905c61f885b9 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 14 Oct 2008 01:45:30 -0600 Subject: Started roughing out user_add() using api.Backend.ldap; added Command.output_for_cli() to take care of formatting print output --- ipalib/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 5bebc88d..5dd2c44f 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -302,7 +302,7 @@ class CLI(object): break except errors.ValidationError, e: error = e.error - cmd(**kw) + cmd.output_for_cli(cmd(**kw)) def parse(self, cmd, argv): parser = self.build_parser(cmd) -- cgit From 3a80297b04d6fbfd2367ec76c5651d20293adccc Mon Sep 17 00:00:00 2001 From: Martin Nagy Date: Fri, 17 Oct 2008 22:55:03 +0200 Subject: Reworking Environment, moved it to config.py --- ipalib/cli.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 5dd2c44f..07956e0a 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -25,11 +25,12 @@ import re import sys import code import optparse + import frontend import errors import plugable import ipa_types -import config +from config import set_default_env, read_config def exit_error(error): sys.exit('ipa: ERROR: %s' % error) @@ -207,7 +208,6 @@ class CLI(object): self.__api = api self.__all_interactive = False self.__not_interactive = False - self.__config = None def __get_api(self): return self.__api @@ -256,9 +256,8 @@ class CLI(object): def run(self): self.finalize() - (args, env_dict) = self.parse_globals() - env_dict.update(config.read_config(self.__config)) - self.api.env.update(config.generate_env(env_dict)) + set_default_env(self.api.env) + args = self.parse_globals() if len(args) < 1: self.print_commands() print 'Usage: ipa [global-options] COMMAND' @@ -329,7 +328,6 @@ class CLI(object): return parser def parse_globals(self, argv=sys.argv[1:]): - env_dict = {} parser = optparse.OptionParser() parser.disable_interspersed_args() parser.add_option('-a', dest='interactive', action='store_true', @@ -348,20 +346,23 @@ class CLI(object): self.__all_interactive = True elif options.interactive == False: self.__not_interactive = True - if options.config_file: - self.__config = options.config_file + if options.verbose != None: + self.api.env.verbose = True if options.environment: + env_dict = {} for a in options.environment.split(','): a = a.split('=', 1) if len(a) < 2: parser.error('badly specified environment string,'\ 'use var1=val1[,var2=val2]..') env_dict[a[0].strip()] = a[1].strip() - if options.verbose != None: - env_dict.update(verbose=True) - - return (args, env_dict) + self.api.env.update(env_dict, True) + if options.config_file: + self.api.env.update(read_config(options.config_file), True) + else: + self.api.env.update(read_config(), True) + return args def get_usage(self, cmd): return ' '.join(self.get_usage_iter(cmd)) -- cgit From 721982870ed6dd5507a634d09dd06309abc3778a Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 17 Oct 2008 21:05:03 -0600 Subject: Removed generic Command.output_for_cli() method; CLI.run_interactive() now only calls output_for_cli() if it has been implemented --- ipalib/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 07956e0a..7148afc1 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -301,7 +301,9 @@ class CLI(object): break except errors.ValidationError, e: error = e.error - cmd.output_for_cli(cmd(**kw)) + ret = cmd(**kw) + if callable(cmd.output_for_cli): + cmd.output_for_cli(ret) def parse(self, cmd, argv): parser = self.build_parser(cmd) -- cgit From ac0a019605e951e1177d4f721bd4174f3c4b53a3 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 20 Oct 2008 18:57:03 -0600 Subject: Reworked 'plugins' command to use output_for_cli() --- ipalib/cli.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 7148afc1..365eea20 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -154,25 +154,28 @@ class plugins(text_ui): """Show all loaded plugins""" def run(self): + plugins = sorted(self.api.plugins, key=lambda o: o.plugin) + return tuple( + (p.plugin, p.bases) for p in plugins + ) + + def output_for_cli(self, result): self.print_name() first = True - for p in sorted(self.api.plugins, key=lambda o: o.plugin): + for (plugin, bases) in result: if first: first = False else: print '' - print ' plugin: %s' % p.plugin - print ' in namespaces: %s' % ', '.join(p.bases) - if len(self.api.plugins) == 1: + print ' Plugin: %s' % plugin + print ' In namespaces: %s' % ', '.join(bases) + if len(result) == 1: s = '1 plugin loaded.' else: - s = '%d plugins loaded.' % len(self.api.plugins) + s = '%d plugins loaded.' % len(result) self.print_dashed(s) - - - cli_application_commands = ( help, console, -- cgit From 6b998ed479958ec288bafa6075bb7dc03641fa48 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Tue, 21 Oct 2008 09:31:06 -0400 Subject: Make boolean options work like standard OptionParser booleans --- ipalib/cli.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 365eea20..62528034 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -325,11 +325,16 @@ class CLI(object): usage=self.get_usage(cmd), ) for option in cmd.options(): - parser.add_option('--%s' % to_cli(option.cli_name), + o = optparse.make_option('--%s' % to_cli(option.cli_name), dest=option.name, metavar=option.type.name.upper(), help=option.doc, ) + if isinstance(option.type, ipa_types.Bool): + o.action = 'store_true' + o.default = option.default + o.type = None + parser.add_option(o) return parser def parse_globals(self, argv=sys.argv[1:]): -- cgit From bc5edcf8939d5ed2bcd3ee9dcc3bc80979bf563a Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Tue, 21 Oct 2008 14:42:13 -0400 Subject: Gracefully handle keyboard interrupts (^C) --- ipalib/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 62528034..ab7e3620 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -277,7 +277,10 @@ class CLI(object): def run_cmd(self, cmd, argv): kw = self.parse(cmd, argv) - self.run_interactive(cmd, kw) + try: + self.run_interactive(cmd, kw) + except KeyboardInterrupt: + return def run_interactive(self, cmd, kw): for param in cmd.params(): -- cgit From f189b02996668e5d600f1abed675cb20cd72290f Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Wed, 22 Oct 2008 17:52:32 -0400 Subject: Return a value to the shell that called ipa --- ipalib/cli.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index ab7e3620..4e5e433e 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -270,7 +270,7 @@ class CLI(object): self.print_commands() print 'ipa: ERROR: unknown command %r' % key sys.exit(2) - self.run_cmd( + return self.run_cmd( self[key], list(s.decode('utf-8') for s in args[1:]) ) @@ -280,7 +280,11 @@ class CLI(object): try: self.run_interactive(cmd, kw) except KeyboardInterrupt: - return + return 0 + except errors.RuleError, e: + print e + return 2 + return 0 def run_interactive(self, cmd, kw): for param in cmd.params(): -- cgit From 06a82bf4b646cd077a43841abb5670d9a495b24c Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Thu, 23 Oct 2008 11:00:50 -0400 Subject: Fix ipa command running in server_context=True Make the LDAP host and port environment variables More changes so that commands have a shell return value lite-xmlrpc no longer hardcodes the kerberos credentials cache location --- ipalib/cli.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 4e5e433e..a802f8ef 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -311,9 +311,25 @@ class CLI(object): break except errors.ValidationError, e: error = e.error + if self.api.env.server_context: + try: + import krbV + import ldap + from ipa_server import conn + from ipa_server.servercore import context + krbccache = krbV.default_context().default_ccache().name + context.conn = conn.IPAConn(self.api.env.ldaphost, self.api.env.ldapport, krbccache) + except ImportError: + print >> sys.stderr, "There was a problem importing a Python module: %s" % sys.exc_value + return 2 + except ldap.LDAPError, e: + print >> sys.stderr, "There was a problem connecting to the LDAP server: %s" % e[0].get('desc') + return 2 ret = cmd(**kw) if callable(cmd.output_for_cli): - cmd.output_for_cli(ret) + return cmd.output_for_cli(ret) + else: + return 0 def parse(self, cmd, argv): parser = self.build_parser(cmd) -- cgit From 10026284dbf8f1b8a6eedf3b1c6ce05da568b4fa Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 14:48:02 -0600 Subject: Started cleanup work on CLI class, added unit tests for CLI.parse_globals() --- ipalib/cli.py | 117 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 69 insertions(+), 48 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index a802f8ef..21b02299 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -84,6 +84,24 @@ 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): 'Start the IPA interactive Python console.' @@ -207,32 +225,24 @@ class CLI(object): __d = None __mcl = None - def __init__(self, api): - self.__api = api - self.__all_interactive = False - self.__not_interactive = False + def __init__(self, api, argv): + self.api = api + self.argv = tuple(argv) + self.__done = set() - def __get_api(self): - return self.__api - api = property(__get_api) + def __doing(self, name): + if name in self.__done: + raise StandardError( + '%s.%s() already called' % (self.__class__.__name__, name) + ) + self.__done.add(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 __do_if_not_done(self, name): + if name not in self.__done: + getattr(self, name)() - def print_cmd(self, cmd): - print ' %s %s' % ( - to_cli(cmd.name).ljust(self.mcl), - cmd.doc, - ) + def isdone(self, name): + return name in self.__done def __contains__(self, key): assert self.__d is not None, 'you must call finalize() first' @@ -360,10 +370,11 @@ class CLI(object): parser.add_option(o) return parser - def parse_globals(self, argv=sys.argv[1:]): + def parse_globals(self): + self.__doing('parse_globals') parser = optparse.OptionParser() parser.disable_interspersed_args() - parser.add_option('-a', dest='interactive', action='store_true', + 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') @@ -373,29 +384,39 @@ class CLI(object): help='Specify or override environment variables') parser.add_option('-v', dest='verbose', action='store_true', help='Verbose output') - (options, args) = parser.parse_args(argv) - - if options.interactive == True: - self.__all_interactive = True - elif options.interactive == False: - self.__not_interactive = True - if options.verbose != None: - self.api.env.verbose = True - if options.environment: - env_dict = {} - for a in options.environment.split(','): - a = a.split('=', 1) - if len(a) < 2: - parser.error('badly specified environment string,'\ - 'use var1=val1[,var2=val2]..') - env_dict[a[0].strip()] = a[1].strip() - self.api.env.update(env_dict, True) - if options.config_file: - self.api.env.update(read_config(options.config_file), True) - else: - self.api.env.update(read_config(), True) - - return args + 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): + pass + +# if options.interactive == True: +# self.__all_interactive = True +# elif options.interactive == False: +# self.__not_interactive = True +# if options.verbose != None: +# self.api.env.verbose = True +# if options.environment: +# env_dict = {} +# for a in options.environment.split(','): +# a = a.split('=', 1) +# if len(a) < 2: +# parser.error('badly specified environment string,'\ +# 'use var1=val1[,var2=val2]..') +# env_dict[a[0].strip()] = a[1].strip() +# self.api.env.update(env_dict, True) +# if options.config_file: +# self.api.env.update(read_config(options.config_file), True) +# else: +# self.api.env.update(read_config(), True) + +# return args def get_usage(self, cmd): return ' '.join(self.get_usage_iter(cmd)) -- cgit From 17fd9cc4315f171a8d9e9d189936eea8ba2af0c0 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 14:49:34 -0600 Subject: Started cleanup work on CLI class, added unit tests for CLI.parse_globals() --- ipalib/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 21b02299..eb8df591 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -394,7 +394,8 @@ class CLI(object): self.cmd_argv = tuple(args) def bootstrap(self): - pass + self.__doing('bootstrap') + self.parse_globals() # if options.interactive == True: # self.__all_interactive = True -- cgit From e6254026fe73c423d357a2fa1489de35475da46c Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 15:19:49 -0600 Subject: Implemented basic CLI.bootstrap(); added corresponding unit tests --- ipalib/cli.py | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index eb8df591..e15e2ff0 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -396,28 +396,19 @@ class CLI(object): def bootstrap(self): self.__doing('bootstrap') self.parse_globals() - -# if options.interactive == True: -# self.__all_interactive = True -# elif options.interactive == False: -# self.__not_interactive = True -# if options.verbose != None: -# self.api.env.verbose = True -# if options.environment: -# env_dict = {} -# for a in options.environment.split(','): -# a = a.split('=', 1) -# if len(a) < 2: -# parser.error('badly specified environment string,'\ -# 'use var1=val1[,var2=val2]..') -# env_dict[a[0].strip()] = a[1].strip() -# self.api.env.update(env_dict, True) -# if options.config_file: -# self.api.env.update(read_config(options.config_file), True) -# else: -# self.api.env.update(read_config(), True) - -# return args + 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)) -- cgit From 9b1e3f59465c6ba33f4266bc3add469b5e1711eb Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 19:21:49 -0600 Subject: More docstrings, functionality, and unit tests for improved CLI class --- ipalib/cli.py | 246 +++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 166 insertions(+), 80 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index e15e2ff0..7141ae4b 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)) -- cgit From 6e456cc7494bc00e905361f3e6d42dff99089c6b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 23:30:55 -0600 Subject: More CLI cleanup, got all basics working again --- ipalib/cli.py | 140 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 70 insertions(+), 70 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 7141ae4b..671d4053 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -218,9 +218,9 @@ class CLI(object): self.argv = tuple(argv) self.__done = set() - def run(self): + def run(self, init_only=False): """ - Run a command (or attempt to at least). + Parse ``argv`` and potentially run a command. This method requires several initialization steps to be completed first, all of which all automatically called with a single call to @@ -245,7 +245,8 @@ class CLI(object): """ self.__doing('run') self.finalize() - return + if self.api.env.mode == 'unit-test': + return if len(self.cmd_argv) < 1: self.print_commands() print 'Usage: ipa [global-options] COMMAND' @@ -255,10 +256,7 @@ class CLI(object): 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:]) - ) + return self.run_cmd(self[key]) def finalize(self): """ @@ -381,51 +379,38 @@ class CLI(object): 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): - assert self.__d is not None, 'you must call finalize() first' - return key in self.__d - - def __getitem__(self, key): - assert self.__d is not None, 'you must call finalize() first' - return self.__d[key] - - def old_finalize(self): - api = self.api - for klass in cli_application_commands: - api.register(klass) - api.finalize() - for a in api.Application(): - a.set_application(self) - self.build_map() - - - - - def run_cmd(self, cmd, argv): - kw = self.parse(cmd, argv) + 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: - self.run_interactive(cmd, kw) - except KeyboardInterrupt: + ret = cmd(**kw) + if callable(cmd.output_for_cli): + cmd.output_for_cli(ret) return 0 - except errors.RuleError, e: + except StandardError, e: print e return 2 - return 0 - def run_interactive(self, cmd, kw): + def prompt_interactively(self, cmd, kw): + """ + Interactively prompt for missing or invalid values. + + By default this method will only prompt for *required* Param that + have a missing or invalid value. However, if + ``CLI.options.prompt_all`` is True, this method will prompt for any + params that have a missing or required values, even if the param is + optional. + """ for param in cmd.params(): if param.name not in kw: - if not param.required: - if not self.__all_interactive: - continue - elif self.__not_interactive: - exit_error('Not enough arguments given') + if not (param.required or self.options.prompt_all): + continue default = param.get_default(**kw) if default is None: prompt = '%s: ' % param.cli_name @@ -443,29 +428,34 @@ class CLI(object): break except errors.ValidationError, e: error = e.error - if self.api.env.server_context: - try: - import krbV - import ldap - from ipa_server import conn - from ipa_server.servercore import context - krbccache = krbV.default_context().default_ccache().name - context.conn = conn.IPAConn(self.api.env.ldaphost, self.api.env.ldapport, krbccache) - except ImportError: - print >> sys.stderr, "There was a problem importing a Python module: %s" % sys.exc_value - return 2 - except ldap.LDAPError, e: - print >> sys.stderr, "There was a problem connecting to the LDAP server: %s" % e[0].get('desc') - return 2 - ret = cmd(**kw) - if callable(cmd.output_for_cli): - return cmd.output_for_cli(ret) - else: - return 0 + return kw - def parse(self, cmd, argv): +# FIXME: This should be done as the plugins are loaded +# if self.api.env.server_context: +# try: +# import krbV +# import ldap +# from ipa_server import conn +# from ipa_server.servercore import context +# krbccache = krbV.default_context().default_ccache().name +# context.conn = conn.IPAConn(self.api.env.ldaphost, self.api.env.ldapport, krbccache) +# except ImportError: +# print >> sys.stderr, "There was a problem importing a Python module: %s" % sys.exc_value +# return 2 +# except ldap.LDAPError, e: +# print >> sys.stderr, "There was a problem connecting to the LDAP server: %s" % e[0].get('desc') +# return 2 +# ret = cmd(**kw) +# if callable(cmd.output_for_cli): +# return cmd.output_for_cli(ret) +# else: +# return 0 + + def parse(self, cmd): parser = self.build_parser(cmd) - (kwc, args) = parser.parse_args(argv, KWCollector()) + (kwc, args) = parser.parse_args( + list(self.cmd_argv), KWCollector() + ) kw = kwc.__todict__() try: arg_kw = cmd.args_to_kw(*args) @@ -492,10 +482,6 @@ class CLI(object): parser.add_option(o) return parser - - - - def get_usage(self, cmd): return ' '.join(self.get_usage_iter(cmd)) @@ -520,3 +506,17 @@ class CLI(object): self.__mcl = max(len(k) for k in self.__d) return self.__mcl mcl = property(__get_mcl) + + def isdone(self, name): + """ + Return True in method named ``name`` has already been called. + """ + return name in self.__done + + def __contains__(self, key): + assert self.__d is not None, 'you must call finalize() first' + return key in self.__d + + def __getitem__(self, key): + assert self.__d is not None, 'you must call finalize() first' + return self.__d[key] -- cgit From 83d6c95e4636049a5bcedb533ad49f6e2cf79dfe Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 27 Oct 2008 23:39:43 -0600 Subject: API.load_plugins() no longer takes dry_run=False kwarg and instead checks in env.mode == 'unit_test' to decide whether to load the plugins; it also only loads ipa_server.plugins in env.in_server is True --- ipalib/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 671d4053..3613dfeb 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -245,7 +245,7 @@ class CLI(object): """ self.__doing('run') self.finalize() - if self.api.env.mode == 'unit-test': + if self.api.env.mode == 'unit_test': return if len(self.cmd_argv) < 1: self.print_commands() -- cgit From 316bd855d5720f4babfb79d20c391de3f8958a60 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 28 Oct 2008 01:39:02 -0600 Subject: Added util.configure_logging() function; API.bootstrap() now calls util.configure_logging() --- ipalib/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 3613dfeb..6407e9e2 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -454,7 +454,7 @@ class CLI(object): def parse(self, cmd): parser = self.build_parser(cmd) (kwc, args) = parser.parse_args( - list(self.cmd_argv), KWCollector() + list(self.cmd_argv[1:]), KWCollector() ) kw = kwc.__todict__() try: -- cgit From 138305b365307a30d151ff144d5a1e0d95723293 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 28 Oct 2008 02:23:13 -0600 Subject: Added an example CLI-specific env command --- ipalib/cli.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 6407e9e2..021e01ad 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -97,6 +97,21 @@ class console(frontend.Application): ) +class env(frontend.Application): + """ + Show environment variables. + """ + + def run(self): + return tuple( + (key, self.api.env[key]) for key in self.api.env + ) + + def output_for_cli(self, ret): + for (key, value) in ret: + print '%s = %r' % (key, value) + + class show_api(text_ui): 'Show attributes on dynamic API object' @@ -183,6 +198,7 @@ cli_application_commands = ( console, show_api, plugins, + env, ) -- cgit From ddb5449c7faabbd4c1b71adfe84c386b943a163f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 30 Oct 2008 01:11:33 -0600 Subject: Did some initial work for Context plugins --- ipalib/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 021e01ad..39773d73 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -331,7 +331,7 @@ class CLI(object): if len(a) < 2: parser.error('badly specified environment string,'\ 'use var1=val1[,var2=val2]..') - overrides[a[0].strip()] = a[1].strip() + overrides[str(a[0].strip())] = a[1].strip() overrides['context'] = 'cli' self.api.bootstrap(**overrides) -- cgit From 6879140db790a23a8782f7200400f2b58a69f6a0 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 30 Oct 2008 02:20:28 -0600 Subject: Added ipalib.plugins.f_misc with new 'context' Command; moved 'env' Command from cli to f_misc --- ipalib/cli.py | 18 ------------------ 1 file changed, 18 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 39773d73..161ea1d8 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -97,22 +97,6 @@ class console(frontend.Application): ) -class env(frontend.Application): - """ - Show environment variables. - """ - - def run(self): - return tuple( - (key, self.api.env[key]) for key in self.api.env - ) - - def output_for_cli(self, ret): - for (key, value) in ret: - print '%s = %r' % (key, value) - - - class show_api(text_ui): 'Show attributes on dynamic API object' @@ -198,8 +182,6 @@ cli_application_commands = ( console, show_api, plugins, - env, - ) -- cgit From a23d41a57f43c3a0f298d3918ae1712181fa544e Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 31 Oct 2008 18:17:08 -0600 Subject: Reoganized global option functionality to it is easy for any script to use the environment-related global options; lite-xmlrpc.py now uses same global options --- ipalib/cli.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 161ea1d8..c1ad82d5 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -31,6 +31,7 @@ import errors import plugable import ipa_types from config import set_default_env, read_config +import util def exit_error(error): sys.exit('ipa: ERROR: %s' % error) @@ -303,19 +304,7 @@ class CLI(object): """ 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[str(a[0].strip())] = a[1].strip() - overrides['context'] = 'cli' - self.api.bootstrap(**overrides) + self.api.bootstrap_from_options(self.options, context='cli') def parse_globals(self): """ @@ -337,17 +326,17 @@ class CLI(object): 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.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, ) + util.add_global_options(parser) (options, args) = parser.parse_args(list(self.argv)) self.options = options self.cmd_argv = tuple(args) -- cgit From 5e5a83e4e84d2e9a5d6d987056199a8ed83978b8 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 31 Oct 2008 19:03:07 -0600 Subject: Renamed API.bootstrap_from_options() to bootstrap_with_global_options() --- ipalib/cli.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index c1ad82d5..732e38bb 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -304,7 +304,7 @@ class CLI(object): """ self.__doing('bootstrap') self.parse_globals() - self.api.bootstrap_from_options(self.options, context='cli') + self.api.bootstrap_with_global_options(self.options, context='cli') def parse_globals(self): """ @@ -318,6 +318,9 @@ class CLI(object): 2. ``CLI.cmd_argv`` - a tuple containing the remainder of ``CLI.argv`` after the global options have been consumed. + + The common global options are added using the + `util.add_global_options` function. """ self.__doing('parse_globals') parser = optparse.OptionParser() @@ -326,12 +329,6 @@ class CLI(object): 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, -- cgit From d53218a9321eb4def0bfeb484709323de74eef1a Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Mon, 3 Nov 2008 17:19:29 -0500 Subject: Handle exceptions in the command-line instead of in the XMLRPC client plugin --- ipalib/cli.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 732e38bb..8cf8d304 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -377,6 +377,16 @@ class CLI(object): if callable(cmd.output_for_cli): cmd.output_for_cli(ret) return 0 + 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 -- cgit From f1314806434b9226f8a7722675b060bdf574c455 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Mon, 3 Nov 2008 17:38:05 -0500 Subject: Move socket errors from the XML-RPC plugin to the client --- ipalib/cli.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 8cf8d304..d7288154 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -25,6 +25,7 @@ import re import sys import code import optparse +import socket import frontend import errors @@ -377,6 +378,9 @@ class CLI(object): if callable(cmd.output_for_cli): cmd.output_for_cli(ret) 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) -- cgit From 014af24731ff39520a9635694ed99dc9d09669c9 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 12 Nov 2008 00:46:04 -0700 Subject: Changed calling signature of output_for_cli(); started work on 'textui' backend plugin --- ipalib/cli.py | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 189 insertions(+), 30 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index d7288154..8878c212 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -22,20 +22,20 @@ Functionality for Command Line Interface. """ import re +import textwrap import sys import code import optparse import socket import frontend +import backend import errors import plugable import ipa_types from config import set_default_env, read_config import util - -def exit_error(error): - sys.exit('ipa: ERROR: %s' % error) +from constants import CLI_TAB def to_cli(name): @@ -55,21 +55,189 @@ def from_cli(cli_name): return str(cli_name).replace('-', '_') -class text_ui(frontend.Application): +class textui(backend.Backend): """ - Base class for CLI commands with special output needs. + Backend plugin to nicely format output to stdout. """ - def print_dashed(self, string, top=True, bottom=True): + def get_tty_width(self): + """ + Return the width (in characters) of output tty. + + If stdout is not a tty, this method will return ``None``. + """ + if sys.stdout.isatty(): + return 80 # FIXME: we need to return the actual tty width + + def max_col_width(self, rows, col=None): + """ + Return the max width (in characters) of a specified column. + + For example: + + >>> ui = textui() + >>> rows = [ + ... ('a', 'package'), + ... ('an', 'egg'), + ... ] + >>> ui.max_col_width(rows, col=0) # len('an') + 2 + >>> ui.max_col_width(rows, col=1) # len('package') + 7 + >>> ui.max_col_width(['a', 'cherry', 'py']) # len('cherry') + 6 + """ + if type(rows) not in (list, tuple): + raise TypeError( + 'rows: need %r or %r; got %r' % (list, tuple, rows) + ) + if len(rows) == 0: + return 0 + if col is None: + return max(len(row) for row in rows) + return max(len(row[col]) for row in rows) + + def print_dashed(self, string, above=True, below=True): + """ + Print a string with with a dashed line above and/or below. + + For example: + + >>> ui = textui() + >>> ui.print_dashed('Dashed above and below.') + ----------------------- + Dashed above and below. + ----------------------- + >>> ui.print_dashed('Only dashed below.', above=False) + Only dashed below. + ------------------ + >>> ui.print_dashed('Only dashed above.', below=False) + ------------------ + Only dashed above. + """ dashes = '-' * len(string) - if top: + if above: print dashes print string - if bottom: + if below: print dashes - def print_name(self, **kw): - self.print_dashed('%s:' % self.name, **kw) + def print_line(self, text, width=None): + """ + Force printing on a single line, using ellipsis if needed. + + For example: + + >>> ui = textui() + >>> ui.print_line('This line can fit!', width=18) + This line can fit! + >>> ui.print_line('This line wont quite fit!', width=18) + This line wont ... + + The above example aside, you normally should not specify the + ``width``. When you don't, it is automatically determined by calling + `textui.get_tty_width()`. + """ + if width is None: + width = self.get_tty_width() + if width is not None and width < len(text): + text = text[:width - 3] + '...' + print text + + def print_indented(self, text, indent=1): + """ + Print at specified indentation level. + + For example: + + >>> ui = textui() + >>> ui.print_indented('One indentation level.') + One indentation level. + >>> ui.print_indented('Two indentation levels.', indent=2) + Two indentation levels. + >>> ui.print_indented('No indentation.', indent=0) + No indentation. + """ + print (CLI_TAB * indent + text) + + def print_name(self, name): + """ + Print a command name. + + The typical use for this is to mark the start of output from a + command. For example, a hypothetical ``show_status`` command would + output something like this: + + >>> ui = textui() + >>> ui.print_name('show_status') + ------------ + show-status: + ------------ + """ + self.print_dashed('%s:' % to_cli(name)) + + def print_keyval(self, rows, indent=1): + """ + Print (key = value) pairs, one pair per line. + + For example: + + >>> items = [ + ... ('in_server', True), + ... ('mode', 'production'), + ... ] + >>> ui = textui() + >>> ui.print_keyval(items) + in_server = True + mode = 'production' + >>> ui.print_keyval(items, indent=0) + in_server = True + mode = 'production' + + Also see `textui.print_indented`. + """ + for row in rows: + self.print_indented('%s = %r' % row, indent) + + def print_count(self, count, singular, plural=None): + """ + Print a summary count. + + The typical use for this is to print the number of items returned + by a command, especially when this return count can vary. This + preferably should be used as a summary and should be the final text + a command outputs. + + For example: + + >>> ui = textui() + >>> ui.print_count(1, '%d goose', '%d geese') + ------- + 1 goose + ------- + >>> ui.print_count(['Don', 'Sue'], 'Found %d user', 'Found %d users') + ------------- + Found 2 users + ------------- + + If ``count`` is not an integer, it must be a list or tuple, and then + ``len(count)`` is used as the count. + """ + if type(count) is not int: + assert type(count) in (list, tuple) + count = len(count) + self.print_dashed( + self.choose_number(count, singular, plural) + ) + + def choose_number(self, n, singular, plural=None): + if n == 1 or plural is None: + return singular % n + return plural % n + + +def exit_error(error): + sys.exit('ipa: ERROR: %s' % error) class help(frontend.Application): @@ -87,10 +255,8 @@ class help(frontend.Application): self.application.build_parser(cmd).print_help() - - class console(frontend.Application): - 'Start the IPA interactive Python console.' + """Start the IPA interactive Python console.""" def run(self): code.interact( @@ -99,7 +265,7 @@ class console(frontend.Application): ) -class show_api(text_ui): +class show_api(frontend.Application): 'Show attributes on dynamic API object' takes_args = ('namespaces*',) @@ -153,7 +319,7 @@ class show_api(text_ui): self.__traverse_namespace(n, attr, lines, tab + 2) -class plugins(text_ui): +class plugins(frontend.Application): """Show all loaded plugins""" def run(self): @@ -162,21 +328,13 @@ class plugins(text_ui): (p.plugin, p.bases) for p in plugins ) - def output_for_cli(self, result): - self.print_name() - first = True + def output_for_cli(self, textui, result, **kw): + textui.print_name(self.name) for (plugin, bases) in result: - if first: - first = False - else: - print '' - print ' Plugin: %s' % plugin - print ' In namespaces: %s' % ', '.join(bases) - if len(result) == 1: - s = '1 plugin loaded.' - else: - s = '%d plugins loaded.' % len(result) - self.print_dashed(s) + textui.print_indented( + '%s: %s' % (plugin, ', '.join(bases)) + ) + textui.print_count(result, '%d plugin loaded', '%s plugins loaded') cli_application_commands = ( @@ -293,6 +451,7 @@ class CLI(object): self.api.load_plugins() for klass in cli_application_commands: self.api.register(klass) + self.api.register(textui) def bootstrap(self): """ @@ -376,7 +535,7 @@ class CLI(object): try: ret = cmd(**kw) if callable(cmd.output_for_cli): - cmd.output_for_cli(ret) + cmd.output_for_cli(self.api.Backend.textui, ret, **kw) return 0 except socket.error, e: print e[1] -- cgit From f04aaff97c9c8c22b36706f2c6d4de6f23d06b95 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 12 Nov 2008 09:55:11 -0700 Subject: output_for_cli signature is now output_for_cli(textui, result, *args, **options) --- ipalib/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 8878c212..d86647c6 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -535,7 +535,8 @@ class CLI(object): try: ret = cmd(**kw) if callable(cmd.output_for_cli): - cmd.output_for_cli(self.api.Backend.textui, ret, **kw) + (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] -- cgit From 01a7f1f437b72c2c13c6abfb02c6dea3924fa291 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 12 Nov 2008 10:15:24 -0700 Subject: Calling ./ipa with no command now calls Command.help() --- ipalib/cli.py | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index d86647c6..febf399d 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -241,12 +241,16 @@ def exit_error(error): class help(frontend.Application): - 'Display help on a command.' + '''Display help on a command.''' - takes_args = ['command'] + takes_args = ['command?'] - def run(self, key): - key = str(key) + def run(self, command): + textui = self.Backend.textui + if command is None: + self.print_commands() + return + key = str(command) if key not in self.application: print 'help: no such command %r' % key sys.exit(2) @@ -254,6 +258,24 @@ class help(frontend.Application): print 'Purpose: %s' % cmd.doc self.application.build_parser(cmd).print_help() + def print_commands(self): + std = set(self.Command) - set(self.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.application.mcl), + cmd.doc, + ) + class console(frontend.Application): """Start the IPA interactive Python console.""" @@ -406,12 +428,9 @@ class CLI(object): if self.api.env.mode == 'unit_test': return if len(self.cmd_argv) < 1: - self.print_commands() - print 'Usage: ipa [global-options] COMMAND' - sys.exit(2) + sys.exit(self.api.Command.help()) 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]) @@ -505,23 +524,7 @@ class CLI(object): ) self.__done.add(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 run_cmd(self, cmd): kw = self.parse(cmd) -- cgit 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 +++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 43 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index febf399d..5659cfc0 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): """ -- cgit From 82d3de773b2504145cddbcf8a6e5d1abf58fcb12 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 13 Nov 2008 23:54:34 -0700 Subject: Added textui.prompt() method, which CLI.prompt_interactively() uses --- ipalib/cli.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 5659cfc0..3b365cdb 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -235,6 +235,16 @@ class textui(backend.Backend): return singular % n return plural % n + def prompt(self, label, default=None, get_values=None): + """ + Prompt user for input. + """ + if default is None: + prompt = '%s: ' % label + else: + prompt = '%s [%s]: ' % (label, default) + return raw_input(prompt) + class help(frontend.Application): '''Display help on a command.''' @@ -489,6 +499,7 @@ class CLI(object): self.__d = dict( (c.name.replace('_', '-'), c) for c in self.api.Command() ) + self.textui = self.api.Backend.textui def load_plugins(self): """ @@ -584,15 +595,11 @@ class CLI(object): if not (param.required or self.options.prompt_all): continue default = param.get_default(**kw) - if default is None: - prompt = '%s: ' % param.cli_name - else: - prompt = '%s [%s]: ' % (param.cli_name, default) error = None while True: if error is not None: print '>>> %s: %s' % (param.cli_name, error) - raw = raw_input(prompt) + raw = self.textui.prompt(param.cli_name, default) try: value = param(raw, **kw) if value is not None: -- cgit From c974451edf7895bcac9531a62c5d49c7c4141978 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 14 Nov 2008 13:33:42 -0700 Subject: Added print_plain() and print_paragraph() methods to textui plugin and cleaned up the order of its methods --- ipalib/cli.py | 119 +++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 42 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 3b365cdb..69a3c28f 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -97,30 +97,16 @@ class textui(backend.Backend): return max(len(row) for row in rows) return max(len(row[col]) for row in rows) - def print_dashed(self, string, above=True, below=True): - """ - Print a string with with a dashed line above and/or below. - - For example: + def choose_number(self, n, singular, plural=None): + if n == 1 or plural is None: + return singular % n + return plural % n - >>> ui = textui() - >>> ui.print_dashed('Dashed above and below.') - ----------------------- - Dashed above and below. - ----------------------- - >>> ui.print_dashed('Only dashed below.', above=False) - Only dashed below. - ------------------ - >>> ui.print_dashed('Only dashed above.', below=False) - ------------------ - Only dashed above. + def print_plain(self, string): + """ + Print exactly like ``print`` statement would. """ - dashes = '-' * len(string) - if above: - print dashes print string - if below: - print dashes def print_line(self, text, width=None): """ @@ -144,6 +130,35 @@ class textui(backend.Backend): text = text[:width - 3] + '...' print text + def print_paragraph(self, text, width=None): + """ + Print a paragraph, automatically word-wrapping to tty width. + + For example: + + >>> text = ''' + ... Python is a dynamic object-oriented programming language that can + ... be used for many kinds of software development. + ... ''' + >>> ui = textui() + >>> ui.print_paragraph(text, width=45) + Python is a dynamic object-oriented + programming language that can be used for + many kinds of software development. + + The above example aside, you normally should not specify the + ``width``. When you don't, it is automatically determined by calling + `textui.get_tty_width()`. + + The word-wrapping is done using the Python ``textwrap`` module. See: + + http://docs.python.org/library/textwrap.html + """ + if width is None: + width = self.get_tty_width() + for line in textwrap.wrap(text.strip(), width): + print line + def print_indented(self, text, indent=1): """ Print at specified indentation level. @@ -160,22 +175,6 @@ class textui(backend.Backend): """ print (CLI_TAB * indent + text) - def print_name(self, name): - """ - Print a command name. - - The typical use for this is to mark the start of output from a - command. For example, a hypothetical ``show_status`` command would - output something like this: - - >>> ui = textui() - >>> ui.print_name('show_status') - ------------ - show-status: - ------------ - """ - self.print_dashed('%s:' % to_cli(name)) - def print_keyval(self, rows, indent=1): """ Print (key = value) pairs, one pair per line. @@ -199,6 +198,47 @@ class textui(backend.Backend): for row in rows: self.print_indented('%s = %r' % row, indent) + def print_dashed(self, string, above=True, below=True): + """ + Print a string with a dashed line above and/or below. + + For example: + + >>> ui = textui() + >>> ui.print_dashed('Dashed above and below.') + ----------------------- + Dashed above and below. + ----------------------- + >>> ui.print_dashed('Only dashed below.', above=False) + Only dashed below. + ------------------ + >>> ui.print_dashed('Only dashed above.', below=False) + ------------------ + Only dashed above. + """ + dashes = '-' * len(string) + if above: + print dashes + print string + if below: + print dashes + + def print_name(self, name): + """ + Print a command name. + + The typical use for this is to mark the start of output from a + command. For example, a hypothetical ``show_status`` command would + output something like this: + + >>> ui = textui() + >>> ui.print_name('show_status') + ------------ + show-status: + ------------ + """ + self.print_dashed('%s:' % to_cli(name)) + def print_count(self, count, singular, plural=None): """ Print a summary count. @@ -230,11 +270,6 @@ class textui(backend.Backend): self.choose_number(count, singular, plural) ) - def choose_number(self, n, singular, plural=None): - if n == 1 or plural is None: - return singular % n - return plural % n - def prompt(self, label, default=None, get_values=None): """ Prompt user for input. -- cgit From e3fec8f2192019e9b16dcc8ef3e965bd13c1e1d4 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 14 Nov 2008 22:03:31 -0700 Subject: Fixed textui.print_keyval for cases when the row is a list instead of a tuple --- ipalib/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 69a3c28f..dd20b365 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -195,8 +195,8 @@ class textui(backend.Backend): Also see `textui.print_indented`. """ - for row in rows: - self.print_indented('%s = %r' % row, indent) + for (key, value) in rows: + self.print_indented('%s = %r' % (key, value), indent) def print_dashed(self, string, above=True, below=True): """ -- cgit From e7ec4131589d5d387c4257bca76e91a17ad7e1a3 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 16 Nov 2008 19:50:17 -0700 Subject: Moved plugins command from ipalib.cli to ipalib.plugins.f_misc --- ipalib/cli.py | 19 ------------------- 1 file changed, 19 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index dd20b365..fc58f2e9 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -381,29 +381,10 @@ class show_api(frontend.Application): self.__traverse_namespace(n, attr, lines, tab + 2) -class plugins(frontend.Application): - """Show all loaded plugins""" - - def run(self): - plugins = sorted(self.api.plugins, key=lambda o: o.plugin) - return tuple( - (p.plugin, p.bases) for p in plugins - ) - - def output_for_cli(self, textui, result, **kw): - textui.print_name(self.name) - for (plugin, bases) in result: - textui.print_indented( - '%s: %s' % (plugin, ', '.join(bases)) - ) - textui.print_count(result, '%d plugin loaded', '%s plugins loaded') - - cli_application_commands = ( help, console, show_api, - plugins, ) -- cgit From 42bf555a3ad1f1777b26a4092b49512b9360c882 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 17 Nov 2008 15:27:08 -0700 Subject: Started updated user_* commands to use textui --- ipalib/cli.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index fc58f2e9..1c7256a2 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -198,6 +198,28 @@ class textui(backend.Backend): for (key, value) in rows: self.print_indented('%s = %r' % (key, value), indent) + def print_entry(self, entry, indent=1): + """ + Print an ldap entry dict. + + For example: + + >>> entry = dict(sn='Last', givenname='First', uid='flast') + >>> ui = textui() + >>> ui.print_entry(entry) + givenname: 'First' + sn: 'Last' + uid: 'flast' + """ + assert type(entry) is dict + for key in sorted(entry): + value = entry[key] + if type(value) in (list, tuple): + value = ', '.join(repr(v) for v in value) + else: + value = repr(value) + self.print_indented('%s: %s' % (key, value), indent) + def print_dashed(self, string, above=True, below=True): """ Print a string with a dashed line above and/or below. -- cgit From 8474bd01da13b9b72ba06e832d4c35ef6ccf5c9e Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 17 Nov 2008 18:50:30 -0700 Subject: Command.get_defaults() now returns param.default if param.type is a Bool --- ipalib/cli.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 1c7256a2..909e2acb 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -693,8 +693,10 @@ class CLI(object): help=option.doc, ) if isinstance(option.type, ipa_types.Bool): - o.action = 'store_true' - o.default = option.default + if option.default is True: + o.action = 'store_false' + else: + o.action = 'store_true' o.type = None parser.add_option(o) return parser -- cgit From 75d1918996dc62a99927d3f7e2ff13404c67ef5b Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 17 Nov 2008 20:41:01 -0700 Subject: Added some experimental textui methods --- ipalib/cli.py | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 909e2acb..f1d8eebe 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -220,7 +220,7 @@ class textui(backend.Backend): value = repr(value) self.print_indented('%s: %s' % (key, value), indent) - def print_dashed(self, string, above=True, below=True): + def print_dashed(self, string, above=True, below=True, indent=0, dash='-'): """ Print a string with a dashed line above and/or below. @@ -238,12 +238,42 @@ class textui(backend.Backend): ------------------ Only dashed above. """ - dashes = '-' * len(string) + assert isinstance(dash, basestring) + assert len(dash) == 1 + dashes = dash * len(string) if above: - print dashes - print string + self.print_indented(dashes, indent) + self.print_indented(string, indent) if below: - print dashes + self.print_indented(dashes, indent) + + def print_h1(self, text): + """ + Print a primary header at indentation level 0. + + For example: + + >>> ui = textui() + >>> ui.print_h1('A primary header') + ================ + A primary header + ================ + """ + self.print_dashed(text, indent=0, dash='=') + + def print_h2(self, text): + """ + Print a secondary header at indentation level 1. + + For example: + + >>> ui = textui() + >>> ui.print_h2('A secondary header') + ------------------ + A secondary header + ------------------ + """ + self.print_dashed(text, indent=1, dash='-') def print_name(self, name): """ -- cgit From 0a60a6bcc4c8eaa7d42dc25fa6fc69d837e3e816 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 18 Nov 2008 11:30:16 -0700 Subject: Added textui.prompt_password() method; added logic in cli for dealing with 'password' flag in param.flags --- ipalib/cli.py | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index f1d8eebe..7cbf6e4b 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -24,6 +24,7 @@ Functionality for Command Line Interface. import re import textwrap import sys +import getpass import code import optparse import socket @@ -332,6 +333,21 @@ class textui(backend.Backend): prompt = '%s [%s]: ' % (label, default) return raw_input(prompt) + def prompt_password(self, label): + """ + Prompt user for a password. + """ + try: + while True: + pw1 = getpass.getpass('%s: ' % label) + pw2 = getpass.getpass('Enter again to verify: ') + if pw1 == pw2: + return pw1 + print ' ** Passwords do not match. Please enter again. **' + except KeyboardInterrupt: + print '' + print ' ** Cancelled. **' + class help(frontend.Application): '''Display help on a command.''' @@ -659,7 +675,14 @@ class CLI(object): optional. """ for param in cmd.params(): - if param.name not in kw: + if 'password' in param.flags: + if kw.get(param.name, False) is True: + kw[param.name] = self.textui.prompt_password( + param.cli_name + ) + else: + kw.pop(param.name, None) + elif param.name not in kw: if not (param.required or self.options.prompt_all): continue default = param.get_default(**kw) @@ -704,10 +727,7 @@ class CLI(object): list(self.cmd_argv[1:]), KWCollector() ) kw = kwc.__todict__() - try: - arg_kw = cmd.args_to_kw(*args) - except errors.ArgumentError, e: - exit_error('%s %s' % (to_cli(cmd.name), e.error)) + arg_kw = cmd.args_to_kw(*args) assert set(arg_kw).intersection(kw) == set() kw.update(arg_kw) return kw @@ -717,17 +737,20 @@ class CLI(object): usage=self.get_usage(cmd), ) for option in cmd.options(): - o = optparse.make_option('--%s' % to_cli(option.cli_name), + kw = dict( dest=option.name, - metavar=option.type.name.upper(), help=option.doc, ) - if isinstance(option.type, ipa_types.Bool): + if 'password' in option.flags: + kw['action'] = 'store_true' + elif isinstance(option.type, ipa_types.Bool): if option.default is True: - o.action = 'store_false' + kw['action'] = 'store_false' else: - o.action = 'store_true' - o.type = None + kw['action'] = 'store_true' + else: + kw['metavar'] = metavar=option.type.name.upper() + o = optparse.make_option('--%s' % to_cli(option.cli_name), **kw) parser.add_option(o) return parser -- cgit From 4afee15d4b523a641552bee9993882bb1ae6e2cc Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 18 Nov 2008 13:43:43 -0700 Subject: Calling 'passwd' command now prompts for password using textui.prompt_password() --- ipalib/cli.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 7cbf6e4b..b3aa1099 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -658,12 +658,28 @@ class CLI(object): def run_cmd(self, cmd): kw = self.parse(cmd) if self.options.interactive: - kw = self.prompt_interactively(cmd, kw) + self.prompt_interactively(cmd, kw) + self.prompt_for_passwords(cmd, kw) result = cmd(**kw) if callable(cmd.output_for_cli): + for param in cmd.params(): + if param.ispassword(): + del kw[param.name] (args, options) = cmd.params_2_args_options(kw) cmd.output_for_cli(self.api.Backend.textui, result, *args, **options) + def prompt_for_passwords(self, cmd, kw): + for param in cmd.params(): + if 'password' not in param.flags: + continue + if kw.get(param.name, False) is True or param.name in cmd.args: + kw[param.name] = self.textui.prompt_password( + param.cli_name + ) + else: + kw.pop(param.name, None) + return kw + def prompt_interactively(self, cmd, kw): """ Interactively prompt for missing or invalid values. @@ -676,12 +692,7 @@ class CLI(object): """ for param in cmd.params(): if 'password' in param.flags: - if kw.get(param.name, False) is True: - kw[param.name] = self.textui.prompt_password( - param.cli_name - ) - else: - kw.pop(param.name, None) + continue elif param.name not in kw: if not (param.required or self.options.prompt_all): continue @@ -760,6 +771,8 @@ class CLI(object): def get_usage_iter(self, cmd): yield 'Usage: %%prog [global-options] %s' % to_cli(cmd.name) for arg in cmd.args(): + if 'password' in arg.flags: + continue name = to_cli(arg.cli_name).upper() if arg.multivalue: name = '%s...' % name -- cgit From 2478ccd357efa7c38cc4acbfe2bfab2f3a8bf0cd Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 19 Nov 2008 03:31:29 -0700 Subject: Fixed some unicode encoded/decode issues in textui.prompt_password() and textui.prompt() --- ipalib/cli.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index b3aa1099..37fdad44 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -98,6 +98,30 @@ class textui(backend.Backend): return max(len(row) for row in rows) return max(len(row[col]) for row in rows) + def __get_encoding(self, stream): + assert stream in (sys.stdin, sys.stdout) + if stream.encoding is None: + if stream.isatty(): + return sys.getdefaultencoding() + return 'UTF-8' + return stream.encoding + + def decode(self, str_buffer): + """ + Decode text from stdin. + """ + assert type(str_buffer) is str + encoding = self.__get_encoding(sys.stdin) + return str_buffer.decode(encoding) + + def encode(self, unicode_text): + """ + Encode text for output to stdout. + """ + assert type(unicode_text) is unicode + encoding = self.__get_encoding(sys.stdout) + return unicode_text.encode(encoding) + def choose_number(self, n, singular, plural=None): if n == 1 or plural is None: return singular % n @@ -327,11 +351,14 @@ class textui(backend.Backend): """ Prompt user for input. """ + # TODO: Add tab completion using readline if default is None: - prompt = '%s: ' % label + prompt = u'%s: ' % label else: - prompt = '%s [%s]: ' % (label, default) - return raw_input(prompt) + prompt = u'%s [%s]: ' % (label, default) + return self.decode( + raw_input(self.encode(prompt)) + ) def prompt_password(self, label): """ @@ -342,7 +369,7 @@ class textui(backend.Backend): pw1 = getpass.getpass('%s: ' % label) pw2 = getpass.getpass('Enter again to verify: ') if pw1 == pw2: - return pw1 + return self.decode(pw1) print ' ** Passwords do not match. Please enter again. **' except KeyboardInterrupt: print '' -- cgit From fc8ac693726ec33b5c0924f9b8ff5d663705a5a3 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Fri, 5 Dec 2008 15:31:18 -0500 Subject: Port plugins to use the new output_for_cli() argument list Fix some errors uncovered by the nosetests --- ipalib/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 37fdad44..af3eb6e3 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -691,7 +691,10 @@ class CLI(object): if callable(cmd.output_for_cli): for param in cmd.params(): if param.ispassword(): - del kw[param.name] + try: + del kw[param.name] + except KeyError: + pass (args, options) = cmd.params_2_args_options(kw) cmd.output_for_cli(self.api.Backend.textui, result, *args, **options) -- cgit From 3583735c60515d604b02ddb0c62e3da9c47807cf Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Wed, 10 Dec 2008 13:49:59 -0500 Subject: Set defaults even for optional arguments. --- ipalib/cli.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index af3eb6e3..ca3364ae 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -687,6 +687,7 @@ class CLI(object): if self.options.interactive: self.prompt_interactively(cmd, kw) self.prompt_for_passwords(cmd, kw) + self.set_defaults(cmd, kw) result = cmd(**kw) if callable(cmd.output_for_cli): for param in cmd.params(): @@ -698,6 +699,13 @@ class CLI(object): (args, options) = cmd.params_2_args_options(kw) cmd.output_for_cli(self.api.Backend.textui, result, *args, **options) + def set_defaults(self, cmd, kw): + for param in cmd.params(): + if not kw.get(param.name): + value = param.get_default(**kw) + if value: + kw[param.name] = value + def prompt_for_passwords(self, cmd, kw): for param in cmd.params(): if 'password' not in param.flags: -- cgit From 67b688c7b26845dedeca378d4ba8df88e6b44c0c Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Tue, 16 Dec 2008 19:00:39 -0700 Subject: Jakub Hrozek's patch to make textui.get_tty_width() actually work --- ipalib/cli.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 37fdad44..a0fe63db 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -28,6 +28,9 @@ import getpass import code import optparse import socket +import fcntl +import termios +import struct import frontend import backend @@ -67,8 +70,15 @@ class textui(backend.Backend): If stdout is not a tty, this method will return ``None``. """ + # /usr/include/asm/termios.h says that struct winsize has four + # unsigned shorts, hence the HHHH if sys.stdout.isatty(): - return 80 # FIXME: we need to return the actual tty width + try: + winsize = fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, + struct.pack('HHHH', 0, 0, 0, 0)) + return struct.unpack('HHHH', winsize)[1] + except IOError: + return None def max_col_width(self, rows, col=None): """ -- cgit From 360f95341a78e2fd601a38ffa103a5f5cbe8c424 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Wed, 17 Dec 2008 17:21:25 -0700 Subject: Fix show_api command --- ipalib/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index a0fe63db..0bab952b 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -443,11 +443,11 @@ class show_api(frontend.Application): else: for name in namespaces: if name not in self.api: - exit_error('api has no such namespace: %s' % name) + raise errors.NoSuchNamespaceError(name) names = namespaces lines = self.__traverse(names) ml = max(len(l[1]) for l in lines) - self.print_name() + self.Backend.textui.print_name('run') first = True for line in lines: if line[0] == 0 and not first: @@ -463,7 +463,7 @@ class show_api(frontend.Application): s = '1 attribute shown.' else: s = '%d attributes show.' % len(lines) - self.print_dashed(s) + self.Backend.textui.print_dashed(s) def __traverse(self, names): -- cgit From 5b637f6a18a647a0ff084b2932faa1a4a887a5c2 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 22 Dec 2008 15:41:24 -0700 Subject: Removed depreciated code from config.py; removed corresponding unit tests --- ipalib/cli.py | 1 - 1 file changed, 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 518b7129..f4cdbbf5 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -37,7 +37,6 @@ import backend import errors import plugable import ipa_types -from config import set_default_env, read_config import util from constants import CLI_TAB -- cgit From 6fe78a4944f11d430b724103f7d8d49c92af9b63 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 4 Jan 2009 18:39:39 -0700 Subject: Renamed all references to 'ipa_server' to 'ipaserver' --- ipalib/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index f4cdbbf5..442e5061 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -763,8 +763,8 @@ class CLI(object): # try: # import krbV # import ldap -# from ipa_server import conn -# from ipa_server.servercore import context +# from ipaserver import conn +# from ipaserver.servercore import context # krbccache = krbV.default_context().default_ccache().name # context.conn = conn.IPAConn(self.api.env.ldaphost, self.api.env.ldapport, krbccache) # except ImportError: -- cgit From 69acff450c043bdd7d70da473c3adafdd9d3fe03 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 14 Jan 2009 12:00:47 -0700 Subject: New Param: removed more depreciated 'import ipa_types' --- ipalib/cli.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 442e5061..f6c187b3 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -36,7 +36,6 @@ import frontend import backend import errors import plugable -import ipa_types import util from constants import CLI_TAB @@ -801,7 +800,7 @@ class CLI(object): ) if 'password' in option.flags: kw['action'] = 'store_true' - elif isinstance(option.type, ipa_types.Bool): + elif option.type is bool: if option.default is True: kw['action'] = 'store_false' else: -- cgit From 4d4fa694ee2b84975a0e7596b51e21109f58a76d Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Wed, 14 Jan 2009 23:15:46 -0700 Subject: Small change in ipalib.cli to check if param is a Password instance instead of calling depreciated ispasswd() method --- ipalib/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index f6c187b3..5cf68852 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -38,6 +38,7 @@ import errors import plugable import util from constants import CLI_TAB +from parameters import Password def to_cli(name): @@ -699,7 +700,7 @@ class CLI(object): result = cmd(**kw) if callable(cmd.output_for_cli): for param in cmd.params(): - if param.ispassword(): + if isinstance(param, Password): try: del kw[param.name] except KeyError: -- cgit From 7514f96173575c42c2f5592034211d19ff711a02 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Fri, 16 Jan 2009 11:07:21 -0700 Subject: New Param: fixed metavar bug in cli.py --- ipalib/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ipalib/cli.py') diff --git a/ipalib/cli.py b/ipalib/cli.py index 5cf68852..fb2fd95f 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -807,7 +807,7 @@ class CLI(object): else: kw['action'] = 'store_true' else: - kw['metavar'] = metavar=option.type.name.upper() + kw['metavar'] = metavar=option.__class__.__name__.upper() o = optparse.make_option('--%s' % to_cli(option.cli_name), **kw) parser.add_option(o) return parser -- cgit