Files
deb-python-taskflow/tools/toxgen.py
Joshua Harlow 3d437df749 Add envs for different sqlalchemy versions
Adjust tests to skip the sqlalchemy test if
sqlalchemy is not installed. Adjust examples
to fallback to a directory based backend if
the sqlalchemy does not load or is not available.

Include a updated tox.ini (generated from the
toxgen.py script) that includes the new venv
variations.

Change-Id: I7686f09901a9b65d7c81b4e037b5bffc24aa7ef7
2014-01-09 11:09:08 -08:00

212 lines
6.4 KiB
Python
Executable File

#!/usr/bin/env python
# From: https://bitbucket.org/cdevienne/toxgen (pypi soon hopefully) and
# modified slightly to work in python 2.6 and set some values that are not
# being set.
#
# TODO(harlowja): remove me when toxgen is a pypi package.
"""
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.
"""
import collections
import itertools
import optparse
import os
import six
from six.moves import configparser
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
HEADER = '# DO NOT EDIT THIS FILE - it is machine generated from %(filename)s'
SKIP_VENVS = frozenset(['venv'])
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 = dict(config.items(sectionname))
else:
self.options = dict()
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 = dict()
values = config.get('axes', self.name).split(',')
if config.has_section('axis:%s' % self.name):
self.defaults = dict(
config.items('axis:%s' % self.name)
)
else:
self.defaults = {}
for value in values:
self.items[value.strip('*')] = AxisItem(self, value, config)
def format_list(contents, max_len=80, sep=","):
lines = []
for line in contents:
if not lines:
lines.append(line + ",")
else:
last_len = len(lines[-1])
if last_len + len(line) >= max_len:
lines.append(str(line) + sep)
else:
lines[-1] = lines[-1] + str(line) + sep
return "\n".join(lines).rstrip(",")
def render(incfg, filename, adjust_envlist=True):
test_envs = set()
for s in incfg.sections():
if s.startswith("testenv:"):
env = s[len("testenv:"):].strip()
if env in SKIP_VENVS or not env:
continue
test_envs.add(env)
test_envs = [s for s in test_envs if s]
try:
envlist = incfg.get("tox", 'envlist')
envlist = [e.strip() for e in envlist.split(",")]
envlist = set([e for e in envlist if e])
except (configparser.NoOptionError, configparser.NoSectionError):
envlist = set()
for e in test_envs:
if e not in envlist:
envlist.add(e)
if not incfg.has_section("tox"):
incfg.add_section("tox")
incfg.set("tox", "envlist",
format_list(list(sorted(envlist)), max_len=-1))
text = six.StringIO()
incfg.write(text)
contents = [
HEADER % {'filename': os.path.basename(filename)},
'',
# Remove how configparser uses tabs instead of spaces, madness...
text.getvalue().replace("\t", " " * 4),
]
return "\n".join(contents)
def compile_template(incfg):
axes = dict()
if incfg.has_section('axes'):
for axis in incfg.options('axes'):
axes[axis] = Axis(axis, incfg)
out = configparser.ConfigParser(dict_type=OrderedDict)
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)
items = [axis.items.keys() for axis in axes.values()]
for combination in itertools.product(*items):
options = {}
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.ConfigParser()
with open(options.input, 'rb') as fh:
tmpl.readfp(fh, filename=options.input)
with open(options.output, 'wb') as outfile:
text = render(compile_template(tmpl), options.input)
outfile.write(text)
if __name__ == '__main__':
main()