Home | History | Annotate | Download | only in base
      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 
      7 Contains all the support code to enable the shared context used by the cr tool.
      8 This includes the configuration variables and command line handling.
      9 """
     10 
     11 import argparse
     12 import os
     13 import cr
     14 
     15 class _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 
     51 class _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 
     69 class _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 
    101 class _ContextData:
    102   pass
    103 
    104 
    105 class 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 
    242 def Create(description='', epilog=''):
    243   context = Context()
    244   context.CreateData(description=description, epilog=epilog)
    245   return context
    246