# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Common code for ADB and Fastboot CLI.
Usage introspects the given class for methods, args, and docs to show the user.
:func:`StartCli` handles connecting to a device, calling the expected method, and
outputting the results.
.. rubric:: Contents
* :func:`_DocToArgs`
* :class:`_PortPathAction`
* :func:`_RunMethod`
* :func:`GetCommonArguments`
* :func:`GetDeviceArguments`
* :func:`MakeSubparser`
* :class:`PositionalArg`
* :func:`StartCli`
"""
from __future__ import print_function
import argparse
import io
import inspect
import logging
import re
import sys
import types
from adb import usb_exceptions
[docs]class _PortPathAction(argparse.Action):
"""TODO
.. image:: _static/adb.common_cli._PortPathAction.CALL_GRAPH.svg
"""
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, [int(i) for i in values.replace('/', ',').split(',')])
[docs]class PositionalArg(argparse.Action):
"""A positional CLI argument.
.. image:: _static/adb.common_cli.PositionalArg.CALL_GRAPH.svg
"""
def __call__(self, parser, namespace, values, option_string=None):
namespace.positional.append(values)
[docs]def GetDeviceArguments():
"""Return a parser for device-related CLI commands.
Returns
-------
group : argparse.ArgumentParser
A parser for device-related CLI commands.
"""
group = argparse.ArgumentParser('Device', add_help=False)
group.add_argument('--timeout_ms', default=10000, type=int, metavar='10000', help='Timeout in milliseconds.')
group.add_argument('--port_path', action=_PortPathAction, help='USB port path integers (eg 1,2 or 2,1,1)')
group.add_argument('-s', '--serial', help='Device serial to look for (host:port or USB serial)')
return group
[docs]def GetCommonArguments():
"""Return a parser for common CLI commands.
Returns
-------
group : argparse.ArgumentParser
A parser for common CLI commands.
"""
group = argparse.ArgumentParser('Common', add_help=False)
group.add_argument('--verbose', action='store_true', help='Enable logging')
return group
[docs]def _DocToArgs(doc):
"""Converts a docstring documenting arguments into a dict.
.. image:: _static/adb.common_cli._DocToArgs.CALLER_GRAPH.svg
Parameters
----------
doc : str
The docstring for a method; see `MakeSubparser`.
Returns
-------
out : dict
A dictionary of arguments and their descriptions from the docstring ``doc``.
"""
offset = None
param = None
out = {}
for l in doc.splitlines():
if l.strip() == 'Parameters':
offset = len(l.rstrip()) - len(l.strip())
elif offset:
# The "----------" line
if l.strip() == '-' * len('Parameters'):
continue
if l.strip() in ['Returns', 'Yields', 'Raises']:
break
# start of a parameter
if len(l.rstrip()) - len(l.strip()) == offset:
param = l.strip().split()[0]
out[param] = ''
# add to a parameter
elif l.strip():
if out[param]:
out[param] += ' ' + l.strip()
else:
out[param] = l.strip()
return out
[docs]def MakeSubparser(subparsers, parents, method, arguments=None):
"""Returns an argparse subparser to create a 'subcommand' to adb.
.. image:: _static/adb.common_cli.MakeSubparser.CALL_GRAPH.svg
Parameters
----------
subparsers : TODO
TODO
parents : TODO
TODO
method : TODO
TODO
arguments : TODO, None
TODO
Returns
-------
subparser : TODO
TODO
"""
name = ('-'.join(re.split(r'([A-Z][a-z]+)', method.__name__)[1:-1:2])).lower()
help = method.__doc__.splitlines()[0] # pylint: disable=redefined-builtin
subparser = subparsers.add_parser(name=name, description=help, help=help.rstrip('.'), parents=parents)
subparser.set_defaults(method=method, positional=[])
argspec = inspect.getfullargspec(method)
# Figure out positionals and default argument, if any. Explicitly includes
# arguments that default to '' but excludes arguments that default to None.
offset = len(argspec.args) - len(argspec.defaults or []) - 1
positional = []
for i in range(1, len(argspec.args)):
if i > offset and argspec.defaults[i - offset - 1] is None:
break
positional.append(argspec.args[i])
defaults = [None] * offset + list(argspec.defaults or [])
# Add all arguments so they append to args.positional.
args_help = _DocToArgs(method.__doc__)
for name, default in zip(positional, defaults):
if not isinstance(default, (None.__class__, str)):
continue
subparser.add_argument(
name, help=(arguments or {}).get(name, args_help.get(name)),
default=default, nargs='?' if default is not None else None,
action=PositionalArg)
if argspec.varargs:
subparser.add_argument(
argspec.varargs, nargs=argparse.REMAINDER,
help=(arguments or {}).get(argspec.varargs, args_help.get(argspec.varargs)))
return subparser
[docs]def _RunMethod(dev, args, extra):
"""Runs a method registered via :func:`MakeSubparser`.
.. image:: _static/adb.common_cli._RunMethod.CALLER_GRAPH.svg
Parameters
----------
dev : TODO
TODO
args : TODO
TODO
extra : TODO
TODO
Returns
-------
int
0
"""
logging.info('%s(%s)', args.method.__name__, ', '.join(args.positional))
result = args.method(dev, *args.positional, **extra)
if result is not None:
if isinstance(result, io.StringIO):
sys.stdout.write(result.getvalue())
elif isinstance(result, (list, types.GeneratorType)):
r = ''
for r in result:
r = str(r)
sys.stdout.write(r)
if not r.endswith('\n'):
sys.stdout.write('\n')
else:
result = str(result)
sys.stdout.write(result)
if not result.endswith('\n'):
sys.stdout.write('\n')
return 0
[docs]def StartCli(args, adb_commands, extra=None, **device_kwargs):
"""Starts a common CLI interface for this usb path and protocol.
Handles connecting to a device, calling the expected method, and outputting the results.
.. image:: _static/adb.common_cli.StartCli.CALL_GRAPH.svg
Parameters
----------
args : TODO
TODO
adb_commands : TODO
TODO
extra : TODO, None
TODO
**device_kwargs : TODO
TODO
Returns
-------
TODO
TODO
"""
try:
dev = adb_commands()
dev.ConnectDevice(port_path=args.port_path, serial=args.serial, default_timeout_ms=args.timeout_ms,
**device_kwargs)
except usb_exceptions.DeviceNotFoundError as e:
print('No device found: {}'.format(e), file=sys.stderr)
return 1
except usb_exceptions.CommonUsbError as e:
print('Could not connect to device: {}'.format(e), file=sys.stderr)
return 1
try:
return _RunMethod(dev, args, extra or {})
except Exception as e: # pylint: disable=broad-except
sys.stdout.write(str(e))
return 1
finally:
dev.Close()