blob: 3fe3b288ae2e2bb0cfd0b9943e102817b7895da4 [file] [log] [blame]
#!/usr/bin/env python
"""Base script for generated CLI."""
import atexit
import code
import logging
import os
import readline
import rlcompleter
import sys
from google.apputils import appcommands
import gflags as flags
from apitools.base.py import encoding
from apitools.base.py import exceptions
__all__ = [
'ConsoleWithReadline',
'DeclareBaseFlags',
'FormatOutput',
'SetupLogger',
'run_main',
]
# TODO(craigcitro): We should move all the flags for the
# StandardQueryParameters into this file, so that they can be used
# elsewhere easily.
_BASE_FLAGS_DECLARED = False
_OUTPUT_FORMATTER_MAP = {
'protorpc': lambda x: x,
'json': encoding.MessageToJson,
}
def DeclareBaseFlags():
"""Declare base flags for all CLIs."""
# TODO(craigcitro): FlagValidators?
global _BASE_FLAGS_DECLARED # pylint: disable=global-statement
if _BASE_FLAGS_DECLARED:
return
flags.DEFINE_boolean(
'log_request', False,
'Log requests.')
flags.DEFINE_boolean(
'log_response', False,
'Log responses.')
flags.DEFINE_boolean(
'log_request_response', False,
'Log requests and responses.')
flags.DEFINE_enum(
'output_format',
'protorpc',
_OUTPUT_FORMATTER_MAP.keys(),
'Display format for results.')
_BASE_FLAGS_DECLARED = True
# NOTE: This is specified here so that it can be read by other files
# without depending on the flag to be registered.
TRACE_HELP = (
'A tracing token of the form "token:<tokenid>" '
'to include in api requests.')
FLAGS = flags.FLAGS
def SetupLogger():
if FLAGS.log_request or FLAGS.log_response or FLAGS.log_request_response:
logging.basicConfig()
logging.getLogger().setLevel(logging.INFO)
def FormatOutput(message, output_format=None):
"""Convert the output to the user-specified format."""
output_format = output_format or FLAGS.output_format
formatter = _OUTPUT_FORMATTER_MAP.get(FLAGS.output_format)
if formatter is None:
raise exceptions.UserError('Unknown output format: %s' % output_format)
return formatter(message)
class _SmartCompleter(rlcompleter.Completer):
def _callable_postfix(self, val, word):
if ('(' in readline.get_line_buffer() or
not callable(val)):
return word
else:
return word + '('
def complete(self, text, state):
if not readline.get_line_buffer().strip():
if not state:
return ' '
else:
return None
return rlcompleter.Completer.complete(self, text, state)
class ConsoleWithReadline(code.InteractiveConsole):
"""InteractiveConsole with readline, tab completion, and history."""
def __init__(self, env, filename='<console>', histfile=None):
new_locals = dict(env)
new_locals.update({
'_SmartCompleter': _SmartCompleter,
'readline': readline,
'rlcompleter': rlcompleter,
})
code.InteractiveConsole.__init__(self, new_locals, filename)
readline.parse_and_bind('tab: complete')
readline.set_completer(_SmartCompleter(new_locals).complete)
if histfile is not None:
histfile = os.path.expanduser(histfile)
if os.path.exists(histfile):
readline.read_history_file(histfile)
atexit.register(lambda: readline.write_history_file(histfile))
def run_main(): # pylint: disable=invalid-name
"""Function to be used as setuptools script entry point.
Appcommands assumes that it always runs as __main__, but launching
via a setuptools-generated entry_point breaks this rule. We do some
trickery here to make sure that appcommands and flags find their
state where they expect to by faking ourselves as __main__.
"""
# Put the flags for this module somewhere the flags module will look
# for them.
# pylint: disable=protected-access
new_name = flags._GetMainModule()
sys.modules[new_name] = sys.modules['__main__']
for flag in FLAGS.FlagsByModuleDict().get(__name__, []):
FLAGS._RegisterFlagByModule(new_name, flag)
for key_flag in FLAGS.KeyFlagsByModuleDict().get(__name__, []):
FLAGS._RegisterKeyFlagForModule(new_name, key_flag)
# pylint: enable=protected-access
# Now set __main__ appropriately so that appcommands will be
# happy.
sys.modules['__main__'] = sys.modules[__name__]
appcommands.Run()
sys.modules['__main__'] = sys.modules.pop(new_name)
if __name__ == '__main__':
appcommands.Run()