''' Created on Dec 11, 2013 @author: Chris Collection of functions for extracting argparse related statements from the client code. ''' import os import ast from itertools import chain import codegen from monkey_parser import MonkeyParser from gooey.gui.action_sorter import ActionSorter from parser_exceptions import ParserError def parse_source_file(file_name): """ Parses the AST of Python file for lines containing references to the argparse module. returns the collection of ast objects found. Example client code: 1. parser = ArgumentParser(desc="My help Message") 2. parser.add_argument('filename', help="Name of the file to load") 3. parser.add_argument('-f', '--format', help='Format of output \nOptions: ['md', 'html'] 4. args = parser.parse_args() Variables: * nodes Primary syntax tree object * _mainfunc_block main() method as found in the ast nodes * _try_blocks Try/except/finally blocks found in the main method * main_block The code block in which the ArgumentParser statements are located * argparse_assign_obj The assignment of the ArgumentParser (line 1 in example code) * parser_nodes Nodes which have parser references (lines 1-4 in example code) * parser_var_name The instance variable of the ArgumentParser (line 1 in example code) * ast_source The curated collection of all parser related nodes in the client code """ nodes = ast.parse(_openfile(file_name)) _mainfunc_block = find_main(nodes) _try_blocks = find_try_blocks(_mainfunc_block) nodes_to_search = chain([_mainfunc_block], _try_blocks) main_block = find_block_containing_argparse(nodes_to_search) argparse_assign_obj = find_assignment_objects(main_block) parser_nodes = find_parser_nodes(main_block) full_ast_source = chain(argparse_assign_obj, parser_nodes) return full_ast_source def _openfile(file_name): with open(file_name, 'rb') as f: return f.read() def find_main(nodes): code_block = _find_block(nodes, ast.FunctionDef, lambda node: node.name == 'main') if code_block != None: return code_block else: raise ParserError('Could not find main function') def find_try_blocks(nodes): return _find_blocks(nodes, [ast.TryExcept, ast.TryFinally], lambda x: x) def find_imports(nodes): return _find_blocks(nodes, ast.ImportFrom, lambda x: x.module == 'argparse') def _find_block(nodes, types, predicate): blocks = _find_blocks(nodes, types, predicate) return blocks[0] if blocks else None def _find_blocks(nodes, types, predicate): _types = types if isinstance(types, list) else [types] return [node for node in nodes.body if any([isinstance(node, _type) for _type in _types]) and predicate(node)] def find_block_containing_argparse(search_locations): # Browses a collection of Nodes for the one containing the Argparse instantiation for location in search_locations: if has_argparse_assignment(location): return location raise ParserError("Could not locate AugmentParser.") def has_argparse_assignment(block): # Checks a given node for presence of an ArgumentParser instantiation argparse_assignment = _find_statement(block, has_instantiator, 'ArgumentParser') return is_found(argparse_assignment) def find_assignment_objects(ast_block): return _find_statement(ast_block, has_instantiator, 'ArgumentParser') def find_parser_nodes(ast_block): return _find_statement(ast_block, has_assignment, 'add_argument') def is_found(stmnt): return len(stmnt) def _find_statement(block, predicate, name): return [node for node in block.body if predicate(node, name)] def has_instantiator(x, name): # Checks if the astObject is one with an instantiation of the ArgParse class return has_attr(name, lambda _name: x.value.func.id == _name) def has_assignment(node, name): # Checks if the astObject contains a function with a name of name return has_attr(name, lambda _name: node.value.func.attr == _name) def has_attr(name, attr_predicate): try: return attr_predicate(name) except AttributeError as e: return False # Wrong type. Ignore. def get_assignment_name(node): # return the variable name to which ArgumentParser is assigned return node.targets[0].id def convert_to_python(ast_source): """ Converts the ast objects back into human readable Python code """ return map(codegen.to_source, ast_source) def extract_parser(modulepath): ast_source = parse_source_file(modulepath) if ast_source: python_code = convert_to_python(ast_source) return MonkeyParser(python_code) return None if __name__ == '__main__': filepath = os.path.join(os.path.dirname(__file__), 'mockapplications', 'example_argparse_souce_in_main.py') nodes = ast.parse(_openfile(filepath)) ast_source = parse_source_file(filepath) python_code = convert_to_python(ast_source) parser = MonkeyParser(python_code) factory = ActionSorter(parser._actions) print factory._positionals