This repository was archived by the owner on Jan 10, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 365
Expand file tree
/
Copy pathcommon_cli.py
More file actions
165 lines (143 loc) · 5.78 KB
/
common_cli.py
File metadata and controls
165 lines (143 loc) · 5.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# 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
import traceback
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(traceback.format_exc())
return 1
finally:
dev.Close()