• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2015 Google Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Base script for generated CLI."""
18
19from __future__ import absolute_import
20
21import atexit
22import code
23import logging
24import os
25import readline
26import rlcompleter
27import sys
28
29import gflags as flags
30from google.apputils import appcommands
31
32from apitools.base.py import encoding
33from apitools.base.py import exceptions
34
35__all__ = [
36    'ConsoleWithReadline',
37    'DeclareBaseFlags',
38    'FormatOutput',
39    'SetupLogger',
40    'run_main',
41]
42
43
44# TODO(craigcitro): We should move all the flags for the
45# StandardQueryParameters into this file, so that they can be used
46# elsewhere easily.
47
48_BASE_FLAGS_DECLARED = False
49_OUTPUT_FORMATTER_MAP = {
50    'protorpc': lambda x: x,
51    'json': encoding.MessageToJson,
52}
53
54
55def DeclareBaseFlags():
56    """Declare base flags for all CLIs."""
57    # TODO(craigcitro): FlagValidators?
58    global _BASE_FLAGS_DECLARED  # pylint: disable=global-statement
59    if _BASE_FLAGS_DECLARED:
60        return
61    flags.DEFINE_boolean(
62        'log_request', False,
63        'Log requests.')
64    flags.DEFINE_boolean(
65        'log_response', False,
66        'Log responses.')
67    flags.DEFINE_boolean(
68        'log_request_response', False,
69        'Log requests and responses.')
70    flags.DEFINE_enum(
71        'output_format',
72        'protorpc',
73        _OUTPUT_FORMATTER_MAP.keys(),
74        'Display format for results.')
75
76    _BASE_FLAGS_DECLARED = True
77
78FLAGS = flags.FLAGS
79
80
81def SetupLogger():
82    if FLAGS.log_request or FLAGS.log_response or FLAGS.log_request_response:
83        logging.basicConfig()
84        logging.getLogger().setLevel(logging.INFO)
85
86
87def FormatOutput(message, output_format=None):
88    """Convert the output to the user-specified format."""
89    output_format = output_format or FLAGS.output_format
90    formatter = _OUTPUT_FORMATTER_MAP.get(FLAGS.output_format)
91    if formatter is None:
92        raise exceptions.UserError('Unknown output format: %s' % output_format)
93    return formatter(message)
94
95
96class _SmartCompleter(rlcompleter.Completer):
97
98    def _callable_postfix(self, val, word):
99        if ('(' in readline.get_line_buffer() or
100                not callable(val)):
101            return word
102        return word + '('
103
104    def complete(self, text, state):
105        if not readline.get_line_buffer().strip():
106            if not state:
107                return '  '
108            return None
109        return rlcompleter.Completer.complete(self, text, state)
110
111
112class ConsoleWithReadline(code.InteractiveConsole):
113
114    """InteractiveConsole with readline, tab completion, and history."""
115
116    def __init__(self, env, filename='<console>', histfile=None):
117        new_locals = dict(env)
118        new_locals.update({
119            '_SmartCompleter': _SmartCompleter,
120            'readline': readline,
121            'rlcompleter': rlcompleter,
122        })
123        code.InteractiveConsole.__init__(self, new_locals, filename)
124        readline.parse_and_bind('tab: complete')
125        readline.set_completer(_SmartCompleter(new_locals).complete)
126        if histfile is not None:
127            histfile = os.path.expanduser(histfile)
128            if os.path.exists(histfile):
129                readline.read_history_file(histfile)
130            atexit.register(lambda: readline.write_history_file(histfile))
131
132
133def run_main():  # pylint: disable=invalid-name
134    """Function to be used as setuptools script entry point.
135
136    Appcommands assumes that it always runs as __main__, but launching
137    via a setuptools-generated entry_point breaks this rule. We do some
138    trickery here to make sure that appcommands and flags find their
139    state where they expect to by faking ourselves as __main__.
140    """
141
142    # Put the flags for this module somewhere the flags module will look
143    # for them.
144    # pylint: disable=protected-access
145    new_name = flags._GetMainModule()
146    sys.modules[new_name] = sys.modules['__main__']
147    for flag in FLAGS.FlagsByModuleDict().get(__name__, []):
148        FLAGS._RegisterFlagByModule(new_name, flag)
149        for key_flag in FLAGS.KeyFlagsByModuleDict().get(__name__, []):
150            FLAGS._RegisterKeyFlagForModule(new_name, key_flag)
151    # pylint: enable=protected-access
152
153    # Now set __main__ appropriately so that appcommands will be
154    # happy.
155    sys.modules['__main__'] = sys.modules[__name__]
156    appcommands.Run()
157    sys.modules['__main__'] = sys.modules.pop(new_name)
158
159
160if __name__ == '__main__':
161    appcommands.Run()
162