15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
25d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# found in the LICENSE file.
45d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
523730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)import argparse
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import optparse
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
823730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)from telemetry.core import camel_case
923730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class ArgumentHandlerMixIn(object):
12a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  """A structured way to handle command-line arguments.
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  In AddCommandLineArgs, add command-line arguments.
15a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  In ProcessCommandLineArgs, validate them and store them in a private class
16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  variable. This way, each class encapsulates its own arguments, without needing
17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  to pass an arguments object around everywhere.
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  """
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
20a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @classmethod
21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def AddCommandLineArgs(cls, parser):
22a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """Override to accept custom command-line arguments."""
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
24a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @classmethod
25a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def ProcessCommandLineArgs(cls, parser, args):
26a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """Override to process command-line arguments.
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    We pass in parser so we can call parser.error()."""
29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class Command(ArgumentHandlerMixIn):
320529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  """An abstraction for things that run from the command-line."""
33a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @classmethod
35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def Name(cls):
3623730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)    return camel_case.ToUnderscore(cls.__name__)
37a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @classmethod
39a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def Description(cls):
40a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if cls.__doc__:
41a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return cls.__doc__.splitlines()[0]
42a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    else:
43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return ''
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def Run(self, args):
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    raise NotImplementedError()
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
4823730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)  @classmethod
49116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  def main(cls, args=None):
500529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    """Main method to run this command as a standalone script."""
5123730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)    parser = argparse.ArgumentParser()
5223730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)    cls.AddCommandLineArgs(parser)
53116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    args = parser.parse_args(args=args)
5423730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)    cls.ProcessCommandLineArgs(parser, args)
55116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    return min(cls().Run(args), 255)
5623730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# TODO: Convert everything to argparse.
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class OptparseCommand(Command):
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  usage = ''
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @classmethod
63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def CreateParser(cls):
64f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    return optparse.OptionParser('%%prog %s %s' % (cls.Name(), cls.usage),
65f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                                 description=cls.Description())
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def Run(self, args):
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    raise NotImplementedError()
6923730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)
7023730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)  @classmethod
71116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  def main(cls, args=None):
720529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    """Main method to run this command as a standalone script."""
730529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    parser = cls.CreateParser()
7423730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)    cls.AddCommandLineArgs(parser)
75116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    options, args = parser.parse_args(args=args)
7623730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)    options.positional_args = args
7723730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)    cls.ProcessCommandLineArgs(parser, options)
78116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    return min(cls().Run(options), 255)
790529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
800529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
810529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochclass SubcommandCommand(Command):
820529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  """Combines Commands into one big command with sub-commands.
830529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
840529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  E.g. "svn checkout", "svn update", and "svn commit" are separate sub-commands.
850529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
860529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  Example usage:
870529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    class MyCommand(command_line.SubcommandCommand):
880529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      commands = (Help, List, Run)
890529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
900529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    if __name__ == '__main__':
910529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      sys.exit(MyCommand.main())
920529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  """
930529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
940529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  commands = ()
950529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
960529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  @classmethod
970529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  def AddCommandLineArgs(cls, parser):
980529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    subparsers = parser.add_subparsers()
990529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
1000529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    for command in cls.commands:
1010529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      subparser = subparsers.add_parser(
1020529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch          command.Name(), help=command.Description())
1030529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      subparser.set_defaults(command=command)
1040529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch      command.AddCommandLineArgs(subparser)
1050529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
1060529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  @classmethod
1070529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  def ProcessCommandLineArgs(cls, parser, args):
1080529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    args.command.ProcessCommandLineArgs(parser, args)
1090529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
1100529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  def Run(self, args):
111116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    return args.command().Run(args)
112