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.

113 lines
3.7 KiB

  1. '''
  2. Created on Feb 8, 2014
  3. @author: Chris
  4. '''
  5. import types
  6. from parser_exceptions import ArgumentError
  7. from argparse import ArgumentParser
  8. from argparse import RawDescriptionHelpFormatter
  9. import code_prep
  10. class MonkeyParser(object):
  11. '''
  12. Builds a parser instance from the code
  13. extracted from the client module.
  14. The instance is stored as a private variable in the
  15. class and all methods are delagted to it so that the
  16. user of the class can treat it just as a normal argparse
  17. instance.
  18. '''
  19. def __init__(self, source_code):
  20. self._parser_instance = self._build_argparser_from_client_source(source_code)
  21. # Monkey patch parser's `error` method so that it raises an error
  22. # rather than silently exiting
  23. self._parser_instance.error = types.MethodType(
  24. self._ErrorAsString,
  25. self._parser_instance
  26. )
  27. def _build_argparser_from_client_source(self, source_code):
  28. '''
  29. runs the client code by evaling each line.
  30. Example input Code:
  31. parser = ArgumentParser(description=program_license, formatter_class=RawDescriptionHelpFormatter)
  32. parser.add_argument("-r", "--recursive", dest="recurse", action="store_true", help="recurse into subfolders [default: %(default)s]")
  33. parser.add_argument("-v", "--verbose", dest="verbose", action="count", help="set verbosity level [default: %(default)s]")
  34. Method extracts the instance name (e.g. parser) from the first line,
  35. and instantiates it in a local variable by evaling the rest of the lines.
  36. Each subsequent line updates the local variable in turn.
  37. '''
  38. imports = filter(lambda x: 'gooey' not in x, code_prep.take_imports(source_code))
  39. arg_code = code_prep.drop_imports(source_code)
  40. updated_source_code = code_prep.update_parser_varname('clients_parser', arg_code)
  41. for _import in imports:
  42. exec(_import)
  43. first_line = updated_source_code.pop(0)
  44. clients_parser, assignment = code_prep.split_line(first_line)
  45. clients_parser = eval(assignment)
  46. for line in updated_source_code:
  47. eval(line)
  48. return clients_parser
  49. def _format_source_with_new_varname(self, new_variable_name, source):
  50. '''
  51. 'injects' the client code with a known variable name so that it
  52. can be `eval`d and assigned to a variable in the local code.
  53. For example, if the client code was:
  54. parser = ArgumentParser(descrip...)
  55. parser.add_argument("-r", "--re...)
  56. parser.add_argument("-v", "--ve...)
  57. The variable "parser" would be overwritten with a custom name. e.g.
  58. my_parser = ArgumentParser(descrip...)
  59. my_parser.add_argument("-r", "--re...)
  60. '''
  61. source_code = source[:]
  62. first_line = source_code[0]
  63. client_parser_variable, statement = self._split_line(first_line)
  64. client_parser_variable = client_parser_variable.strip()
  65. for index, line in enumerate(source_code):
  66. source_code[index] = line.replace(client_parser_variable, new_variable_name)
  67. source_code.append('{0}.parse_args()'.format(new_variable_name))
  68. return source_code
  69. def _split_line(self, line):
  70. # Splits line at the first = sign,
  71. # joins everything after the first =
  72. # to account for additional = signs in
  73. # parameters
  74. components = line.split('=')
  75. var = components.pop(0)
  76. return var, '='.join(components)
  77. def __getattr__(self, attr):
  78. '''
  79. Auto-delegates everything to the ArgumentParser instance'''
  80. return getattr(self._parser_instance, attr)
  81. @staticmethod
  82. def _ErrorAsString(self, msg):
  83. '''
  84. Monkey patch for parser.error
  85. Raises an error rather than
  86. printing and silently exiting.
  87. '''
  88. raise ArgumentError(msg)
  89. if __name__ == '__main__':
  90. pass