1 2from . import common 3 4import re 5 6 7class ConfigureError(common.TaiseiError): 8 pass 9 10 11class NameNotDefinedError(ConfigureError): 12 def __init__(self, type, name): 13 return super().__init__("{} '{}' is not defined".format(type, name)) 14 15 16class VariableNotDefinedError(NameNotDefinedError): 17 def __init__(self, name): 18 return super().__init__('Variable', name) 19 20 21class MacroNotDefinedError(NameNotDefinedError): 22 def __init__(self, name): 23 return super().__init__('Macro', name) 24 25 26def make_macros(args=common.default_args): 27 from . import version 28 vobj = version.get(args=args) 29 30 def macro_version(format): 31 return vobj.format(format) 32 33 def macro_file(path): 34 with open(path, 'r') as infile: 35 return infile.read() 36 37 return { 38 'VERSION': macro_version, 39 'FILE': macro_file, 40 } 41 42 43def configure(source, variables, *, default=None, use_macros=True, prefix='${', suffix='}', args=common.default_args): 44 prefix = re.escape(prefix) 45 suffix = re.escape(suffix) 46 47 if use_macros: 48 pattern = re.compile(prefix + r'(\w+(?:\(.*?\))?)' + suffix, re.A) 49 macros = make_macros(args) 50 else: 51 pattern = re.compile(prefix + r'(\w+)' + suffix, re.A) 52 53 def sub(match): 54 var = match.group(1) 55 56 if var[-1] == ')': 57 macro_name, macro_arg = var.split('(', 1) 58 macro_arg = macro_arg[:-1] 59 60 try: 61 macro = macros[macro_name] 62 except KeyError: 63 raise MacroNotDefinedError(var) 64 65 val = macro(macro_arg) 66 else: 67 try: 68 val = variables[var] 69 except KeyError: 70 if default is not None: 71 val = default 72 73 raise VariableNotDefinedError(var) 74 75 return str(val) 76 77 return pattern.sub(sub, source) 78 79 80def configure_file(inpath, outpath, variables, **options): 81 with open(inpath, "r") as infile: 82 result = configure(infile.read(), variables, **options) 83 84 common.update_text_file(outpath, result) 85 86 87def add_configure_args(parser): 88 def parse_definition(defstring): 89 return defstring.split('=', 1) 90 91 def parse_definition_from_file(defstring): 92 var, fpath = parse_definition(defstring) 93 94 with open(fpath, 'r') as infile: 95 return (var, infile.read()) 96 97 parser.add_argument('--define', '-D', 98 type=parse_definition, 99 action='append', 100 metavar='VARIABLE=VALUE', 101 dest='variables', 102 default=[], 103 help='define a variable that may appear in template, can be used multiple times' 104 ) 105 106 parser.add_argument('--define-from-file', '-F', 107 type=parse_definition_from_file, 108 action='append', 109 metavar='VARIABLE=FILE', 110 dest='variables', 111 default=[], 112 help='like --define, but the value is read from the specified file' 113 ) 114 115 parser.add_argument('--prefix', 116 default='${', 117 help='prefix for substitution expressions, default: ${' 118 ) 119 120 parser.add_argument('--suffix', 121 default='}', 122 help='suffix for substitution expressions, default: }' 123 ) 124 125 126def main(args): 127 import argparse 128 129 parser = argparse.ArgumentParser(description='Generate a text file based on a template.', prog=args[0]) 130 131 parser.add_argument('input', help='the template file') 132 parser.add_argument('output', help='the output file') 133 134 add_configure_args(parser) 135 common.add_common_args(parser, depfile=True) 136 137 args = parser.parse_args(args[1:]) 138 args.variables = dict(args.variables) 139 140 configure_file(args.input, args.output, args.variables, 141 prefix=args.prefix, 142 suffix=args.suffix, 143 args=args 144 ) 145 146 if args.depfile is not None: 147 common.write_depfile(args.depfile, args.output, [args.input, __file__]) 148