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.

168 lines
5.3 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
  1. '''
  2. Created on Dec 11, 2013
  3. @author: Chris
  4. Collection of functions for extracting argparse related statements from the
  5. client code.
  6. '''
  7. import re
  8. import os
  9. import ast
  10. import _ast
  11. from itertools import *
  12. from gooey.python_bindings import codegen, modules
  13. def parse_source_file(file_name):
  14. """
  15. Parses the AST of Python file for lines containing
  16. references to the argparse module.
  17. returns the collection of ast objects found.
  18. Example client code:
  19. 1. parser = ArgumentParser(desc="My help Message")
  20. 2. parser.add_argument('filename', help="Name of the file to load")
  21. 3. parser.add_argument('-f', '--format', help='Format of output \nOptions: ['md', 'html']
  22. 4. args = parser.parse_args()
  23. Variables:
  24. * nodes Primary syntax tree object
  25. * argparse_assignments The assignment of the ArgumentParser (line 1 in example code)
  26. * add_arg_assignments Calls to add_argument() (lines 2-3 in example code)
  27. * parser_var_name The instance variable of the ArgumentParser (line 1 in example code)
  28. * ast_source The curated collection of all parser related nodes in the client code
  29. """
  30. nodes = ast.parse(_openfile(file_name))
  31. module_imports = get_nodes_by_instance_type(nodes, _ast.Import)
  32. specific_imports = get_nodes_by_instance_type(nodes, _ast.ImportFrom)
  33. assignment_objs = get_nodes_by_instance_type(nodes, _ast.Assign)
  34. call_objects = get_nodes_by_instance_type(nodes, _ast.Call)
  35. argparse_assignments = get_nodes_by_containing_attr(assignment_objs, 'ArgumentParser')
  36. add_arg_assignments = get_nodes_by_containing_attr(call_objects, 'add_argument')
  37. parse_args_assignment = get_nodes_by_containing_attr(call_objects, 'parse_args')
  38. ast_argparse_source = chain(
  39. module_imports,
  40. specific_imports,
  41. argparse_assignments,
  42. add_arg_assignments
  43. # parse_args_assignment
  44. )
  45. return ast_argparse_source
  46. def _openfile(file_name):
  47. with open(file_name, 'rb') as f:
  48. return f.read()
  49. def read_client_module(filename):
  50. with open(filename, 'r') as f:
  51. return f.readlines()
  52. def get_nodes_by_instance_type(nodes, object_type):
  53. return [node for node in walk_tree(nodes) if isinstance(node, object_type)]
  54. def get_nodes_by_containing_attr(nodes, attr):
  55. return [node for node in nodes if attr in walk_tree(node)]
  56. def walk_tree(node):
  57. yield node
  58. d = node.__dict__
  59. for key, value in d.iteritems():
  60. if isinstance(value, list):
  61. for val in value:
  62. for _ in walk_tree(val): yield _
  63. elif 'ast' in str(type(value)):
  64. for _ in walk_tree(value): yield _
  65. else:
  66. yield value
  67. def convert_to_python(ast_source):
  68. """
  69. Converts the ast objects back into human readable Python code
  70. """
  71. return map(codegen.to_source, ast_source)
  72. def get_assignment_name(lines):
  73. nodes = ast.parse(''.join(lines))
  74. assignments = get_nodes_by_instance_type(nodes, _ast.Assign)
  75. argparse_var = get_nodes_by_containing_attr(assignments, 'parse_args')
  76. return argparse_var[0].value.func.value.id
  77. def lines_indented(line):
  78. unindented = re.compile("^[a-zA-Z0-9_@]+")
  79. return unindented.match(line) is None
  80. def not_at_main(line):
  81. return 'def main' not in line
  82. def not_at_parse_args(line):
  83. return 'parse_args(' not in line
  84. def get_indent(line):
  85. indent = re.compile("(\t|\s)")
  86. return ''.join(takewhile(lambda char: indent.match(char) is not None, line))
  87. def format_source_to_return_parser(source, cutoff_line, restart_line, col_offset, parser_name):
  88. top = source[:cutoff_line - 1]
  89. bottom = source[restart_line:]
  90. indentation = source[cutoff_line - 1][:col_offset]
  91. return_statement = ['{}return {}\n\n'.format(indentation, parser_name)]
  92. # stitch it all back together excluding the Gooey decorator
  93. new_source = (line for line in chain(top, return_statement, bottom)
  94. if '@gooey' not in line.lower())
  95. return ''.join(new_source)
  96. def extract_parser(modulepath):
  97. source = read_client_module(modulepath)
  98. nodes = ast.parse(''.join(source))
  99. funcs = get_nodes_by_instance_type(nodes, _ast.FunctionDef)
  100. assignment_objs = get_nodes_by_instance_type(nodes, _ast.Assign)
  101. main_func = get_nodes_by_containing_attr(funcs, 'main')[0]
  102. parse_args_assignment = get_nodes_by_containing_attr(main_func.body, 'parse_args')[0]
  103. # ast reports the line no of a block structure as the start of the structure,
  104. # not the end, so we look for the line no of the next node after main()
  105. # and use that as the end of the main() function.
  106. try:
  107. restart_line = nodes.body[nodes.body.index(main_func)+1].lineno - 1
  108. except IndexError:
  109. restart_line = len(source)
  110. module_source = format_source_to_return_parser(
  111. source,
  112. cutoff_line=parse_args_assignment.lineno,
  113. restart_line=restart_line,
  114. col_offset=parse_args_assignment.col_offset,
  115. parser_name=parse_args_assignment.value.func.value.id
  116. )
  117. client_module = modules.load(module_source)
  118. return client_module.main()
  119. if __name__ == '__main__':
  120. filepath = os.path.join(os.path.dirname(__file__),
  121. 'examples',
  122. 'example_argparse_souce_in_main.py')
  123. nodes = ast.parse(_openfile(filepath))
  124. #
  125. ast_source = parse_source_file(filepath)
  126. python_code = convert_to_python(list(ast_source))