File: //lib/python2.7/site-packages/kobo/cli.py
# -*- coding: utf-8 -*-
"""
CommandOptionParser HOWTO
=========================
1) setup CommandContainer
-------------------------
# In case you don't need any special functionality, just import default CommandContainer.
# Otherwise it's recommended to inherit your own container and extend it's functionality.
# Typical use cases are shared configuration or shared XML-RPC connection.
from kobo.cli import CommandContainer
class MyCommandContainer(CommandContainer):
def __init__(self, *args, **kwargs):
CommandContainer.__init__(self, *args, **kwargs)
self.xmlrpc_client = ...
2) write your own Commands
--------------------------
# It usually makes sense to inherit directly from Command class.
# All common methods and attributes should be in the container.
# Specify any OptionParser options in options() method.
# OptionParser.parse_args() result is automatically passed to run(*args, **kwargs) method.
# A OptionParser instance os available in self.parser attribute.
class Make_Dirs(Command):
'''create directories'''
enabled = True
admin = False
def options(self):
self.parser.usage = "%%prog %s [options] <user>" % self.normalized_name
self.parser.add_option("-m", "--mode", help="set directory perms (0xxx)")
def run(self, *args, **kwargs):
if len(args) < 1:
self.parser.error("Please specify a directory")
mode = kwargs.pop("mode", "0755")
mode = int(mode, 0) # convert oct string to int
import os
for directory in args:
os.makedirs(directory, mode=mode)
3) register commands to a container
-----------------------------------
# Register either either all plugins (register_plugin)
# or all plugins in a module (register_module) to a container.
# All plugins must have enabled=True otherwise they won't be registered.
(My)CommandContainer.register_plugin(plugin_class)
(My)CommandContainer.register_module(module_with_plugins)
4) Use CommandOptionParser
--------------------------
command_container = (My)CommandContainer()
parser = CommandOptionParser(command_container=command_container)
parser.run()
# See kobo.client.main for slightly advanced example.
"""
import sys
import optparse
import datetime
from optparse import Option
from xmlrpclib import Fault
from kobo.plugins import Plugin, PluginContainer
from kobo.shortcuts import force_list
__all__ = (
"Command",
"CommandContainer",
"CommandOptionParser",
"Option",
"username_prompt",
"password_prompt",
"yes_no_prompt",
"are_you_sure_prompt",
)
def username_prompt(prompt=None, default_value=None):
"""Ask for a username."""
if default_value is not None:
return default_value
prompt = prompt or "Enter your username: "
print >>sys.stderr, prompt,
return sys.stdin.readline()
def password_prompt(prompt=None, default_value=None):
"""Ask for a password."""
import getpass
if default_value is not None:
return default_value
prompt = prompt or "Enter your password: "
try:
# try to use stderr stream
result = getpass.getpass(prompt, stream=sys.stderr)
except TypeError:
# fall back to stdout
result = getpass.getpass(prompt)
return result
def yes_no_prompt(prompt, default_value=None):
"""Give a yes/no (y/n) question."""
if default_value is not None:
if default_value not in ("Y", "N"):
raise ValueError("Invalid default value: %s" % default_value)
default_value = default_value.upper()
prompt = "%s [%s/%s]: " % (prompt, ("y", "Y")[default_value == "Y"], ("n", "N")[default_value == "N"])
print >>sys.stderr, prompt,
while True:
user_input = sys.stdin.readline().strip().upper()
if user_input == "" and default_value is not None:
user_input = default_value
if user_input == "Y":
return True
if user_input == "N":
return False
def are_you_sure_prompt(prompt=None):
"""Give a yes/no (y/n) question."""
prompt = prompt or "Are you sure? Enter 'YES' to continue: "
print >>sys.stderr, prompt,
user_input = sys.stdin.readline().strip()
if user_input == "YES":
return True
return False
class Command(Plugin):
"""An abstract class representing a command for CommandOptionParser."""
enabled = False
admin = False
username_prompt = staticmethod(username_prompt)
password_prompt = staticmethod(password_prompt)
yes_no_prompt = staticmethod(yes_no_prompt)
are_you_sure_prompt = staticmethod(are_you_sure_prompt)
def __init__(self, parser):
Plugin.__init__(self)
self.parser = parser
def options(self):
"""Add options to self.parser."""
pass
def run(self, *args, **kwargs):
"""Run a command. Arguments contain parsed options."""
raise NotImplementedError()
class CommandContainer(PluginContainer):
"""Container for Command classes."""
@classmethod
def normalize_name(cls, name):
"""Replace some characters in command names."""
return name.lower().replace('_', '-').replace(' ', '-')
class CommandOptionParser(optparse.OptionParser):
"""Enhanced OptionParser with plugin support."""
def __init__(self,
usage=None,
option_list=None,
option_class=Option,
version=None,
conflict_handler="error",
description=None,
formatter=None,
add_help_option=True,
prog=None,
command_container=None,
default_command="help",
add_username_password_options=False):
usage = usage or "%prog <command> [args] [--help]"
self.container = command_container
self.default_command = default_command
self.command = None
formatter = formatter or optparse.IndentedHelpFormatter(max_help_position=33)
optparse.OptionParser.__init__(self, usage, option_list, option_class, version, conflict_handler, description, formatter, add_help_option, prog)
if add_username_password_options:
option_list = [
optparse.Option("--username", help="specify user"),
optparse.Option("--password", help="specify password"),
]
self._populate_option_list(option_list, add_help=False)
def print_help(self, file=None, admin=False):
if file is None:
file = sys.stdout
file.write(self.format_help())
if self.command in (None, "help", "help-admin"):
file.write("\n")
file.write(self.format_help_commands(admin=admin))
def format_help_commands(self, admin=False):
commands = []
admin_commands = []
for name, plugin in sorted(self.container.plugins.iteritems()):
is_admin = getattr(plugin, "admin", False)
text = " %-30s %s" % (name, plugin.__doc__ or "")
if is_admin:
if admin:
admin_commands.append(text)
else:
commands.append(text)
if commands:
commands.insert(0, "commands:")
commands.append("")
if admin_commands:
admin_commands.insert(0, "admin commands:")
admin_commands.append("")
return "\n".join(commands + admin_commands)
def parse_args(self, args=None, values=None):
"""return (command_instance, opts, args)"""
args = self._get_args(args)
command = None
if len(args) > 0 and not args[0].startswith("-"):
command = args[0]
args = args[1:]
else:
command = self.default_command
# keep args as is
if not command in self.container.plugins:
self.error("unknown command: %s" % command)
CommandClass = self.container[command]
cmd = CommandClass(self)
if self.command != cmd.normalized_name:
self.command = cmd.normalized_name
cmd.options()
cmd_opts, cmd_args = optparse.OptionParser.parse_args(self, args, values)
return (cmd, cmd_opts, cmd_args)
def run(self, args=None, values=None):
"""parse arguments and run a command"""
cmd, cmd_opts, cmd_args = self.parse_args(args, values)
cmd_kwargs = cmd_opts.__dict__
cmd.run(*cmd_args, **cmd_kwargs)
class Help(Command):
"""show this help message and exit"""
enabled = True
def options(self):
pass
def run(self, *args, **kwargs):
self.parser.print_help(admin=False)
class Help_Admin(Command):
"""show help message about administrative commands and exit"""
enabled = True
def options(self):
# override default --help option
opt = self.parser.get_option("--help")
opt.action = "store_true"
opt.dest = "help"
def run(self, *args, **kwargs):
self.parser.print_help(admin=True)
class Help_RST(Command):
"""print program usage as reStructuredText"""
enabled = True
def options(self):
pass
def run(self, *args, **kwargs):
prog = self.parser.get_prog_name()
print ".. -*- coding: utf-8 -*-"
print
print "=" * len(prog)
print prog
print "=" * len(prog)
print
# add subtitle (command description)
description = getattr(self.parser.container, "_description", None)
if description:
print ":Subtitle: %s" % description
print
# add copyright
copyright = getattr(self.parser.container, "_copyright", None)
if copyright:
print ":Copyright: %s" % copyright
# add date
print ":Date: $Date: %s $" % datetime.datetime.strftime(datetime.datetime.utcnow(), format="%F %X")
print
print "--------"
print "COMMANDS"
print "--------"
for command_name, CommandClass in sorted(self.parser.container.plugins.items()):
parser = optparse.OptionParser(usage=self.parser.usage)
cmd = CommandClass(parser)
cmd.normalized_name = command_name
cmd.options()
cmd.container = self.parser.container
cmd_opts, cmd_args = parser.parse_args()
print command_name
print "-" * len(command_name)
if cmd.admin:
print "[ADMIN ONLY]",
print cmd.__doc__.strip()
print
usage = parser.get_usage().strip().replace("Usage: ", "**Usage:** ", 1)
if usage:
print usage
print
for opt in sorted(parser.option_list, lambda x, y: cmp(str(x), str(y))):
if "-h/--help" in str(opt):
continue
if opt.nargs:
metavar = opt.metavar or opt.dest.upper()
opt_list = []
for opt_str in opt._short_opts + opt._long_opts:
if opt.nargs is not None:
opt_list.append("%s=%s" % (opt_str, metavar))
else:
opt_list.append(opt_str)
print "/".join(opt_list)
print " %s" % opt.help
if opt.action == "append":
print
print " This option can be specified multiple times"
print
print
# handle :Contact: and :Author: ourselves
authors = force_list(getattr(self.parser.container, "_authors", []))
contact = getattr(self.parser.container, "_contact", None)
if authors or contact:
print "-------"
print "AUTHORS"
print "-------"
for author in sorted(authors):
print "- %s" % author
print
if contact:
print "**Contact:** %s" % contact
print
CommandContainer.register_plugin(Help)
CommandContainer.register_plugin(Help_Admin)
CommandContainer.register_plugin(Help_RST)