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