mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-01-11 00:31:56 -06:00
Changed calling signature of output_for_cli(); started work on 'textui' backend plugin
This commit is contained in:
parent
f3869d7b24
commit
014af24731
7
ipa
7
ipa
@ -30,7 +30,12 @@ from ipalib import api
|
||||
from ipalib.cli import CLI
|
||||
|
||||
if __name__ == '__main__':
|
||||
# If we can't explicitly determin the encoding, we assume UTF-8:
|
||||
if sys.stdin.encoding is None:
|
||||
encoding = 'UTF-8'
|
||||
else:
|
||||
encoding = sys.stdin.encoding
|
||||
cli = CLI(api,
|
||||
(s.decode('utf-8') for s in sys.argv[1:])
|
||||
(s.decode(encoding) for s in sys.argv[1:])
|
||||
)
|
||||
sys.exit(cli.run())
|
||||
|
219
ipalib/cli.py
219
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]
|
||||
|
@ -22,6 +22,8 @@
|
||||
All constants centralized in one file.
|
||||
"""
|
||||
|
||||
# Used for a tab (or indentation level) when formatting for CLI:
|
||||
CLI_TAB = ' ' # Two spaces
|
||||
|
||||
# The section to read in the config files, i.e. [global]
|
||||
CONFIG_SECTION = 'global'
|
||||
|
@ -24,22 +24,11 @@ Misc frontend plugins.
|
||||
from ipalib import api, Command, Param, Bool
|
||||
|
||||
|
||||
class env_and_context(Command):
|
||||
"""
|
||||
Base class for `env` and `context` commands.
|
||||
"""
|
||||
|
||||
def run(self, **kw):
|
||||
if kw.get('server', False) and not self.api.env.in_server:
|
||||
return self.forward()
|
||||
return self.execute()
|
||||
|
||||
def output_for_cli(self, ret):
|
||||
for (key, value) in ret:
|
||||
print '%s = %r' % (key, value)
|
||||
|
||||
|
||||
class env(env_and_context):
|
||||
# FIXME: We should not let env return anything in_server
|
||||
# when mode == 'production'. This would allow an attacker to see the
|
||||
# configuration of the server, potentially revealing compromising
|
||||
# information. However, it's damn handy for testing/debugging.
|
||||
class env(Command):
|
||||
"""Show environment variables"""
|
||||
|
||||
takes_options = (
|
||||
@ -48,26 +37,19 @@ class env(env_and_context):
|
||||
),
|
||||
)
|
||||
|
||||
def run(self, **kw):
|
||||
if kw.get('server', False) and not self.api.env.in_server:
|
||||
return self.forward()
|
||||
return self.execute()
|
||||
|
||||
def execute(self):
|
||||
return tuple(
|
||||
(key, self.api.env[key]) for key in self.api.env
|
||||
)
|
||||
|
||||
def output_for_cli(self, textui, result, **kw):
|
||||
textui.print_name(self.name)
|
||||
textui.print_keyval(result)
|
||||
textui.print_count(result, '%d variable', '%d variables')
|
||||
|
||||
api.register(env)
|
||||
|
||||
|
||||
class context(env_and_context):
|
||||
"""Show request context"""
|
||||
|
||||
takes_options = (
|
||||
Param('server?', type=Bool(), default=False,
|
||||
doc='Show request context in server',
|
||||
),
|
||||
)
|
||||
|
||||
def execute(self):
|
||||
return [
|
||||
(key, self.api.context[key]) for key in self.api.Context
|
||||
]
|
||||
|
||||
api.register(context)
|
||||
|
@ -25,6 +25,31 @@ from tests.util import raises, get_api, ClassChecker
|
||||
from ipalib import cli, plugable, frontend, backend
|
||||
|
||||
|
||||
class test_textui(ClassChecker):
|
||||
_cls = cli.textui
|
||||
|
||||
def test_max_col_width(self):
|
||||
"""
|
||||
Test the `ipalib.cli.textui.max_col_width` method.
|
||||
"""
|
||||
o = self.cls()
|
||||
e = raises(TypeError, o.max_col_width, 'hello')
|
||||
assert str(e) == 'rows: need %r or %r; got %r' % (list, tuple, 'hello')
|
||||
rows = [
|
||||
'hello',
|
||||
'naughty',
|
||||
'nurse',
|
||||
]
|
||||
assert o.max_col_width(rows) == len('naughty')
|
||||
rows = (
|
||||
( 'a', 'bbb', 'ccccc'),
|
||||
('aa', 'bbbb', 'cccccc'),
|
||||
)
|
||||
assert o.max_col_width(rows, col=0) == 2
|
||||
assert o.max_col_width(rows, col=1) == 4
|
||||
assert o.max_col_width(rows, col=2) == 6
|
||||
|
||||
|
||||
def test_to_cli():
|
||||
"""
|
||||
Test the `ipalib.cli.to_cli` function.
|
||||
|
Loading…
Reference in New Issue
Block a user