mirror of https://github.com/chriskiehl/Gooey.git
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.
109 lines
3.5 KiB
109 lines
3.5 KiB
'''
|
|
Created on Feb 8, 2014
|
|
|
|
@author: Chris
|
|
'''
|
|
|
|
import types
|
|
|
|
from parser_exceptions import ArgumentError
|
|
from argparse import ArgumentParser
|
|
from argparse import RawDescriptionHelpFormatter
|
|
|
|
|
|
class MonkeyParser(object):
|
|
'''
|
|
Builds a parser instance from the code
|
|
extracted from the client module.
|
|
|
|
The instance is stored as a private variable in the
|
|
class and all methods are delagted to it so that the
|
|
user of the class can treat it just as a normal argparse
|
|
instance.
|
|
'''
|
|
|
|
def __init__(self, source_code):
|
|
self._parser_instance = self._build_argparser_from_client_source(source_code)
|
|
# Monkey patch parser's `error` method so that it raises an error
|
|
# rather than silently exiting
|
|
self._parser_instance.error = types.MethodType(
|
|
self._ErrorAsString,
|
|
self._parser_instance
|
|
)
|
|
|
|
def _build_argparser_from_client_source(self, source_code):
|
|
'''
|
|
runs the client code by evaling each line.
|
|
|
|
Example input Code:
|
|
parser = ArgumentParser(description=program_license, formatter_class=RawDescriptionHelpFormatter)
|
|
parser.add_argument("-r", "--recursive", dest="recurse", action="store_true", help="recurse into subfolders [default: %(default)s]")
|
|
parser.add_argument("-v", "--verbose", dest="verbose", action="count", help="set verbosity level [default: %(default)s]")
|
|
|
|
Method extracts the instance name (e.g. parser) from the first line,
|
|
and instantiates it in a local variable by evaling the rest of the lines.
|
|
Each subsequent line updates the local variable in turn.
|
|
'''
|
|
new_source_code = self._format_source_with_new_varname('clients_parser', source_code)
|
|
# variable of the same name as the one passed into the format_source method.
|
|
# Used to hold the eval'd statements
|
|
|
|
first_line = new_source_code.pop(0)
|
|
clients_parser, assignment = self._split_line(first_line)
|
|
|
|
clients_parser = eval(assignment)
|
|
|
|
for line in new_source_code:
|
|
eval(line)
|
|
return clients_parser
|
|
|
|
def _format_source_with_new_varname(self, variable_name, source):
|
|
'''
|
|
'injects' the client code with a known variable name so that it
|
|
can be `eval`d and assigned to a variable in the local code.
|
|
|
|
For example, if the client code was:
|
|
parser = ArgumentParser(descrip...)
|
|
parser.add_argument("-r", "--re...)
|
|
parser.add_argument("-v", "--ve...)
|
|
|
|
The variable "parser" would be overwritten with a custom name. e.g.
|
|
my_parser = ArgumentParser(descrip...)
|
|
my_parser.add_argument("-r", "--re...)
|
|
'''
|
|
source_code = source[:]
|
|
|
|
first_line = source_code[0]
|
|
parser_variable, statement = self._split_line(first_line)
|
|
parser_variable = parser_variable.strip()
|
|
|
|
for index, line in enumerate(source_code):
|
|
source_code[index] = line.replace(parser_variable, variable_name)
|
|
return source_code
|
|
|
|
def _split_line(self, line):
|
|
# Splits line at the first = sign,
|
|
# joins everything after the first =
|
|
# to account for additional = signs in
|
|
# parameters
|
|
components = line.split('=')
|
|
var = components.pop(0)
|
|
return var, '='.join(components)
|
|
|
|
def __getattr__(self, attr):
|
|
'''
|
|
Auto-delegates everything to the ArgumentParser instance'''
|
|
return getattr(self._parser_instance, attr)
|
|
|
|
@staticmethod
|
|
def _ErrorAsString(self, msg):
|
|
'''
|
|
Monkey patch for parser.error
|
|
Raises an error rather than
|
|
printing and silently exiting.
|
|
'''
|
|
raise ArgumentError(msg)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pass
|