A fork of hyde, the static site generation. Some patches will be pushed upstream.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

114 lines
3.4 KiB

  1. # -*- coding: utf-8 -*-
  2. """
  3. A nice declarative interface for Argument parser
  4. """
  5. from argparse import ArgumentParser, Namespace
  6. from collections import namedtuple
  7. __all__ = [
  8. 'command',
  9. 'subcommand',
  10. 'param',
  11. 'Application'
  12. ]
  13. class CommandLine(type):
  14. """
  15. Meta class that enables declarative command definitions
  16. """
  17. def __new__(mcs, name, bases, attrs):
  18. instance = super(CommandLine, mcs).__new__(mcs, name, bases, attrs)
  19. subcommands = []
  20. main_command = None
  21. for name, member in attrs.iteritems():
  22. if hasattr(member, "command"):
  23. main_command = member
  24. elif hasattr(member, "subcommand"):
  25. subcommands.append(member)
  26. main_parser = None
  27. def add_arguments(parser, params):
  28. """
  29. Adds parameters to the parser
  30. """
  31. for parameter in params:
  32. parser.add_argument(*parameter.args, **parameter.kwargs)
  33. if main_command:
  34. main_parser = ArgumentParser(*main_command.command.args, **main_command.command.kwargs)
  35. add_arguments(main_parser, main_command.params)
  36. subparsers = None
  37. if len(subcommands):
  38. subparsers = main_parser.add_subparsers()
  39. for sub in subcommands:
  40. parser = subparsers.add_parser(*sub.subcommand.args, **sub.subcommand.kwargs)
  41. parser.set_defaults(run=sub)
  42. add_arguments(parser, sub.params)
  43. instance.__parser__ = main_parser
  44. instance.__main__ = main_command
  45. return instance
  46. values = namedtuple('__meta_values', 'args, kwargs')
  47. class metarator(object):
  48. """
  49. A generic decorator that tags the decorated method with
  50. the passed in arguments for meta classes to process them.
  51. """
  52. def __init__(self, *args, **kwargs):
  53. self.values = values._make((args, kwargs))
  54. def metarate(self, func, name='values'):
  55. """
  56. Set the values object to the function object's namespace
  57. """
  58. setattr(func, name, self.values)
  59. return func
  60. def __call__(self, func):
  61. return self.metarate(func)
  62. class command(metarator):
  63. """
  64. Used to decorate the main entry point
  65. """
  66. def __call__(self, func):
  67. return self.metarate(func, name='command')
  68. class subcommand(metarator):
  69. """
  70. Used to decorate the subcommands
  71. """
  72. def __call__(self, func):
  73. return self.metarate(func, name='subcommand')
  74. class param(metarator):
  75. """
  76. Use this decorator instead of `ArgumentParser.add_argument`.
  77. """
  78. def __call__(self, func):
  79. func.params = func.params if hasattr(func, 'params') else []
  80. func.params.append(self.values)
  81. return func
  82. class Application(object):
  83. """
  84. Bare bones base class for command line applications. Hides the
  85. meta programming complexities.
  86. """
  87. __metaclass__ = CommandLine
  88. def parse(self, argv):
  89. """
  90. Simple method that delegates to the ArgumentParser
  91. """
  92. return self.__parser__.parse_args(argv)
  93. def run(self, args):
  94. """
  95. Runs the main command or sub command based on user input
  96. """
  97. if hasattr(args, 'run'):
  98. args.run(self, args)
  99. else:
  100. self.__main__(args)