Changed calling signature of output_for_cli(); started work on 'textui' backend plugin

This commit is contained in:
Jason Gerard DeRose 2008-11-12 00:46:04 -07:00
parent f3869d7b24
commit 014af24731
5 changed files with 237 additions and 64 deletions

7
ipa
View File

@ -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())

View File

@ -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]

View File

@ -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'

View File

@ -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)

View File

@ -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.