Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 1 | # Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 4 | """Common code for servo parsing support.""" |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 5 | |
| 6 | import argparse |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 7 | import logging |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 8 | import os |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 9 | import textwrap |
| 10 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 11 | import client |
| 12 | import servo_logging |
| 13 | import servodutil |
| 14 | |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 15 | if os.getuid(): |
| 16 | DEFAULT_RC_FILE = '/home/%s/.servodrc' % os.getenv('USER', '') |
| 17 | else: |
| 18 | DEFAULT_RC_FILE = '/home/%s/.servodrc' % os.getenv('SUDO_USER', '') |
| 19 | |
| 20 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 21 | PORT_ENV_VAR = 'SERVOD_PORT' |
| 22 | NAME_ENV_VAR = 'SERVOD_NAME' |
| 23 | |
| 24 | |
| 25 | ARG_BY_USER_MARKER = 'supplied_by_user' |
| 26 | |
| 27 | |
| 28 | def ArgMarkedAsUserSupplied(namespace, arg_name): |
| 29 | """Query whether an argument that uses StoreAndMarkAction is user supplied.""" |
| 30 | marker_name = '%s_%s' % (arg_name, ARG_BY_USER_MARKER) |
| 31 | return hasattr(namespace, marker_name) |
| 32 | |
| 33 | # pylint: disable=protected-access |
| 34 | # Need to expand the StoreAction of the parser. |
| 35 | class StoreAndMarkAction(argparse._StoreAction): |
| 36 | """Helper to mark arguments whether they were supplied by the user. |
| 37 | |
| 38 | If an argument is supplied by the user instead of using defaults or RC, |
| 39 | add another option with the name |arg|_supplied_by_user. |
| 40 | """ |
| 41 | |
| 42 | def __call__(self, parser, namespace, values, option_string=None): |
| 43 | """Extend default __call__ implementation.""" |
| 44 | # This sets the |values| to |self.dest|. |
| 45 | super(StoreAndMarkAction, self).__call__(parser=parser, namespace=namespace, |
| 46 | values=values, |
| 47 | option_string=option_string) |
| 48 | marker_name = '%s_%s' % (self.dest, ARG_BY_USER_MARKER) |
| 49 | setattr(namespace, marker_name, True) |
| 50 | |
| 51 | |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 52 | class ServodParserHelpFormatter(argparse.RawDescriptionHelpFormatter, |
| 53 | argparse.ArgumentDefaultsHelpFormatter): |
| 54 | """Servod help formatter. |
| 55 | |
| 56 | Combines ability for raw description printing (needed to have control over |
| 57 | how to print examples) and default argument printing, printing the default |
| 58 | which each argument. |
| 59 | """ |
| 60 | pass |
| 61 | |
| 62 | |
| 63 | class ServodParserError(Exception): |
| 64 | """Error class for Servod parsing errors.""" |
| 65 | pass |
| 66 | |
| 67 | |
| 68 | class _BaseServodParser(argparse.ArgumentParser): |
| 69 | """Extension to ArgumentParser that allows for examples in the description. |
| 70 | |
| 71 | _BaseServodParser allows for a list of example tuples, where |
| 72 | element[0]: is the cmdline invocation |
| 73 | element[1]: is a comment to explain what the invocation does. |
| 74 | |
| 75 | For example (loosely based on servod.) |
| 76 | ('-b board', 'Start servod with the configuation for board |board|') |
| 77 | would print the following help message: |
| 78 | ... |
| 79 | |
| 80 | Examples: |
| 81 | > servod -b board |
| 82 | Start servod with the configuration for board |board| |
| 83 | |
| 84 | Optional Arguments... |
| 85 | |
| 86 | see servod, or dut_control for more examples. |
| 87 | """ |
| 88 | |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 89 | def __init__(self, description='', examples=None, **kwargs): |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 90 | """Initialize _BaseServodParser by setting description and formatter. |
| 91 | |
| 92 | Args: |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 93 | description: description of the program |
| 94 | examples: list of tuples where the first element is the cmdline example, |
| 95 | and the second element is a comment explaining the example. |
| 96 | %(prog)s will be prepended to each example if it does not |
| 97 | start with %(prog)s. |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 98 | **kwargs: keyword arguments forwarded to ArgumentParser |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 99 | """ |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 100 | # Initialize logging up here first to ensure log messages from parsing |
| 101 | # can go through. |
| 102 | loglevel, fmt = servo_logging.LOGLEVEL_MAP[servo_logging.DEFAULT_LOGLEVEL] |
| 103 | logging.basicConfig(loglevel=loglevel, format=fmt) |
| 104 | self._logger = logging.getLogger(type(self).__name__) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 105 | # Generate description. |
| 106 | description_lines = textwrap.wrap(description) |
| 107 | # Setting it into the kwargs here ensures that we overwrite an potentially |
| 108 | # passed in and undesired formatter class. |
| 109 | kwargs['formatter_class'] = ServodParserHelpFormatter |
| 110 | if examples: |
| 111 | # Extra newline to separate description from examples. |
| 112 | description_lines.append('\n') |
| 113 | description_lines.append('Examples:') |
| 114 | for example, comment in examples: |
| 115 | if not example.startswith('%(prog)s'): |
| 116 | example = '%(prog)s ' + example |
| 117 | example_lines = [' > ' + example] |
| 118 | example_lines.extend(textwrap.wrap(comment)) |
| 119 | description_lines.append('\n\t'.join(example_lines)) |
| 120 | description = '\n'.join(description_lines) |
| 121 | kwargs['description'] = description |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 122 | super(_BaseServodParser, self).__init__(**kwargs) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 123 | |
| 124 | |
| 125 | class BaseServodParser(_BaseServodParser): |
| 126 | """BaseServodParser handling common arguments in the servod cmdline tools.""" |
| 127 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 128 | def __init__(self, add_port=True, **kwargs): |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 129 | """Initialize by adding common arguments. |
| 130 | |
| 131 | Adds: |
| 132 | - host/port arguments to find/initialize a servod instance |
| 133 | - debug argument to toggle debug message printing |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 134 | |
| 135 | Args: |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 136 | add_port: bool, whether to add --port to the parser. A caller might want |
| 137 | to add port themselves either to rename it (servod-port), |
| 138 | or to create mutual exclusion with serialname and name (clients) |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 139 | **kwargs: keyword arguments forwarded to _BaseServodParser |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 140 | """ |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 141 | super(BaseServodParser, self).__init__(**kwargs) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 142 | self.add_argument('-d', '--debug', action='store_true', default=False, |
| 143 | help='enable debug messages') |
| 144 | self.add_argument('--host', default='localhost', type=str, |
| 145 | help='hostname of the servod server.') |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 146 | if add_port: |
| 147 | BaseServodParser.AddRCEnabledPortArg(self) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 148 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 149 | @staticmethod |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 150 | def AddRCEnabledPortArg(parser, port_flags=['-p', '--port']): |
| 151 | """Add the port to the argparser. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 152 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 153 | Set the default to environment variable ENV_PORT_NAME if defined |
| 154 | |
| 155 | Note: while this helper does allow for arbitrary flags for the port |
| 156 | variable, the destination is still set to 'port'. It's on the caller to |
| 157 | ensure that there is no conflict. |
Ruben Rodriguez Buchillon | 0902f09 | 2018-09-19 15:03:17 +0800 | [diff] [blame] | 158 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 159 | Args: |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 160 | parser: parser or group to add argument to |
| 161 | port_flags: optional, list, if the flags for the port should be different |
| 162 | than the default ones. |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 163 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 164 | # pylint: disable=dangerous-default-value |
| 165 | # Having the default flags here simplifies the code logic. |
| 166 | default = os.environ.get(PORT_ENV_VAR, client.DEFAULT_PORT) |
| 167 | parser.add_argument(*port_flags, default=default, type=int, dest='port', |
| 168 | action=StoreAndMarkAction, |
| 169 | help='port of the servod server. Can also be supplied ' |
| 170 | 'through environment variable ' + PORT_ENV_VAR) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 171 | |
| 172 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 173 | class ServodRCParser(_BaseServodParser): |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 174 | """Base class to build Servod parsers to natively handle servorc. |
| 175 | |
| 176 | This class overwrites parse_args & parse_known_args to: |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 177 | - handle NAME_ENV_VAR environment variable |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 178 | - parse & substitute in the servorc file on matches |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 179 | """ |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 180 | |
| 181 | def __init__(self, **kwargs): |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 182 | super(ServodRCParser, self).__init__(**kwargs) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 183 | self.add_argument('--rcfile', type=str, default=DEFAULT_RC_FILE, |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 184 | help='servo description file for multi-servo operation.') |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 185 | # name and serialname are both ways to ID a servo device |
| 186 | self._id_group = self.add_mutually_exclusive_group() |
| 187 | self._id_group.add_argument('-s', '--serialname', default=None, type=str, |
| 188 | help='device serialname stored in eeprom.') |
| 189 | ServodRCParser.AddRCEnabledNameArg(self._id_group) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 190 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 191 | @staticmethod |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 192 | def AddRCEnabledNameArg(parser, name_flags=['-n', '--name']): |
| 193 | """Add the name to the argparser. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 194 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 195 | Set the default to environment variable ENV_VAR_NAME if defined |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 196 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 197 | Note: while this helper does allow for arbitrary flags for the name |
| 198 | variable, the destination is still set to 'name'. It's on the caller to |
| 199 | ensure that there is no conflict. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 200 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 201 | Args: |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 202 | parser: parser or group to add argument to |
| 203 | name_flags: optional, list, if the flags for the name should be different |
| 204 | than the default ones. |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 205 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 206 | # pylint: disable=dangerous-default-value |
| 207 | # Having the default flags here simplifies the code logic. |
| 208 | default = os.environ.get(NAME_ENV_VAR, '') |
| 209 | parser.add_argument(*name_flags, default=default, type=str, dest='name', |
| 210 | help='symbolic name of the servo board, ' |
| 211 | 'used as a config shortcut, could also be supplied ' |
| 212 | 'through environment variable ' + NAME_ENV_VAR) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 213 | |
| 214 | @staticmethod |
| 215 | def PostProcessRCElements(options, rcpath=None, logger=logging): |
| 216 | """Handle 'name' in |options| by substituting it with the intended config. |
| 217 | |
| 218 | This replaces the name option in the options with the intended serialname |
| 219 | for that name if one can be found. If a board file is also specified in the |
| 220 | rc file it appends that to the options too, which can be ignored if not |
| 221 | needed. |
| 222 | |
| 223 | Note: this function changes the content of options. |
| 224 | |
| 225 | Args: |
| 226 | options: argparse Namespace of options to process. |
| 227 | rcpath: optional rcfile path if it's not stored under options.rcfile |
| 228 | logger: logger instance to use |
| 229 | |
| 230 | Returns: |
| 231 | Reference back to the same options passed in. |
| 232 | |
| 233 | Raises: |
| 234 | ServodParserError: if -n/--name and -s/--serialname both defined |
| 235 | ServodParserError: if name in options doesn't show up in servodrc |
| 236 | """ |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 237 | if not rcpath: |
| 238 | rcpath = options.rcfile |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 239 | rcd = ServodRCParser.ParseRC(rcpath, logger=logger) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 240 | rc = None |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 241 | if not options.serialname and options.name: |
| 242 | # |name| can be set through the commandline or through an environment |
| 243 | # variable. If it's set through the commandline, serialname cannot have |
| 244 | # been set. However, if serialname is set and name is also set (through |
| 245 | # the environment variable) name gets ignored. |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 246 | if options.name not in rcd: |
| 247 | raise ServodParserError('Name %r not in rc at %r' % (options.name, |
| 248 | rcpath)) |
| 249 | rc = rcd[options.name] |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 250 | # For an rc to exist, 'sn' has to be a part of it |
| 251 | setattr(options, 'serialname', rc['sn']) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 252 | elif options.serialname: |
| 253 | # srcs meaning serialname runtime configurations (rcs). |
| 254 | srcs = [(name, rc) for name, rc in rcd.iteritems() if |
| 255 | rc['sn'] == options.serialname] |
| 256 | if srcs: |
| 257 | logger.info('Found servodrc entry %r for serialname %r. Using it.', |
| 258 | srcs[0][0], options.serialname) |
| 259 | rc = srcs[0][1] |
| 260 | if rc: |
| 261 | for elem in ['board', 'model']: |
| 262 | # Unlike serialname explicit overwrites of board and model in the |
| 263 | # cmdline are fine as the name flag is still useful to refer to a |
| 264 | # serialname. |
| 265 | if elem in rc and hasattr(options, elem): |
| 266 | if not getattr(options, elem): |
| 267 | logger.info('Setting %r to %r in the options as indicated by ' |
| 268 | 'servodrc file.', elem, rc[elem]) |
| 269 | setattr(options, elem, rc[elem]) |
| 270 | else: |
| 271 | if getattr(options, elem) != rc[elem]: |
| 272 | logger.warning('Ignoring rc configured %r name %r for servo %r. ' |
| 273 | 'Option already defined on the command line as %r', |
| 274 | elem, rc[elem], rc['sn'], getattr(options, elem)) |
| 275 | return options |
| 276 | |
| 277 | def parse_known_args(self, args=None, namespace=None): |
| 278 | """Overwrite from Argumentparser to handle servo rc. |
| 279 | |
| 280 | Note: this also overwrites parse_args as parse_args just calls |
| 281 | parse_known_args and throws an error if there's anything inside of |
| 282 | xtra. |
| 283 | |
| 284 | Args: |
| 285 | args: list of cmdline elements |
| 286 | namespace: namespace to place the results into |
| 287 | |
| 288 | Returns: |
| 289 | tuple (options, xtra) the result from parsing known args |
| 290 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 291 | opts, xtra = _BaseServodParser.parse_known_args(self, args=args, |
| 292 | namespace=namespace) |
| 293 | opts = ServodRCParser.PostProcessRCElements(opts, logger=self._logger) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 294 | return (opts, xtra) |
| 295 | |
| 296 | @staticmethod |
| 297 | def ParseRC(rc_file, logger=logging): |
| 298 | """Parse servodrc configuration file. |
| 299 | |
| 300 | The format of the configuration file is described above in comments to |
| 301 | DEFAULT_RC_FILE. If the file is not found or is mis-formatted, a warning is |
| 302 | printed but the program tries to continue. |
| 303 | |
| 304 | Args: |
| 305 | rc_file: a string, name of the file storing the configuration |
| 306 | logger: logger instance to use |
| 307 | |
| 308 | Returns: |
| 309 | a dictionary, where keys are symbolic servo names, and values are |
| 310 | dictionaries representing servo parameters read from the config file, |
| 311 | keyed by strings 'sn' (for serial number), 'port', 'board', and 'model'. |
| 312 | """ |
| 313 | |
| 314 | if not os.path.isfile(rc_file): |
| 315 | return {} |
| 316 | rcd = {} # Dictionary representing the rc file contents. |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 317 | attributes = ['name', 'sn', 'port', 'board', 'model'] |
| 318 | # These attributes have to be defined for a line to be valid. |
| 319 | required_attributes = ['name', 'sn'] |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 320 | with open(rc_file) as f: |
| 321 | for rc_line in f: |
| 322 | line = rc_line.split('#')[0].strip() |
| 323 | if not line: |
| 324 | continue |
| 325 | elts = [x.strip() for x in line.split(',')] |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 326 | if len(elts) < len(required_attributes): |
| 327 | logger.warning('ignoring rc line %r. Not all required ' |
| 328 | 'attributes defined %r.', rc_line.rstrip(), |
| 329 | required_attributes) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 330 | continue |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 331 | # Initialize all to None that are not in elts |
| 332 | line_content = dict(zip(attributes, elts + [None] * len(attributes))) |
| 333 | # All required attributes are defined. Store the entry. |
| 334 | name = line_content.pop('name') |
| 335 | if len(elts) > len(attributes): |
| 336 | extra_info = elts[len(attributes):] |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 337 | logger.warning('discarding %r for for %r', ', '.join(extra_info), |
| 338 | name) |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 339 | rcd[name] = line_content |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 340 | return rcd |
| 341 | |
| 342 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 343 | class ServodClientParser(ServodRCParser): |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 344 | """Parser to use for servod client cmdline tools. |
| 345 | |
| 346 | This parser adds servoscratch serialname<>port conversion to allow |
| 347 | for servod client cmdline tools to address servod using a servo device's |
| 348 | serialname as well. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 349 | """ |
| 350 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 351 | def __init__(self, scratch=None, **kwargs): |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 352 | """Create a ServodRCParser that has the BaseServodParser args. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 353 | |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 354 | (for testing) pass a scratch directory instead of the global default. |
| 355 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 356 | Args: |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 357 | scratch: scratch directory to use |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 358 | **kwargs: keyword arguments forwarded to _BaseServodParser |
| 359 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 360 | # BaseServodParser is used here to get the common arguments. Later, |
| 361 | # the ServodClientParser adds port itself, because from a client perspective |
| 362 | # there is mutual exclusion between --port/--serialname/--name as they serve |
| 363 | # one purpose: to identify an instance. |
| 364 | self._scratchdir = scratch |
| 365 | base_parser = BaseServodParser(add_port=False, add_help=False) |
| 366 | if 'parents' not in kwargs: |
| 367 | kwargs['parents'] = [] |
| 368 | kwargs['parents'].append(base_parser) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 369 | super(ServodClientParser, self).__init__(**kwargs) |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 370 | # Add --port to the |_id_group| to ensure exclusion with name and |
| 371 | # serialname. |
| 372 | BaseServodParser.AddRCEnabledPortArg(self._id_group) |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 373 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 374 | def _MapSNToPort(self, opts): |
| 375 | """Helper to map the serialname in opts to the port its running on. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 376 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 377 | Args: |
| 378 | opts: ArgumentParser Namespace after parsing. |
Ruben Rodriguez Buchillon | 63e3860 | 2018-09-19 10:36:58 +0800 | [diff] [blame] | 379 | |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 380 | Returns: |
| 381 | opts: reference back to passed in opts |
| 382 | |
| 383 | Raises: |
| 384 | Forces a program exit if |opts.serialname| is not found in the servo |
| 385 | scratch |
| 386 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 387 | # Passing None here uses the default production logic while passing any |
| 388 | # other directory can be used for testing. No need to check whether |
| 389 | # |self._scratchdir| is None. |
| 390 | scratch = servodutil.ServoScratch(self._scratchdir) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 391 | try: |
| 392 | entry = scratch.FindById(opts.serialname) |
| 393 | except servodutil.ServodUtilError: |
| 394 | self.error('No servod instance running for device with serialname: %r' % |
| 395 | opts.serialname) |
| 396 | opts.port = int(entry['port']) |
| 397 | return opts |
| 398 | |
| 399 | def parse_known_args(self, args=None, namespace=None): |
| 400 | """Overwrite from Argumentparser to handle servo scratch logic. |
| 401 | |
| 402 | If port is not defined and serialname is defined, and serialname has |
| 403 | no scratch entry, this will raise an error & terminate the program. |
| 404 | |
| 405 | If there was neither a serialname nor a port, set the port to the |
| 406 | default port. |
| 407 | |
| 408 | Note: this also overwrites parse_args as parse_args just calls |
| 409 | parse_known_args and throws an error if there's anything inside of |
| 410 | xtra. |
| 411 | |
| 412 | Args: |
| 413 | args: list of cmdline elements |
| 414 | namespace: namespace to place the results into |
| 415 | |
| 416 | Returns: |
| 417 | tuple (opts, xtra) the result from parsing known args |
| 418 | """ |
Ruben Rodriguez Buchillon | 7be98d7 | 2018-09-19 18:03:36 +0800 | [diff] [blame^] | 419 | opts, xtra = _BaseServodParser.parse_known_args(self, args=args, |
| 420 | namespace=namespace) |
| 421 | opts = ServodRCParser.PostProcessRCElements(opts, logger=self._logger) |
| 422 | if opts.serialname: |
| 423 | # If serialname is set, this means that either serialname or name was used |
| 424 | # to find it, and therefore port cannot have been set by the user due to |
| 425 | # mutual exclusion. |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 426 | opts = self._MapSNToPort(opts) |
Ruben Rodriguez Buchillon | 9066239 | 2018-09-19 16:28:38 +0800 | [diff] [blame] | 427 | return (opts, xtra) |