|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +from argparse import ArgumentParser, FileType |
| 4 | +import imp |
| 5 | +import sys |
| 6 | +import types |
| 7 | +from pyparsing import Regex, Literal, ZeroOrMore, Group |
| 8 | + |
| 9 | + |
| 10 | +class UndefinedMacroError(Exception): |
| 11 | + '''Class encoding an exception for an undefined macro encountered |
| 12 | + while parsing a text''' |
| 13 | + |
| 14 | + def __init__(self, function_name): |
| 15 | + '''Constructor, takes the unknown macro name as an argument''' |
| 16 | + super(UndefinedMacroError, self).__init__() |
| 17 | + self._msg = "unknown macro '{0}'".format(function_name.strip('\\')) |
| 18 | + |
| 19 | + def __str__(self): |
| 20 | + '''method to stringify the exception''' |
| 21 | + return repr(self._msg) |
| 22 | + |
| 23 | + |
| 24 | +class MacroExpander(object): |
| 25 | + '''Macro expansion class, macros are encoded as |
| 26 | + \\macro_name{param_1}...{param_n}''' |
| 27 | + |
| 28 | + def __init__(self): |
| 29 | + '''Constructor''' |
| 30 | + self._macros = {} |
| 31 | + text = Regex(r'[^\\]+').leaveWhitespace() |
| 32 | + lb = Literal('{').suppress() |
| 33 | + rb = Literal('}').suppress() |
| 34 | + param_value = Regex(r'[^}\\]+') |
| 35 | + param = lb + ZeroOrMore(param_value) + rb |
| 36 | + params = Group(ZeroOrMore(param)).setResultsName('params') |
| 37 | + macro_name = Regex(r'\\\w+').setResultsName('macro') |
| 38 | + macro_call = macro_name + params |
| 39 | + text_file = ZeroOrMore(text | macro_call) |
| 40 | + |
| 41 | + def macro_action(toks): |
| 42 | + macro_name = toks['macro'] |
| 43 | + params = toks['params'] |
| 44 | + if self._has_macro(macro_name): |
| 45 | + return self._macros[macro_name](*params) |
| 46 | + else: |
| 47 | + raise UndefinedMacroError(macro_name) |
| 48 | + macro_call.addParseAction(macro_action) |
| 49 | + self._grammar = text_file |
| 50 | + |
| 51 | + def add_macro(self, macro_name, macro_impl): |
| 52 | + '''method to add a new macro to the macro expander, given |
| 53 | + the function name, and its implementation as arguments''' |
| 54 | + self._macros['\\' + macro_name] = macro_impl |
| 55 | + |
| 56 | + def _has_macro(self, macro_name): |
| 57 | + '''internal method to check whether the parser has a |
| 58 | + definition for the given macro name''' |
| 59 | + return macro_name in self._macros |
| 60 | + |
| 61 | + def expand(self, text): |
| 62 | + '''method to perform the macro expansion on the given text''' |
| 63 | + results = self._grammar.parseString(text) |
| 64 | + return ''.join(results) |
| 65 | + |
| 66 | + |
| 67 | +def main(): |
| 68 | + arg_parser = ArgumentParser(description='macro expansion utility') |
| 69 | + arg_parser.add_argument('--file', type=FileType('r'), |
| 70 | + action='store', dest='file', |
| 71 | + required=True, help='file to expand') |
| 72 | + arg_parser.add_argument('--def', type=str, action='store', |
| 73 | + default='macro_defs', dest='defs', |
| 74 | + help='macro definitions module name') |
| 75 | + try: |
| 76 | + options = arg_parser.parse_args() |
| 77 | + text = ''.join(options.file) |
| 78 | + module_info = imp.find_module(options.defs) |
| 79 | + macro_module = imp.load_module(options.defs, *module_info) |
| 80 | + expander = MacroExpander() |
| 81 | + for macro_def in macro_module.__dict__.values(): |
| 82 | + if isinstance(macro_def, types.FunctionType): |
| 83 | + expander.add_macro(macro_def.__name__, macro_def) |
| 84 | + print(expander.expand(text)) |
| 85 | + except UndefinedMacroError as error: |
| 86 | + sys.stderr.write('### error: ' + str(error) + '\n') |
| 87 | + sys.exit(2) |
| 88 | + except Exception as error: |
| 89 | + sys.stderr.write('### error: ' + str(error) + '\n') |
| 90 | + sys.exit(1) |
| 91 | + |
| 92 | +if __name__ == '__main__': |
| 93 | + main() |
0 commit comments