summaryrefslogtreecommitdiff
path: root/toxgen.py
blob: 862c80f70a8de29c2b1d46040fb41fb7138265c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""
Produce a tox.ini file from a template config file.

The template config file is a standard tox.ini file with additional sections.
Theses sections will be combined to create new testenv: sections if they do
not exists yet.

See REAME.rst for more detail.
"""

import itertools
import collections
import optparse

try:
    from configparser import ConfigParser
except:
    from ConfigParser import ConfigParser  # noqa


parser = optparse.OptionParser(epilog=__doc__)
parser.add_option('-i', '--input', dest='input',
                  default='tox-tmpl.ini', metavar='FILE')
parser.add_option('-o', '--output', dest='output',
                  default='tox.ini', metavar='FILE')


class AxisItem(object):
    def __init__(self, axis, name, config):
        self.axis = axis
        self.isdefault = name[-1] == '*'
        self.name = name[:-1] if self.isdefault else name
        self.load(config)

    def load(self, config):
        sectionname = 'axis:%s:%s' % (self.axis.name, self.name)
        if config.has_section(sectionname):
            self.options = collections.OrderedDict(config.items(sectionname))
        else:
            self.options = collections.OrderedDict()

        for name, value in self.axis.defaults.items():
            if name not in self.options:
                self.options[name] = value


class Axis(object):
    def __init__(self, name, config):
        self.name = name
        self.load(config)

    def load(self, config):
        self.items = collections.OrderedDict()
        values = config.get('axes', self.name).split(',')
        if config.has_section('axis:%s' % self.name):
            self.defaults = collections.OrderedDict(
                config.items('axis:%s' % self.name)
            )
        else:
            self.defaults = {}
        for value in values:
            self.items[value.strip('*')] = AxisItem(self, value, config)


def render(incfg):
    axes = collections.OrderedDict()

    if incfg.has_section('axes'):
        for axis in incfg.options('axes'):
            axes[axis] = Axis(axis, incfg)

    out = ConfigParser()
    for section in incfg.sections():
        if section == 'axes' or section.startswith('axis:'):
            continue
        out.add_section(section)
        for name, value in incfg.items(section):
            out.set(section, name, value)

    for combination in itertools.product(
            *[axis.items.keys() for axis in axes.values()]):
        options = collections.OrderedDict()

        section_name = (
            'testenv:' + '-'.join([item for item in combination if item])
        )
        section_alt_name = (
            'testenv:' + '-'.join([
                itemname
                for axis, itemname in zip(axes.values(), combination)
                if itemname and not axis.items[itemname].isdefault
            ])
        )
        if section_alt_name == section_name:
            section_alt_name = None

        axes_items = [
            '%s:%s' % (axis, itemname)
            for axis, itemname in zip(axes, combination)
        ]

        for axis, itemname in zip(axes.values(), combination):
            axis_options = axis.items[itemname].options
            if 'constraints' in axis_options:
                constraints = axis_options['constraints'].split('\n')
                for c in constraints:
                    if c.startswith('!') and c[1:] in axes_items:
                        continue
            for name, value in axis_options.items():
                if name in options:
                    options[name] += value
                else:
                    options[name] = value

        constraints = options.pop('constraints', '').split('\n')
        neg_constraints = [c[1:] for c in constraints if c and c[0] == '!']
        if not set(neg_constraints).isdisjoint(axes_items):
            continue

        if not out.has_section(section_name):
            out.add_section(section_name)

        if (section_alt_name and not out.has_section(section_alt_name)):
            out.add_section(section_alt_name)

        for name, value in reversed(options.items()):
            if not out.has_option(section_name, name):
                out.set(section_name, name, value)
            if section_alt_name and not out.has_option(section_alt_name, name):
                out.set(section_alt_name, name, value)

    return out


def main():
    options, args = parser.parse_args()
    tmpl = ConfigParser()
    tmpl.read(options.input)
    with open(options.output, 'wb') as outfile:
        render(tmpl).write(outfile)


if __name__ == '__main__':
    main()