freeipa/ipalib/cli.py
Rob Crittenden c7789199f9 Fix output of failed managedby hosts, allow a host to manage itself.
The output problem was a missing label for failed managedby.

This also fixes a call to print_entry that was missing the flags argument.

Add a flag to specify whether a group can be a member of itself, defaulting
to False.

ticket 708
2011-01-11 10:23:31 -05:00

1069 lines
35 KiB
Python

# Authors:
# Jason Gerard DeRose <jderose@redhat.com>
#
# 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, either version 3 of the License, or
# (at your option) any later version.
#
# 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, see <http://www.gnu.org/licenses/>.
"""
Functionality for Command Line Interface.
"""
import re
import textwrap
import sys
import getpass
import code
import optparse
import socket
import fcntl
import termios
import struct
import base64
import default_encoding_utf8
import frontend
import backend
import plugable
import util
from errors import PublicError, CommandError, HelpError, InternalError, NoSuchNamespaceError, ValidationError, NotFound, NotConfiguredError
from constants import CLI_TAB
from parameters import Password, Bytes, File
from text import _
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.
"""
return str(cli_name).replace('-', '_')
class textui(backend.Backend):
"""
Backend plugin to nicely format output to stdout.
"""
def get_tty_width(self):
"""
Return the width (in characters) of output tty.
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():
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):
"""
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 __get_encoding(self, stream):
assert stream in (sys.stdin, sys.stdout)
if stream.encoding is None:
return 'UTF-8'
return stream.encoding
def decode(self, value):
"""
Decode text from stdin.
"""
if type(value) is str:
encoding = self.__get_encoding(sys.stdin)
return value.decode(encoding)
elif type(value) in (list, tuple):
return tuple(self.decode(v) for v in value)
return value
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
return plural % n
def encode_binary(self, value):
"""
Convert a binary value to base64. We know a value is binary
if it is a python str type, otherwise it is a plain string.
"""
if type(value) is str:
return base64.b64encode(value)
else:
return value
def print_plain(self, string):
"""
Print exactly like ``print`` statement would.
"""
print unicode(string)
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 unicode(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.
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_keyval(self, rows, indent=1):
"""
Print (key = value) pairs, one pair per line.
For example:
>>> items = [
... ('in_server', True),
... ('mode', u'production'),
... ]
>>> ui = textui()
>>> ui.print_keyval(items)
in_server = True
mode = u'production'
>>> ui.print_keyval(items, indent=0)
in_server = True
mode = u'production'
Also see `textui.print_indented`.
"""
for (key, value) in rows:
self.print_indented('%s = %r' % (key, self.encode_binary(value)), indent)
def print_attribute(self, attr, value, format='%s: %s', indent=1, one_value_per_line=True):
"""
Print an ldap attribute.
For example:
>>> attr = 'dn'
>>> ui = textui()
>>> ui.print_attribute(attr, u'dc=example,dc=com')
dn: dc=example,dc=com
>>> attr = 'objectClass'
>>> ui.print_attribute(attr, [u'top', u'someClass'], one_value_per_line=False)
objectClass: top, someClass
>>> ui.print_attribute(attr, [u'top', u'someClass'])
objectClass: top
objectClass: someClass
"""
assert isinstance(attr, basestring)
if not isinstance(value, (list, tuple)):
# single-value attribute
self.print_indented(format % (attr, self.encode_binary(value)), indent)
else:
# multi-value attribute
if one_value_per_line:
for v in value:
self.print_indented(format % (attr, self.encode_binary(v)), indent)
else:
value = map(lambda v: self.encode_binary(v), value)
if len(value) > 0 and type(value[0]) in (list, tuple):
# This is where we print failed add/remove members
for l in value:
text = ': '.join(l)
self.print_indented(format % (attr, self.encode_binary(text)), indent)
return
else:
if len(value) > 0:
text = ', '.join(value)
else:
return
line_len = self.get_tty_width()
if line_len and text:
s_indent = '%s%s' % (
CLI_TAB * indent, ' ' * (len(attr) + 2)
)
line_len -= len(s_indent)
text = textwrap.wrap(
text, line_len, break_long_words=False
)
else:
text = [text]
self.print_indented(format % (attr, text[0]), indent)
for line in text[1:]:
self.print_plain('%s%s' % (s_indent, line))
def print_entry1(self, entry, indent=1, attr_map={}, attr_order=['dn'],
one_value_per_line=True):
"""
Print an ldap entry dict.
"""
assert isinstance(entry, dict)
assert isinstance(attr_map, dict)
assert isinstance(attr_order, (list, tuple))
def print_attr(a):
if attr in attr_map:
self.print_attribute(
attr_map[attr], entry[attr], indent=indent, one_value_per_line=one_value_per_line
)
else:
self.print_attribute(
attr, entry[attr], indent=indent, one_value_per_line=one_value_per_line
)
for attr in attr_order:
if attr in entry:
print_attr(attr)
del entry[attr]
for attr in sorted(entry):
print_attr(attr)
def print_entries(self, entries, order=None, labels=None, flags=None, print_all=True, format='%s: %s', indent=1):
assert isinstance(entries, (list, tuple))
first = True
for entry in entries:
if not first:
print ''
first = False
self.print_entry(entry, order, labels, flags, print_all, format, indent)
def print_entry(self, entry, order=None, labels=None, flags=None, print_all=True, format='%s: %s', indent=1):
"""
"""
if isinstance(entry, (list, tuple)):
entry = dict(entry)
assert isinstance(entry, dict)
if labels is None:
labels = dict()
one_value_per_line = True
else:
one_value_per_line = False
if order is not None:
for key in order:
if key not in entry:
continue
label = labels.get(key, key)
flag = flags.get(key, [])
value = entry[key]
if 'suppress_empty' in flag and value in [u'', '', [], None]:
continue
if isinstance(value, dict):
if frontend.entry_count(value) == 0:
continue
self.print_indented(format % (label, ''), indent)
self.print_entry(
value, order, labels, flags, print_all, format,
indent=indent+1
)
else:
self.print_attribute(
label, value, format, indent, one_value_per_line
)
del entry[key]
if print_all:
for key in sorted(entry):
label = labels.get(key, key)
self.print_attribute(
key, entry[key], format, indent, one_value_per_line
)
def print_dashed(self, string, above=True, below=True, indent=0, dash='-'):
"""
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.
"""
assert isinstance(dash, basestring)
assert len(dash) == 1
dashes = dash * len(string)
if above:
self.print_indented(dashes, indent)
self.print_indented(string, indent)
if below:
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):
"""
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_header(self, msg, output):
self.print_dashed(msg % output)
def print_summary(self, msg):
"""
Print a summary at the end of a comand's output.
For example:
>>> ui = textui()
>>> ui.print_summary('Added user "jdoe"')
-----------------
Added user "jdoe"
-----------------
"""
self.print_dashed(msg)
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, dict)
count = len(count)
self.print_dashed(
self.choose_number(count, singular, plural)
)
def print_error(self, text):
print ' ** %s **' % unicode(text)
def prompt(self, label, default=None, get_values=None):
"""
Prompt user for input.
"""
# TODO: Add tab completion using readline
if default is None:
prompt = u'%s: ' % label
else:
prompt = u'%s [%s]: ' % (label, default)
try:
data = raw_input(self.encode(prompt))
except EOFError:
return None
return self.decode(data)
def prompt_password(self, label):
"""
Prompt user for a password or read it in via stdin depending
on whether there is a tty or not.
"""
try:
if sys.stdin.isatty():
while True:
pw1 = getpass.getpass(u'%s: ' % unicode(label))
pw2 = getpass.getpass(
unicode(_('Enter %(label)s again to verify: ') % dict(label=label))
)
if pw1 == pw2:
return self.decode(pw1)
self.print_error( _('Passwords do not match!'))
else:
return self.decode(sys.stdin.readline().strip())
except KeyboardInterrupt:
print ''
self.print_error(_('Cancelled.'))
def select_entry(self, entries, format, attrs, display_count=True):
"""
Display a list of lines in with formatting defined in ``format``.
``attrs`` is a list of attributes in the format.
Prompt user for a selection and return the value (index of
``entries`` -1).
If only one entry is provided then always return 0.
Return: 0..n for the index of the selected entry
-1 if all entries should be displayed
-2 to quit, no entries to be displayed
"""
if not self.env.interactive or not sys.stdout.isatty():
return -1
counter = len(entries)
if counter == 0:
raise NotFound(reason="No matching entries found")
i = 1
for e in entries:
# There is no guarantee that all attrs are in any given
# entry
d = {}
for a in attrs:
d[a] = e.get(a, '')
self.print_line("%d: %s" % (i, format % d))
i = i + 1
if display_count:
self.print_count(entries, 'Found %d match', 'Found %d matches')
while True:
try:
resp = self.prompt("Choose one: (1 - %s), a for all, q to quit" % counter)
except EOFError:
return -2
if resp.lower() == "q":
return -2
if resp.lower() == "a":
return -1
try:
selection = int(resp) - 1
if (selection >= 0 and selection < counter):
break
except:
# fall through to the error msg
pass
self.print_line("Please enter a number between 1 and %s" % counter)
self.print_line('')
return selection
class help(frontend.Local):
"""
Display help for a command or topic.
"""
takes_args = (Bytes('command?'),)
has_output = tuple()
_PLUGIN_BASE_MODULE = 'ipalib.plugins'
def _get_command_module(self, module):
"""
Return last part of ``module`` name, or ``None`` if module is this file.
For example:
"""
if module == __name__:
return
return module.split('.')[-1]
# get representation in the form of 'base_module.bare_module.command()'
r = repr(cmd_plugin_proxy)
# skip base module part and the following dot
start = r.find(self._PLUGIN_BASE_MODULE)
if start == -1:
# command module isn't a plugin module, it's a builtin
return None
start += len(self._PLUGIN_BASE_MODULE) + 1
# parse bare module name
end = r.find('.', start)
return r[start:end]
def _get_module_topic(self, module_name):
if not sys.modules[module_name]:
__import__(module_name)
module = sys.modules[module_name]
dir_list = dir(module)
if 'topic' in dir_list:
topic = module.topic
else:
topic = (self._get_command_module(module_name), None)
return topic
def _count_topic_mcl(self, topic_name, mod_name):
mcl = max((self._topics[topic_name][1], len(mod_name)))
self._topics[topic_name][1] = mcl
def finalize(self):
# {topic: ["description", mcl, {"subtopic": ["description", mcl, [commands]]}]}
# {topic: ["description", mcl, [commands]]}
self._topics = {}
# [builtin_commands]
self._builtins = []
# build help topics
for c in self.Command():
if c.INTERNAL:
continue
topic = self._get_module_topic(c.module)
topic_name = topic[0]
if topic_name:
if topic[1] is None: # a module without grouping
if topic_name in self._topics:
self._topics[topic_name][2].append(c)
else:
m = '%s.%s' % (self._PLUGIN_BASE_MODULE, topic_name)
doc = (sys.modules[m].__doc__ or '').strip().split('\n', 1)[0]
self._topics[topic_name] = [doc, 0, [c]]
mcl = max((self._topics[topic_name][1], len(c.name)))
self._topics[topic_name][1] = mcl
else: # a module grouped in a topic
doc = (sys.modules[c.module].__doc__ or '').strip().split('\n', 1)[0]
mod_name = c.module.rsplit('.',1)[1]
if topic_name in self._topics:
if mod_name in self._topics[topic_name][2]:
self._topics[topic_name][2][mod_name][2].append(c)
else:
self._topics[topic_name][2][mod_name] = [doc, 0, [c]]
self._count_topic_mcl(topic_name, mod_name)
# count mcl for for the subtopic
mcl = max((self._topics[topic_name][2][mod_name][1], len(c.name)))
self._topics[topic_name][2][mod_name][1] = mcl
else:
self._topics[topic_name] = [topic[1], 0, {mod_name: [doc, 0, [c]]}]
self._count_topic_mcl(topic_name, mod_name)
else:
self._builtins.append(c)
# compute maximum topic length
self._mtl = max(
len(s) for s in (self._topics.keys() + [c.name for c in self._builtins])
)
super(help, self).finalize()
def run(self, key):
name = from_cli(key)
mod_name = '%s.%s' % (self._PLUGIN_BASE_MODULE, name)
if key is None or name == "topics":
self.print_topics()
return
if name in self._topics:
self.print_commands(name)
elif name in self.Command:
cmd = self.Command[name]
print 'Purpose: %s' % cmd.doc
self.Backend.cli.build_parser(cmd).print_help()
elif mod_name in sys.modules:
self.print_commands(name)
elif name == "commands":
mcl = max(len(s) for s in (self.Command))
for cname in self.Command:
cmd = self.Command[cname]
if cmd.INTERNAL:
continue
print '%s %s' % (to_cli(cmd.name).ljust(mcl), cmd.summary)
else:
raise HelpError(topic=name)
def print_topics(self):
topics = sorted(self._topics.keys())
print 'Usage: ipa [global-options] COMMAND ...'
print ''
print 'Built-in commands:'
for c in self._builtins:
print 'Help subtopics:'
print ' %s %s' % (to_cli(c.name).ljust(self._mtl), c.summary)
print ''
print 'Help topics:'
for t in topics:
topic = self._topics[t]
print ' %s %s' % (to_cli(t).ljust(self._mtl), topic[0])
if False:
topic_commands = self._topics[t][2]
mod_list = [self._get_command_module(c.module) for c in topic_commands]
mod_list = list(set(mod_list))
for mod in mod_list:
m = '%s.%s' % (self._PLUGIN_BASE_MODULE, mod)
if 'topic' in dir(sys.modules[m]):
doc = sys.modules[m].topic[1]
else:
doc = (sys.modules[m].__doc__ or '').strip().split('\n', 1)[0]
print ' %s %s' % (to_cli(t).ljust(self._mtl), doc)
print ''
print 'Try `ipa --help` for a list of global options.'
def print_commands(self, topic):
if topic in self._topics and type(self._topics[topic][2]) is dict:
# we want to display topic which has subtopics
for subtopic in self._topics[topic][2]:
doc = self._topics[topic][2][subtopic][0]
mcl = self._topics[topic][1]
print ' %s %s' % (to_cli(subtopic).ljust(mcl), doc)
else:
# we want to display subtopic or a topic which has no subtopics
if topic in self._topics:
mcl = self._topics[topic][1]
commands = self._topics[topic][2]
else:
for t in self._topics:
if type(self._topics[t][2]) is not dict:
continue
if topic not in self._topics[t][2]:
continue
mcl = self._topics[t][2][topic][1]
commands = self._topics[t][2][topic][2]
break
m = '%s.%s' % (self._PLUGIN_BASE_MODULE, topic)
doc = (sys.modules[m].__doc__ or '').strip()
print doc
print ''
if len(commands) > 1:
print 'Topic commands:'
for c in commands:
print ' %s %s' % (to_cli(c.name).ljust(mcl), c.summary)
print "\n"
class console(frontend.Command):
"""Start the IPA interactive Python console."""
has_output = tuple()
def run(self):
code.interact(
'(Custom IPA interactive Python console)',
local=dict(api=self.api)
)
class show_api(frontend.Command):
'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:
raise NoSuchNamespaceError(name=name)
names = namespaces
lines = self.__traverse(names)
ml = max(len(l[1]) for l in lines)
self.Backend.textui.print_name('run')
first = True
for line in lines:
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.Backend.textui.print_dashed(s)
def __traverse(self, names):
lines = []
for name in names:
namespace = self.api[name]
self.__traverse_namespace('%s' % 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) and len(attr) > 0:
self.__traverse_namespace(n, attr, lines, tab + 2)
cli_application_commands = (
help,
console,
show_api,
)
class Collector(object):
def __init__(self):
object.__setattr__(self, '_Collector__options', {})
def __setattr__(self, name, value):
if name in self.__options:
v = self.__options[name]
if type(v) is tuple:
value = v + (value,)
else:
value = (v, value)
self.__options[name] = value
object.__setattr__(self, name, value)
def __todict__(self):
return dict(self.__options)
class cli(backend.Executioner):
"""
Backend plugin for executing from command line interface.
"""
def run(self, argv):
if len(argv) == 0:
self.Command.help()
return
(key, argv) = (argv[0], argv[1:])
name = from_cli(key)
if name not in self.Command or self.Command[name].INTERNAL:
raise CommandError(name=key)
cmd = self.Command[name]
if not isinstance(cmd, frontend.Local):
self.create_context()
kw = self.parse(cmd, argv)
if self.env.interactive:
self.prompt_interactively(cmd, kw)
self.load_files(cmd, kw)
try:
result = self.execute(name, **kw)
if callable(cmd.output_for_cli):
for param in cmd.params():
if param.password and param.name in kw:
del kw[param.name]
(args, options) = cmd.params_2_args_options(**kw)
rv = cmd.output_for_cli(self.api.Backend.textui, result, *args, **options)
if rv:
return rv
else:
return 0
finally:
self.destroy_context()
def parse(self, cmd, argv):
parser = self.build_parser(cmd)
(collector, args) = parser.parse_args(argv, Collector())
options = collector.__todict__()
kw = cmd.args_options_2_params(*args, **options)
return dict(self.parse_iter(cmd, kw))
# FIXME: Probably move decoding to Command, use same method regardless of
# request source:
def parse_iter(self, cmd, kw):
"""
Decode param values if appropriate.
"""
for (key, value) in kw.iteritems():
param = cmd.params[key]
if isinstance(param, Bytes):
yield (key, value)
else:
yield (key, self.Backend.textui.decode(value))
def build_parser(self, cmd):
parser = optparse.OptionParser(
usage=' '.join(self.usage_iter(cmd))
)
for option in cmd.options():
kw = dict(
dest=option.name,
help=unicode(option.doc),
)
if option.password and self.env.interactive:
kw['action'] = 'store_true'
elif option.type is bool and option.autofill:
if option.default is True:
kw['action'] = 'store_false'
else:
kw['action'] = 'store_true'
else:
kw['metavar'] = metavar=option.__class__.__name__.upper()
if option.cli_short_name:
o = optparse.make_option('-%s' % option.cli_short_name, '--%s' % to_cli(option.cli_name), **kw)
else:
o = optparse.make_option('--%s' % to_cli(option.cli_name), **kw)
parser.add_option(o)
return parser
def usage_iter(self, cmd):
yield 'Usage: %%prog [global-options] %s' % to_cli(cmd.name)
for arg in cmd.args():
if arg.password:
continue
name = to_cli(arg.cli_name).upper()
if arg.multivalue:
name = '%s...' % name
if arg.required:
yield name
else:
yield '[%s]' % name
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
``self.env.prompt_all`` is ``True``, this method will prompt for any
params that have a missing values, even if the param is optional.
"""
honor_alwaysask = True
for param in cmd.params():
if param.alwaysask and param.name in kw:
honor_alwaysask = False
break
for param in cmd.params():
if (param.required and param.name not in kw) or \
(param.alwaysask and honor_alwaysask) or self.env.prompt_all:
if param.password:
kw[param.name] = self.Backend.textui.prompt_password(
param.label
)
elif param.autofill:
kw[param.name] = param.get_default(**kw)
else:
default = param.get_default(**kw)
error = None
while True:
if error is not None:
print '>>> %s: %s' % (unicode(param.label), unicode(error))
raw = self.Backend.textui.prompt(param.label, default)
try:
value = param(raw, **kw)
if value is not None:
kw[param.name] = value
break
except ValidationError, e:
error = e.error
elif param.password and kw.get(param.name, False) is True:
kw[param.name] = self.Backend.textui.prompt_password(
param.label
)
def load_files(self, cmd, kw):
"""
Load files from File parameters.
This has to be done after all required parameters have been read
(i.e. after prompt_interactively has or would have been called)
AND before they are passed to the command. This is because:
1) we need to be sure no more files are going to be added
2) we load files from the machine where the command was executed
3) the webUI will use a different way of loading files
"""
for p in cmd.params():
if isinstance(p, File):
if p.name in kw:
try:
f = open(kw[p.name], 'r')
raw = f.read()
f.close()
except IOError, e:
raise ValidationError(
name=to_cli(p.cli_name),
error='%s: %s:' % (kw[p.name], e[1])
)
elif p.stdin_if_missing:
try:
raw = sys.stdin.read()
except IOError, e:
raise ValidationError(
name=to_cli(p.cli_name), error=e[1]
)
kw[p.name] = self.Backend.textui.decode(raw)
cli_plugins = (
cli,
textui,
console,
help,
)
def run(api):
error = None
try:
(options, argv) = api.bootstrap_with_global_options(context='cli')
for klass in cli_plugins:
api.register(klass)
api.load_plugins()
api.finalize()
if not 'config_loaded' in api.env:
raise NotConfiguredError()
sys.exit(api.Backend.cli.run(argv))
except KeyboardInterrupt:
print ''
api.log.info('operation aborted')
except PublicError, e:
error = e
except StandardError, e:
api.log.exception('%s: %s', e.__class__.__name__, str(e))
error = InternalError()
if error is not None:
assert isinstance(error, PublicError)
api.log.error(error.strerror)
sys.exit(error.rval)