/commando.py

https://github.com/bewest/commando · Python · 200 lines · 158 code · 13 blank · 29 comment · 9 complexity · 3819163144a26621ab83580bfc654dd5 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. """
  3. Declarative interface for argparse
  4. """
  5. from argparse import ArgumentParser
  6. from collections import namedtuple
  7. # pylint: disable-msg=R0903,C0103,C0301
  8. try:
  9. import pkg_resources
  10. __version__ = pkg_resources.get_distribution('commando').version
  11. except Exception:
  12. __version__ = 'unknown'
  13. __all__ = ['command',
  14. 'subcommand',
  15. 'param',
  16. 'version',
  17. 'store',
  18. 'true',
  19. 'false',
  20. 'append',
  21. 'const',
  22. 'append_const',
  23. 'Application']
  24. class Commando(type):
  25. """
  26. Meta class that enables declarative command definitions
  27. """
  28. def __new__(mcs, name, bases, attrs):
  29. instance = super(Commando, mcs).__new__(mcs, name, bases, attrs)
  30. subcommands = []
  31. main_command = None
  32. for name, member in attrs.iteritems():
  33. if hasattr(member, "command"):
  34. main_command = member
  35. elif hasattr(member, "subcommand"):
  36. subcommands.append(member)
  37. main_parser = None
  38. def add_arguments(parser, params):
  39. """
  40. Adds parameters to the parser
  41. """
  42. for parameter in params:
  43. parser.add_argument(*parameter.args, **parameter.kwargs)
  44. if main_command:
  45. main_parser = ArgumentParser(*main_command.command.args,
  46. **main_command.command.kwargs)
  47. add_arguments(main_parser, getattr(main_command, 'params', []))
  48. subparsers = None
  49. if len(subcommands):
  50. subparsers = main_parser.add_subparsers()
  51. for sub in subcommands:
  52. parser = subparsers.add_parser(*sub.subcommand.args,
  53. **sub.subcommand.kwargs)
  54. parser.set_defaults(run=sub)
  55. add_arguments(parser, getattr(sub, 'params', []))
  56. instance.__parser__ = main_parser
  57. instance.__main__ = main_command
  58. return instance
  59. values = namedtuple('__meta_values', 'args, kwargs')
  60. class metarator(object):
  61. """
  62. A generic decorator that tags the decorated method with
  63. the passed in arguments for meta classes to process them.
  64. """
  65. def __init__(self, *args, **kwargs):
  66. self.values = values._make((args, kwargs)) #pylint: disable-msg=W0212
  67. def metarate(self, func, name='values'):
  68. """
  69. Set the values object to the function object's namespace
  70. """
  71. setattr(func, name, self.values)
  72. return func
  73. def __call__(self, func):
  74. return self.metarate(func)
  75. class command(metarator):
  76. """
  77. Used to decorate the main entry point
  78. """
  79. def __call__(self, func):
  80. return self.metarate(func, name='command')
  81. class subcommand(metarator):
  82. """
  83. Used to decorate the subcommands
  84. """
  85. def __call__(self, func):
  86. return self.metarate(func, name='subcommand')
  87. class param(metarator):
  88. """
  89. Use this decorator instead of `ArgumentParser.add_argument`.
  90. """
  91. def __call__(self, func):
  92. func.params = func.params if hasattr(func, 'params') else []
  93. func.params.append(self.values)
  94. return func
  95. class version(param):
  96. """
  97. Use this decorator for adding the version argument.
  98. """
  99. def __init__(self, *args, **kwargs):
  100. super(version, self).__init__(*args, action='version', **kwargs)
  101. class store(param):
  102. """
  103. Use this decorator for adding the simple params that store data.
  104. """
  105. def __init__(self, *args, **kwargs):
  106. super(store, self).__init__(*args, action='store', **kwargs)
  107. class true(param):
  108. """
  109. Use this decorator as a substitute for 'store_true' action.
  110. """
  111. def __init__(self, *args, **kwargs):
  112. super(true, self).__init__(*args, action='store_true', **kwargs)
  113. class false(param):
  114. """
  115. Use this decorator as a substitute for 'store_false' action.
  116. """
  117. def __init__(self, *args, **kwargs):
  118. super(false, self).__init__(*args, action='store_false', **kwargs)
  119. class const(param):
  120. """
  121. Use this decorator as a substitute for 'store_const' action.
  122. """
  123. def __init__(self, *args, **kwargs):
  124. super(const, self).__init__(*args, action='store_const', **kwargs)
  125. class append(param):
  126. """
  127. Use this decorator as a substitute for 'append' action.
  128. """
  129. def __init__(self, *args, **kwargs):
  130. super(append, self).__init__(*args, action='append', **kwargs)
  131. class append_const(param):
  132. """
  133. Use this decorator as a substitute for 'append_const' action.
  134. """
  135. def __init__(self, *args, **kwargs):
  136. super(append_const, self).__init__(*args, action='append_const', **kwargs)
  137. class Application(object):
  138. """
  139. Barebones base class for command line applications.
  140. """
  141. __metaclass__ = Commando
  142. def parse(self, argv):
  143. """
  144. Simple method that delegates to the ArgumentParser
  145. """
  146. return self.__parser__.parse_args(argv) #pylint: disable-msg=E1101
  147. def run(self, args=None):
  148. """
  149. Runs the main command or sub command based on user input
  150. """
  151. if not args:
  152. import sys
  153. args = self.parse(sys.argv[1:])
  154. if hasattr(args, 'run'):
  155. args.run(self, args)
  156. else:
  157. self.__main__(args) #pylint: disable-msg=E1101