1# Copyright 2014 The Chromium 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. 4 5"""Application context management for the cr tool. 6 7Contains all the support code to enable the shared context used by the cr tool. 8This includes the configuration variables and command line handling. 9""" 10 11import argparse 12import os 13import cr 14 15class _DumpVisitor(cr.visitor.ExportVisitor): 16 """A visitor that prints all variables in a config hierarchy.""" 17 18 def __init__(self, with_source): 19 super(_DumpVisitor, self).__init__({}) 20 self.to_dump = {} 21 self.with_source = with_source 22 23 def StartNode(self): 24 if self.with_source: 25 self._DumpNow() 26 super(_DumpVisitor, self).StartNode() 27 28 def EndNode(self): 29 if self.with_source or not self.stack: 30 self._DumpNow() 31 super(_DumpVisitor, self).EndNode() 32 if not self.stack: 33 self._DumpNow() 34 35 def Visit(self, key, value): 36 super(_DumpVisitor, self).Visit(key, value) 37 if key in self.store: 38 str_value = str(self.store[key]) 39 if str_value != str(os.environ.get(key, None)): 40 self.to_dump[key] = str_value 41 42 def _DumpNow(self): 43 if self.to_dump: 44 if self.with_source: 45 print 'From', self.Where() 46 for key in sorted(self.to_dump.keys()): 47 print ' ', key, '=', self.to_dump[key] 48 self.to_dump = {} 49 50 51class _ShowHelp(argparse.Action): 52 """An argparse action to print the help text. 53 54 This is like the built in help text printing action, except it knows to do 55 nothing when we are just doing the early speculative parse of the args. 56 """ 57 58 def __call__(self, parser, namespace, values, option_string=None): 59 if cr.context.speculative: 60 return 61 command = cr.Command.GetActivePlugin() 62 if command: 63 command.parser.print_help() 64 else: 65 parser.print_help() 66 exit(1) 67 68 69class _ArgumentParser(argparse.ArgumentParser): 70 """An extension of an ArgumentParser to enable speculative parsing. 71 72 It supports doing an early parse that never produces errors or output, to do 73 early collection of arguments that may affect what other arguments are 74 allowed. 75 """ 76 77 def error(self, message): 78 if cr.context.speculative: 79 return 80 super(_ArgumentParser, self).error(message) 81 82 def parse_args(self): 83 if cr.context.speculative: 84 result = self.parse_known_args() 85 if result: 86 return result[0] 87 return None 88 return super(_ArgumentParser, self).parse_args() 89 90 def parse_known_args(self, args=None, namespace=None): 91 result = super(_ArgumentParser, self).parse_known_args(args, namespace) 92 if result is None: 93 return namespace, None 94 return result 95 96 97# The context stack 98_stack = [] 99 100 101class _ContextData: 102 pass 103 104 105class Context(cr.config.Config): 106 """The base context holder for the cr system. 107 108 This holds the common context shared throughout cr. 109 Mostly this is stored in the Config structure of variables. 110 """ 111 112 def __init__(self, name='Context'): 113 super(Context, self).__init__(name) 114 self._data = _ContextData() 115 116 def CreateData(self, description='', epilog=''): 117 self._data.args = None 118 self._data.arguments = cr.config.Config('ARGS') 119 self._data.derived = cr.config.Config('DERIVED') 120 self.AddChildren(*cr.config.GLOBALS) 121 self.AddChildren( 122 cr.config.Config('ENVIRONMENT', literal=True, export=True).Set( 123 {k: self.ParseValue(v) for k, v in os.environ.items()}), 124 self._data.arguments, 125 self._data.derived, 126 ) 127 # Build the command line argument parser 128 self._data.parser = _ArgumentParser(add_help=False, description=description, 129 epilog=epilog) 130 self._data.subparsers = self.parser.add_subparsers() 131 # Add the global arguments 132 self.AddCommonArguments(self._data.parser) 133 self._data.gclient = {} 134 135 @property 136 def data(self): 137 return self._data 138 139 def __enter__(self): 140 """ To support using 'with cr.base.context.Create():'""" 141 _stack.append(self) 142 cr.context = self 143 return self 144 145 def __exit__(self, *_): 146 _stack.pop() 147 if _stack: 148 cr.context = _stack[-1] 149 return False 150 151 def AddSubParser(self, source): 152 parser = source.AddArguments(self._data.subparsers) 153 154 @classmethod 155 def AddCommonArguments(cls, parser): 156 """Adds the command line arguments common to all commands in cr.""" 157 parser.add_argument( 158 '-h', '--help', 159 action=_ShowHelp, nargs=0, 160 help='show the help message and exit.' 161 ) 162 parser.add_argument( 163 '--dry-run', dest='CR_DRY_RUN', 164 action='store_true', default=None, 165 help=""" 166 Don't execute commands, just print them. Implies verbose. 167 Overrides CR_DRY_RUN 168 """ 169 ) 170 parser.add_argument( 171 '-v', '--verbose', dest='CR_VERBOSE', 172 action='count', default=None, 173 help=""" 174 Print information about commands being performed. 175 Repeating multiple times increases the verbosity level. 176 Overrides CR_VERBOSE 177 """ 178 ) 179 180 @property 181 def args(self): 182 return self._data.args 183 184 @property 185 def arguments(self): 186 return self._data.arguments 187 188 @property 189 def speculative(self): 190 return self._data.speculative 191 192 @property 193 def derived(self): 194 return self._data.derived 195 196 @property 197 def parser(self): 198 return self._data.parser 199 200 @property 201 def remains(self): 202 remains = getattr(self._data.args, '_remains', None) 203 if remains and remains[0] == '--': 204 remains = remains[1:] 205 return remains 206 207 @property 208 def verbose(self): 209 if self.autocompleting: 210 return False 211 return self.Find('CR_VERBOSE') or self.dry_run 212 213 @property 214 def dry_run(self): 215 if self.autocompleting: 216 return True 217 return self.Find('CR_DRY_RUN') 218 219 @property 220 def autocompleting(self): 221 return 'COMP_WORD' in os.environ 222 223 @property 224 def gclient(self): 225 if not self._data.gclient: 226 self._data.gclient = cr.base.client.ReadGClient() 227 return self._data.gclient 228 229 def ParseArgs(self, speculative=False): 230 cr.plugin.DynamicChoices.only_active = not speculative 231 self._data.speculative = speculative 232 self._data.args = self._data.parser.parse_args() 233 self._data.arguments.Wipe() 234 if self._data.args: 235 self._data.arguments.Set( 236 {k: v for k, v in vars(self._data.args).items() if v is not None}) 237 238 def DumpValues(self, with_source): 239 _DumpVisitor(with_source).VisitNode(self) 240 241 242def Create(description='', epilog=''): 243 context = Context() 244 context.CreateData(description=description, epilog=epilog) 245 return context 246