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
5import argparse
6import optparse
7
8from telemetry.internal.util import camel_case
9
10
11class ArgumentHandlerMixIn(object):
12  """A structured way to handle command-line arguments.
13
14  In AddCommandLineArgs, add command-line arguments.
15  In ProcessCommandLineArgs, validate them and store them in a private class
16  variable. This way, each class encapsulates its own arguments, without needing
17  to pass an arguments object around everywhere.
18  """
19
20  @classmethod
21  def AddCommandLineArgs(cls, parser):
22    """Override to accept custom command-line arguments."""
23
24  @classmethod
25  def ProcessCommandLineArgs(cls, parser, args):
26    """Override to process command-line arguments.
27
28    We pass in parser so we can call parser.error()."""
29
30
31class Command(ArgumentHandlerMixIn):
32  """An abstraction for things that run from the command-line."""
33
34  @classmethod
35  def Name(cls):
36    return camel_case.ToUnderscore(cls.__name__)
37
38  @classmethod
39  def Description(cls):
40    if cls.__doc__:
41      return cls.__doc__.splitlines()[0]
42    else:
43      return ''
44
45  def Run(self, args):
46    raise NotImplementedError()
47
48  @classmethod
49  def main(cls, args=None):
50    """Main method to run this command as a standalone script."""
51    parser = argparse.ArgumentParser()
52    cls.AddCommandLineArgs(parser)
53    args = parser.parse_args(args=args)
54    cls.ProcessCommandLineArgs(parser, args)
55    return min(cls().Run(args), 255)
56
57
58# TODO: Convert everything to argparse.
59class OptparseCommand(Command):
60  usage = ''
61
62  @classmethod
63  def CreateParser(cls):
64    return optparse.OptionParser('%%prog %s %s' % (cls.Name(), cls.usage),
65                                 description=cls.Description())
66
67  @classmethod
68  def AddCommandLineArgs(cls, parser, environment):
69    # pylint: disable=arguments-differ
70    pass
71
72  @classmethod
73  def ProcessCommandLineArgs(cls, parser, args, environment):
74    # pylint: disable=arguments-differ
75    pass
76
77  def Run(self, args):
78    raise NotImplementedError()
79
80  @classmethod
81  def main(cls, args=None):
82    """Main method to run this command as a standalone script."""
83    parser = cls.CreateParser()
84    cls.AddCommandLineArgs(parser, None)
85    options, args = parser.parse_args(args=args)
86    options.positional_args = args
87    cls.ProcessCommandLineArgs(parser, options, None)
88    return min(cls().Run(options), 255)
89
90
91class SubcommandCommand(Command):
92  """Combines Commands into one big command with sub-commands.
93
94  E.g. "svn checkout", "svn update", and "svn commit" are separate sub-commands.
95
96  Example usage:
97    class MyCommand(command_line.SubcommandCommand):
98      commands = (Help, List, Run)
99
100    if __name__ == '__main__':
101      sys.exit(MyCommand.main())
102  """
103
104  commands = ()
105
106  @classmethod
107  def AddCommandLineArgs(cls, parser):
108    subparsers = parser.add_subparsers()
109
110    for command in cls.commands:
111      subparser = subparsers.add_parser(
112          command.Name(), help=command.Description())
113      subparser.set_defaults(command=command)
114      command.AddCommandLineArgs(subparser)
115
116  @classmethod
117  def ProcessCommandLineArgs(cls, parser, args):
118    args.command.ProcessCommandLineArgs(parser, args)
119
120  def Run(self, args):
121    return args.command().Run(args)
122