mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
View plugin/command help in pager
ipa help code invokes pager if help lines length is more then current terminal height. https://pagure.io/freeipa/issue/7225 Reviewed-By: Tibor Dudlak <tdudlak@redhat.com>
This commit is contained in:
parent
443ecbc29e
commit
caed210bc2
105
ipalib/cli.py
105
ipalib/cli.py
@ -38,6 +38,10 @@ import traceback
|
|||||||
import six
|
import six
|
||||||
from six.moves import input
|
from six.moves import input
|
||||||
|
|
||||||
|
from ipalib.util import (
|
||||||
|
check_client_configuration, get_terminal_height, open_in_pager
|
||||||
|
)
|
||||||
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
unicode = str
|
unicode = str
|
||||||
|
|
||||||
@ -55,7 +59,6 @@ from ipalib.constants import CLI_TAB, LDAP_GENERALIZED_TIME_FORMAT
|
|||||||
from ipalib.parameters import File, Str, Enum, Any, Flag
|
from ipalib.parameters import File, Str, Enum, Any, Flag
|
||||||
from ipalib.text import _
|
from ipalib.text import _
|
||||||
from ipalib import api # pylint: disable=unused-import
|
from ipalib import api # pylint: disable=unused-import
|
||||||
from ipalib.util import check_client_configuration
|
|
||||||
from ipapython.dnsutil import DNSName
|
from ipapython.dnsutil import DNSName
|
||||||
from ipapython.admintool import ScriptError
|
from ipapython.admintool import ScriptError
|
||||||
|
|
||||||
@ -680,10 +683,39 @@ class textui(backend.Backend):
|
|||||||
self.print_line('')
|
self.print_line('')
|
||||||
return selection
|
return selection
|
||||||
|
|
||||||
|
|
||||||
class help(frontend.Local):
|
class help(frontend.Local):
|
||||||
"""
|
"""
|
||||||
Display help for a command or topic.
|
Display help for a command or topic.
|
||||||
"""
|
"""
|
||||||
|
class Writer(object):
|
||||||
|
"""
|
||||||
|
Writer abstraction
|
||||||
|
"""
|
||||||
|
def __init__(self, outfile):
|
||||||
|
self.outfile = outfile
|
||||||
|
self.buffer = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def buffer_length(self):
|
||||||
|
length = 0
|
||||||
|
for line in self.buffer:
|
||||||
|
length += len(line.split("\n"))
|
||||||
|
return length
|
||||||
|
|
||||||
|
def append(self, string=u""):
|
||||||
|
self.buffer.append(unicode(string))
|
||||||
|
|
||||||
|
def write(self):
|
||||||
|
if self.buffer_length > get_terminal_height():
|
||||||
|
data = "\n".join(self.buffer).encode("utf-8")
|
||||||
|
open_in_pager(data)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
for line in self.buffer:
|
||||||
|
print(line, file=self.outfile)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
|
||||||
takes_args = (
|
takes_args = (
|
||||||
Str('command?', cli_name='topic', label=_('Topic or Command'),
|
Str('command?', cli_name='topic', label=_('Topic or Command'),
|
||||||
@ -702,7 +734,7 @@ class help(frontend.Local):
|
|||||||
parent_topic = None
|
parent_topic = None
|
||||||
|
|
||||||
for package in self.api.packages:
|
for package in self.api.packages:
|
||||||
module_name = '%s.%s' % (package.__name__, topic)
|
module_name = '{0}.{1}'.format(package.__name__, topic)
|
||||||
try:
|
try:
|
||||||
module = sys.modules[module_name]
|
module = sys.modules[module_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -725,7 +757,8 @@ class help(frontend.Local):
|
|||||||
self._topics[topic_name][1] = mcl
|
self._topics[topic_name][1] = mcl
|
||||||
|
|
||||||
def _on_finalize(self):
|
def _on_finalize(self):
|
||||||
# {topic: ["description", mcl, {"subtopic": ["description", mcl, [commands]]}]}
|
# {topic: ["description", mcl, {
|
||||||
|
# "subtopic": ["description", mcl, [commands]]}]}
|
||||||
# {topic: ["description", mcl, [commands]]}
|
# {topic: ["description", mcl, [commands]]}
|
||||||
self._topics = {}
|
self._topics = {}
|
||||||
# [builtin_commands]
|
# [builtin_commands]
|
||||||
@ -749,22 +782,26 @@ class help(frontend.Local):
|
|||||||
self._topics[topic_name] = [doc, 0, [c]]
|
self._topics[topic_name] = [doc, 0, [c]]
|
||||||
mcl = max((self._topics[topic_name][1], len(c.name)))
|
mcl = max((self._topics[topic_name][1], len(c.name)))
|
||||||
self._topics[topic_name][1] = mcl
|
self._topics[topic_name][1] = mcl
|
||||||
else: # a module grouped in a topic
|
else: # a module grouped in a topic
|
||||||
topic = self._get_topic(topic_name)
|
topic = self._get_topic(topic_name)
|
||||||
mod_name = c.topic
|
mod_name = c.topic
|
||||||
if topic_name in self._topics:
|
if topic_name in self._topics:
|
||||||
if mod_name in self._topics[topic_name][2]:
|
if mod_name in self._topics[topic_name][2]:
|
||||||
self._topics[topic_name][2][mod_name][2].append(c)
|
self._topics[topic_name][2][mod_name][2].append(c)
|
||||||
else:
|
else:
|
||||||
self._topics[topic_name][2][mod_name] = [doc, 0, [c]]
|
self._topics[topic_name][2][mod_name] = [
|
||||||
|
doc, 0, [c]]
|
||||||
self._count_topic_mcl(topic_name, mod_name)
|
self._count_topic_mcl(topic_name, mod_name)
|
||||||
# count mcl for for the subtopic
|
# count mcl for for the subtopic
|
||||||
mcl = max((self._topics[topic_name][2][mod_name][1], len(c.name)))
|
mcl = max((
|
||||||
|
self._topics[topic_name][2][mod_name][1],
|
||||||
|
len(c.name)))
|
||||||
self._topics[topic_name][2][mod_name][1] = mcl
|
self._topics[topic_name][2][mod_name][1] = mcl
|
||||||
else:
|
else:
|
||||||
self._topics[topic_name] = [topic[0].split('\n', 1)[0],
|
self._topics[topic_name] = [
|
||||||
0,
|
topic[0].split('\n', 1)[0],
|
||||||
{mod_name: [doc, 0, [c]]}]
|
0,
|
||||||
|
{mod_name: [doc, 0, [c]]}]
|
||||||
self._count_topic_mcl(topic_name, mod_name)
|
self._count_topic_mcl(topic_name, mod_name)
|
||||||
else:
|
else:
|
||||||
self._builtins.append(c)
|
self._builtins.append(c)
|
||||||
@ -778,8 +815,10 @@ class help(frontend.Local):
|
|||||||
def run(self, key=None, outfile=None, **options):
|
def run(self, key=None, outfile=None, **options):
|
||||||
if outfile is None:
|
if outfile is None:
|
||||||
outfile = sys.stdout
|
outfile = sys.stdout
|
||||||
writer = self._writer(outfile)
|
|
||||||
|
writer = self.Writer(outfile)
|
||||||
name = from_cli(key)
|
name = from_cli(key)
|
||||||
|
|
||||||
if key is None:
|
if key is None:
|
||||||
self.api.parser.print_help(outfile)
|
self.api.parser.print_help(outfile)
|
||||||
return
|
return
|
||||||
@ -804,33 +843,30 @@ class help(frontend.Local):
|
|||||||
if cmd_plugin.NO_CLI:
|
if cmd_plugin.NO_CLI:
|
||||||
continue
|
continue
|
||||||
mcl = max(mcl, len(cmd_plugin.name))
|
mcl = max(mcl, len(cmd_plugin.name))
|
||||||
writer('%s %s' % (to_cli(cmd_plugin.name).ljust(mcl),
|
writer.append('{0} {1}'.format(
|
||||||
cmd_plugin.summary))
|
to_cli(cmd_plugin.name).ljust(mcl), cmd_plugin.summary))
|
||||||
else:
|
else:
|
||||||
raise HelpError(topic=name)
|
raise HelpError(topic=name)
|
||||||
|
writer.write()
|
||||||
def _writer(self, outfile):
|
|
||||||
def writer(string=''):
|
|
||||||
try:
|
|
||||||
print(unicode(string), file=outfile)
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
return writer
|
|
||||||
|
|
||||||
def print_topics(self, outfile):
|
def print_topics(self, outfile):
|
||||||
writer = self._writer(outfile)
|
writer = self.Writer(outfile)
|
||||||
|
|
||||||
for t, topic in sorted(self._topics.items()):
|
for t, topic in sorted(self._topics.items()):
|
||||||
writer('%s %s' % (to_cli(t).ljust(self._mtl), topic[0]))
|
writer.append('{0} {1}'.format(
|
||||||
|
to_cli(t).ljust(self._mtl), topic[0]))
|
||||||
|
writer.write()
|
||||||
|
|
||||||
def print_commands(self, topic, outfile):
|
def print_commands(self, topic, outfile):
|
||||||
writer = self._writer(outfile)
|
writer = self.Writer(outfile)
|
||||||
|
|
||||||
if topic in self._topics and type(self._topics[topic][2]) is dict:
|
if topic in self._topics and type(self._topics[topic][2]) is dict:
|
||||||
# we want to display topic which has subtopics
|
# we want to display topic which has subtopics
|
||||||
for subtopic in self._topics[topic][2]:
|
for subtopic in self._topics[topic][2]:
|
||||||
doc = self._topics[topic][2][subtopic][0]
|
doc = self._topics[topic][2][subtopic][0]
|
||||||
mcl = self._topics[topic][1]
|
mcl = self._topics[topic][1]
|
||||||
writer(' %s %s' % (to_cli(subtopic).ljust(mcl), doc))
|
writer.append(' {0} {1}'.format(
|
||||||
|
to_cli(subtopic).ljust(mcl), doc))
|
||||||
else:
|
else:
|
||||||
# we want to display subtopic or a topic which has no subtopics
|
# we want to display subtopic or a topic which has no subtopics
|
||||||
if topic in self._topics:
|
if topic in self._topics:
|
||||||
@ -852,17 +888,20 @@ class help(frontend.Local):
|
|||||||
if topic not in self.Command and len(commands) == 0:
|
if topic not in self.Command and len(commands) == 0:
|
||||||
raise HelpError(topic=topic)
|
raise HelpError(topic=topic)
|
||||||
|
|
||||||
writer(doc)
|
writer.append(doc)
|
||||||
if commands:
|
if commands:
|
||||||
writer()
|
writer.append()
|
||||||
writer(_('Topic commands:'))
|
writer.append(_('Topic commands:'))
|
||||||
for c in commands:
|
for c in commands:
|
||||||
writer(
|
writer.append(
|
||||||
' %s %s' % (to_cli(c.name).ljust(mcl), c.summary))
|
' {0} {1}'.format(
|
||||||
writer()
|
to_cli(c.name).ljust(mcl), c.summary))
|
||||||
writer(_('To get command help, use:'))
|
writer.append()
|
||||||
writer(_(' ipa <command> --help'))
|
writer.append(_('To get command help, use:'))
|
||||||
writer()
|
writer.append(_(' ipa <command> --help'))
|
||||||
|
writer.append()
|
||||||
|
writer.write()
|
||||||
|
|
||||||
|
|
||||||
class show_mappings(frontend.Command):
|
class show_mappings(frontend.Command):
|
||||||
"""
|
"""
|
||||||
|
@ -35,6 +35,10 @@ import dns
|
|||||||
import encodings
|
import encodings
|
||||||
import sys
|
import sys
|
||||||
import ssl
|
import ssl
|
||||||
|
import termios
|
||||||
|
import fcntl
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
from dns import resolver, rdatatype
|
from dns import resolver, rdatatype
|
||||||
@ -1157,3 +1161,40 @@ def no_matching_interface_for_ip_address_warning(addr_list):
|
|||||||
"{}".format(ip),
|
"{}".format(ip),
|
||||||
file=sys.stderr
|
file=sys.stderr
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_terminal_height(fd=1):
|
||||||
|
"""
|
||||||
|
Get current terminal height
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fd (int): file descriptor. Default: 1 (stdout)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Terminal height
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return struct.unpack(
|
||||||
|
'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, b'1234'))[0]
|
||||||
|
except (IOError, OSError, struct.error):
|
||||||
|
return os.environ.get("LINES", 25)
|
||||||
|
|
||||||
|
|
||||||
|
def open_in_pager(data):
|
||||||
|
"""
|
||||||
|
Open text data in pager
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (bytes): data to view in pager
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
pager = os.environ.get("PAGER", "less")
|
||||||
|
pager_process = subprocess.Popen([pager], stdin=subprocess.PIPE)
|
||||||
|
|
||||||
|
try:
|
||||||
|
pager_process.stdin.write(data)
|
||||||
|
pager_process.communicate()
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
Loading…
Reference in New Issue
Block a user