# 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. StartCli handles connecting to a device, calling the expected method, and outputting the results. """ from __future__ import print_function import argparse import io import inspect import logging import re import sys import types from adb import usb_exceptions class _PortPathAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr( namespace, self.dest, [int(i) for i in values.replace('/', ',').split(',')]) class PositionalArg(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): namespace.positional.append(values) def GetDeviceArguments(): 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 def GetCommonArguments(): group = argparse.ArgumentParser('Common', add_help=False) group.add_argument('--verbose', action='store_true', help='Enable logging') return group def _DocToArgs(doc): """Converts a docstring documenting arguments into a dict.""" m = None offset = None in_arg = False out = {} for l in doc.splitlines(): if l.strip() == 'Args:': in_arg = True elif in_arg: if not l.strip(): break if offset is None: offset = len(l) - len(l.lstrip()) l = l[offset:] if l[0] == ' ' and m: out[m.group(1)] += ' ' + l.lstrip() else: m = re.match(r'^([a-z_]+): (.+)$', l.strip()) out[m.group(1)] = m.group(2) return out def MakeSubparser(subparsers, parents, method, arguments=None): """Returns an argparse subparser to create a 'subcommand' to adb.""" name = ('-'.join(re.split(r'([A-Z][a-z]+)', method.__name__)[1:-1:2])).lower() help = method.__doc__.splitlines()[0] subparser = subparsers.add_parser( name=name, description=help, help=help.rstrip('.'), parents=parents) subparser.set_defaults(method=method, positional=[]) argspec = inspect.getargspec(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 def _RunMethod(dev, args, extra): """Runs a method registered via MakeSubparser.""" 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 def StartCli(args, adb_commands, extra=None, **device_kwargs): """Starts a common CLI interface for this usb path and protocol.""" 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()