Source code for pypsi.commands.help

#
# Copyright (c) 2015, Adam Meily <meily.adam@gmail.com>
# Pypsi - https://github.com/ameily/pypsi
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

from pypsi.core import Command, PypsiArgParser, CommandShortCircuit
from pypsi.format import Table, Column, title_str
from pypsi.ansi import AnsiCodes
from pypsi.completers import command_completer
import sys


class Topic(object):

    def __init__(self, id, name=None, content=None, commands=None):
        self.id = id
        self.name = name or ''
        self.content = content or ''
        self.commands = commands or []


[docs]class HelpCommand(Command): ''' Provides access to manpage-esque topics and command usage information. ''' def __init__(self, name='help', topic='shell', brief='print information on a topic or command', topics=None, vars=None, **kwargs): self.parser = PypsiArgParser( prog=name, description=brief ) # Add a callback to self.complete_topics so tab-complete can # return the possible topics you may get help on self.parser.add_argument( "topic", metavar="TOPIC", help="command or topic to print", nargs='?', completer=self.complete_topics ) super(HelpCommand, self).__init__( name=name, brief=brief, usage=self.parser.format_help(), topic=topic, **kwargs ) self.vars = vars or {} self.topics = topics def setup(self, shell): shell.ctx.topics = list(self.topics or []) shell.ctx.uncat_topic = Topic('uncat', 'Uncategorized Commands & Topics') shell.ctx.topic_lookup = {t.id: t for t in shell.ctx.topics} shell.ctx.topics_dirty = True def complete_topics(self, shell, args, prefix): completions = [ x.id for x in shell.ctx.topics if x.id.startswith(prefix) or not prefix ] completions.extend([ x for x in shell.commands if x.startswith(prefix) or not prefix ]) return sorted(completions) def complete(self, shell, args, prefix): if shell.ctx.topics_dirty: self.reload(shell) # The command_completer function takes in the parser, automatically # completes optional arguments (ex, '-v'/'--verbose') or sub-commands, # and complete any arguments' values by calling a callback function # with the same arguments as complete if the callback was defined # when the parser was created. return command_completer(self.parser, shell, args, prefix) def reload(self, shell): shell.ctx.uncat_topic.commands = [] for id in shell.ctx.topic_lookup: shell.ctx.topic_lookup[id].commands = [] for (name, cmd) in shell.commands.items(): if cmd.topic == '__hidden__': continue if cmd.topic: if cmd.topic in shell.ctx.topic_lookup: shell.ctx.topic_lookup[cmd.topic].commands.append(cmd) else: self.add_topic(shell, Topic(cmd.topic, commands=[cmd])) else: shell.ctx.uncat_topic.commands.append(cmd) shell.ctx.topics_dirty = False for topic in shell.ctx.topics: if topic.commands: topic.commands = sorted(topic.commands, key=lambda x: x.name) def add_topic(self, shell, topic): shell.ctx.topics_dirty = True shell.ctx.topic_lookup[topic.id] = topic shell.ctx.topics.append(topic) def print_topic_commands(self, shell, topic, title=None, name_col_width=20): print( AnsiCodes.yellow, title_str(title or topic.name or topic.id, shell.width), AnsiCodes.reset, sep='' ) print(AnsiCodes.yellow, end='') Table( columns=(Column(''), Column('', Column.Grow)), spacing=4, header=False, width=shell.width ).extend(*[ (' ' + c.name.ljust(name_col_width - 1), c.brief or '') for c in topic.commands ]).write(sys.stdout) print(AnsiCodes.reset, end='') def print_topics(self, shell): max_name_width = 0 for topic in shell.ctx.topics: for c in topic.commands: max_name_width = max(len(c.name), max_name_width) for c in shell.ctx.uncat_topic.commands: max_name_width = max(len(c.name), max_name_width) addl = [] for topic in shell.ctx.topics: if topic.content or not topic.commands: addl.append(topic) if topic.commands: self.print_topic_commands(shell, topic, name_col_width=max_name_width) print() if shell.ctx.uncat_topic.commands: self.print_topic_commands(shell, shell.ctx.uncat_topic, name_col_width=max_name_width) print() if addl: addl = sorted(addl, key=lambda x: x.id) print( AnsiCodes.yellow, title_str("Additional Topics", shell.width), sep='' ) Table( columns=(Column(''), Column('', Column.Grow)), spacing=4, header=False, width=shell.width ).extend(*[ (' ' + topic.id.ljust(max_name_width - 1), topic.name or '') for topic in addl ]).write(sys.stdout) print(AnsiCodes.reset) def print_topic(self, shell, id): if id not in shell.ctx.topic_lookup: if id in shell.commands: cmd = shell.commands[id] print(AnsiCodes.yellow, cmd.usage, AnsiCodes.reset, sep='') return 0 self.error(shell, "unknown topic: ", id) return -1 topic = shell.ctx.topic_lookup[id] if topic.content: print(title_str(topic.name or topic.id, shell.width)) try: cnt = topic.content.format(**self.vars) except: cnt = topic.content print(cnt) print() if topic.commands: self.print_topic_commands(shell, topic, "Commands") return 0 def run(self, shell, args): if shell.ctx.topics_dirty: self.reload(shell) try: ns = self.parser.parse_args(args) except CommandShortCircuit as e: return e.code rc = 0 if not ns.topic: self.print_topics(shell) else: rc = self.print_topic(shell, ns.topic) return rc