diff options
author | Ben Longbons <b.r.longbons@gmail.com> | 2013-08-01 23:00:53 -0700 |
---|---|---|
committer | Ben Longbons <b.r.longbons@gmail.com> | 2013-08-02 22:32:29 -0700 |
commit | e2540283a9e25748c20bde296919040c93a927db (patch) | |
tree | 41aefc5b45c87fd9762656e240d82aa714074ee9 | |
parent | d53d9bf0e55fddd814ad99ec89134a7228390c65 (diff) | |
download | attobuild-e2540283a9e25748c20bde296919040c93a927db.tar.gz attobuild-e2540283a9e25748c20bde296919040c93a927db.tar.bz2 attobuild-e2540283a9e25748c20bde296919040c93a927db.tar.xz attobuild-e2540283a9e25748c20bde296919040c93a927db.zip |
Add flesh to the skeleton
-rw-r--r-- | attoconf/_version.py | 2 | ||||
-rw-r--r-- | attoconf/core.py | 116 | ||||
-rw-r--r-- | attoconf/help.py | 171 | ||||
-rw-r--r-- | attoconf/tests/__init__.py | 0 | ||||
-rw-r--r-- | attoconf/tests/test_core.py | 129 | ||||
-rw-r--r-- | attoconf/tests/test_help.py | 195 | ||||
-rw-r--r-- | attoconf/tests/test_version.py | 94 | ||||
-rwxr-xr-x | demo-project/configure | 2 | ||||
-rwxr-xr-x | test-everything.sh | 2 |
9 files changed, 705 insertions, 6 deletions
diff --git a/attoconf/_version.py b/attoconf/_version.py index 7721bb4..8d9820c 100644 --- a/attoconf/_version.py +++ b/attoconf/_version.py @@ -7,7 +7,7 @@ major = 0 # Incremented for releases with compatible API additions. # This is the number that is usually incremented. -minor = 0 +minor = 1 # Incremented if there is a bugfix release. # Might not be contiguous. diff --git a/attoconf/core.py b/attoconf/core.py index 0c01d5e..8b53209 100644 --- a/attoconf/core.py +++ b/attoconf/core.py @@ -17,21 +17,48 @@ from __future__ import print_function, division, absolute_import +from collections import namedtuple +import os +import sys + +from .help import Help + +# Nothing to see here. Move along. +Option = namedtuple('Option', ['type', 'init']) +class ArgumentError(Exception): pass + +def as_var(name): + return name.lstrip('-').upper() + +def trim_trailing_slashes(path): + p, s = os.path.split(path) + if not s: + return p + return path + class Project(object): ''' A Project is a directory and the list of options that can be set. ''' __slots__ = ( + 'srcdir', 'aliases', 'options', 'help', + 'checks', ) def __init__(self, srcdir): ''' A Project is initially constructed from just the source directory ''' + self.srcdir = trim_trailing_slashes(srcdir) + self.aliases = {} + self.options = {} + self.help = Help() + self.checks = [] def add_help(self, text, hidden): ''' Directly add a line of text to the help. ''' + self.help.add_text(text, hidden) def add_alias(self, key, expansion, help, hidden): ''' Add an alias. @@ -40,8 +67,17 @@ class Project(object): The expansion is a list of other options, which may be aliases. ''' - - def add_option(self, name, init, type, check, help, hidden): + if key in self.aliases: + raise KeyError(key) + expansion = list(expansion) + self.aliases[key] = expansion + if help is None: + help = 'alias for ' + ' '.join(expansion) + self.help.add_option(key, help, hidden) + + def add_option(self, name, init, type, check, + help, hidden, + help_var=None, help_def=None): ''' Add an actual option. This must be passed with a =. @@ -56,27 +92,99 @@ class Project(object): Additionally, a line of help is added, with additional formatting. ''' + if name in self.options: + raise KeyError(name) + self.options[name] = Option(init=init, type=type) + self.checks.append(lambda bld: check(bld, bld.vars[as_var(name)][0])) + if help_var is None: + help_var = as_var(name) + if help_def is None: + help_def = init + if help_def is not None: + help = '%s [%s]' % (help, help_def) + + if help_var != name: + help_opt = '%s=%s' % (name, help_var) + else: + help_opt = name + self.help.add_option(help_opt, help, hidden) + + def do_help(self, opt): + ''' Pseudo type-hook to be registered for --help (calls sys.exit). + ''' + if opt == 'default': + hidden = False + elif opt == 'hidden': + hidden = True + else: + raise ValueError + self.help.print(sys.stdout, hidden) + sys.exit() class Build(object): ''' A Build is a directory and set of options applied to a Project. ''' __slots__ = ( + 'bindir', 'project', 'vars', ) def __init__(self, project, bindir): - ''' A Build is initially constructed from + ''' A Build is initially constructed from a project and a build dir. ''' + self.project = project + self.bindir = trim_trailing_slashes(bindir) + self.vars = {as_var(k): (o.init, 'default') + for k, o in project.options.iteritems()} def apply_arg(self, arg): ''' Parse a single argument, expanding aliases. ''' + alias = self.project.aliases.get(arg) + if alias is not None: + for e in alias: + # TODO: catch recursive aliases + # or should they be caught earlier? + self.apply_arg(e) + return + + if '=' not in arg: + if arg in self.project.options: + raise ArgumentError('Option %s requires an argument' % arg) + elif arg.startswith('-'): + raise ArgumentError('Unknown option %s' % arg) + else: + raise ArgumentError('Unknown environment variable %s' % arg) + + k, a = arg.split('=', 1) + opt = self.project.options.get(k) + if opt is None: + raise sys.exit('Unknown option %s' % k) + self.vars[as_var(k)] = (opt.type(a), 'command-line') def finish(self): ''' With the current set of variables, run all the checks and presumably produce some sort of output. ''' + for check in self.project.checks: + check(self) def configure(self, args, env): - ''' Automatically call apply_arg() a bunch of times, then finish(). + ''' First apply variables from the environment, + then call apply_arg() a bunch of times, then finish(). + ''' + for k in self.vars: + val = env.get(k) + if val is not None: + self.vars[k] = (self.project.options[k].type(val), 'environment') + + for arg in args: + self.apply_arg(arg) + + self.finish() + + def relative_source(self): + ''' Return a relative path from the build tree to the source tree. ''' + return os.path.relpath(os.path.realpath(self.project.srcdir), + os.path.realpath(self.bindir)) diff --git a/attoconf/help.py b/attoconf/help.py new file mode 100644 index 0000000..610efde --- /dev/null +++ b/attoconf/help.py @@ -0,0 +1,171 @@ +# Copyright 2013 Ben Longbons <b.r.longbons@gmail.com> +# +# This file is part of attoconf. +# +# attoconf is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# attoconf is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with attoconf. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import print_function, division, absolute_import + +def put_line_in_width(file, line, width, indent): + ''' Print a line with wrapping. + ''' + line = line.rstrip(' ') + if len(line) <= width or ' ' not in line.lstrip(' '): + # this is not just an optimization + file.writelines([line, '\n']) + return + line += ' ' + indents = indent * ' ' + + initial_spaces = len(line) - len(line.lstrip(' ')) + while line.lstrip(): + space = line.rfind(' ', initial_spaces, width + 1) + if space == -1: + space = line.find(' ', initial_spaces) + if space == -1: + space = len(line) + file.writelines([line[:space], '\n']) + line = indents + line[space+1:].lstrip(' ') + initial_spaces = indent + + +class HelpSection(object): + ''' A help section contains some header lines and some related options. + ''' + __slots__ = ( + 'headers', + 'options', + ) + def __init__(self): + ''' Create an empty help section. + ''' + self.headers = [] + self.options = [] + + def add_text(self, text, hidden): + ''' Add a header line. + ''' + self.headers.append((hidden, text)) + + def add_option(self, name, text, hidden): + ''' Add an option with its description. + ''' + self.options.append((hidden, name, text)) + + def print(self, file, hidden, width): + ''' Format (some of) the help text prettily. + ''' + if self.options: + for oh, name, ht in self.options: + if oh <= hidden: + break + else: + return False + + # options longer than this will be split into multiple lines + split_width = width / 4 - 2 + # longest option + longest_opt = 0 + for oh, name, ht in self.options: + if oh > hidden: + continue + l = len(name) + 2 + if l > split_width: + continue + if l > longest_opt: + longest_opt = l + if longest_opt == 0: + longest_opt = int(split_width) + + for oh, ht in self.headers: + if oh > hidden: + continue + put_line_in_width(file, ht, width, 0) + + for oh, name, ht in self.options: + if oh > hidden: + continue + # no, it's not really that simple + if len(name) > longest_opt: + file.writelines([' ', name, '\n']) + name = '' + line = ' %-*s%s' % (longest_opt, name, ht) + put_line_in_width(file, line, width, longest_opt + 2) + + return True + + +def detect_terminal_width(fd, DEFAULT_WIDTH=float('inf')): + ''' Detect the width of a terminal. + ''' + if not isinstance(fd, int): + fd = getattr(fd, 'fileno', lambda: -1)() + if fd == -1: + return DEFAULT_WIDTH + + import fcntl + import termios + try: + buf = fcntl.ioctl(fd, termios.TIOCGWINSZ, b'xx' * 4) + except IOError as e: + import errno + if e.errno != errno.ENOTTY: + raise + return DEFAULT_WIDTH + import struct + ws_row, ws_col, ws_xpixel, ws_ypixel = struct.unpack('HHHH', buf) + return ws_col + + +class Help(object): + ''' Help collects the set of flavor text and option descriptions + related to arguments. + ''' + __slots__ = ( + 'sections', + ) + def __init__(self): + ''' Help does not take any arguments during construction. + ''' + self.sections = [] + + def add_text(self, text, hidden): + ''' Add a header line. + + This creates a new section if the last line wasn't a header. + ''' + if not self.sections or self.sections[-1].options: + self.sections.append(HelpSection()) + self.sections[-1].add_text(text, hidden) + + def add_option(self, name, text, hidden): + ''' Add an option with its description to the current section. + + This creates a new section only if it is first. + ''' + if not self.sections: + self.sections.append(HelpSection()) + self.sections[-1].add_option(name, text, hidden) + + def print(self, file, hidden, width=0): + ''' Print all the help at the given level of hidden-ness. + ''' + if width == 0: + width = detect_terminal_width(file) + if width < 20: + width = 80 + + for s in self.sections: + if s.print(file, hidden, width): + file.write('\n') diff --git a/attoconf/tests/__init__.py b/attoconf/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/attoconf/tests/__init__.py diff --git a/attoconf/tests/test_core.py b/attoconf/tests/test_core.py new file mode 100644 index 0000000..88bf616 --- /dev/null +++ b/attoconf/tests/test_core.py @@ -0,0 +1,129 @@ +# Copyright 2013 Ben Longbons <b.r.longbons@gmail.com> +# +# This file is part of attoconf. +# +# attoconf is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# attoconf is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with attoconf. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import print_function, division, absolute_import + +import unittest + +from attoconf.core import Project, Build + +from cStringIO import StringIO +import sys + +class ReplacingStdout(object): + __slots__ = ('old', 'new') + def __init__(self, new): + self.old = None + self.new = new + def __enter__(self): + self.old = sys.stdout + sys.stdout = self.new + def __exit__(self, type, value, traceback): + sys.stdout = self.old + del self.old + +class TestProject(unittest.TestCase): + def test_help(self): + proj = Project('foo') + proj.add_help('General:', False) + proj.add_alias('--help', ['--help=default'], + help='display standard help, then exit', hidden=False) + proj.add_option('--help', init=None, + type=proj.do_help, check=None, + help='display some subset of help', hidden=False, + help_var='TYPE') + proj.help.add_option('--help=hidden', + 'display help you should never ever ever care about', True) + proj.add_option('--foo', init='asdf', + type=str, check=None, + help='set frob target', hidden=False) + proj.add_option('--bar', init=None, + type=str, check=None, + help='set frob source', hidden=False, + help_def='FOO') + + build = Build(proj, 'bar') + + out = StringIO() + with ReplacingStdout(out): + with self.assertRaises(SystemExit): + build.apply_arg('--help') + self.assertEqual(out.getvalue(), ''' +General: + --help display standard help, then exit + --help=TYPE display some subset of help + --foo=FOO set frob target [asdf] + --bar=BAR set frob source [FOO] + +'''[1:]) + + out = StringIO() + with ReplacingStdout(out): + with self.assertRaises(SystemExit): + build.apply_arg('--help=hidden') + self.assertEqual(out.getvalue(), ''' +General: + --help display standard help, then exit + --help=TYPE display some subset of help + --help=hidden display help you should never ever ever care about + --foo=FOO set frob target [asdf] + --bar=BAR set frob source [FOO] + +'''[1:]) + + def test_path(self): + proj = Project('foo/') + build = Build(proj, 'bar/') + self.assertEquals(build.project.srcdir, 'foo') + self.assertEquals(build.bindir, 'bar') + self.assertEquals(build.relative_source(), '../foo') + + def test_configure(self): + def check_foo(bld, foo): + self.assertEqual(foo, 'B') + def check_bar(bld, bar): + self.assertEqual(bar, 1) + def check_qux(bld, qux): + self.assertEqual(qux, None) + def check_var(bld, var): + self.assertEqual(var, 'value') + + proj = Project('.') + proj.add_alias('--alias', ['--foo=A', '--bar=1', '--foo=B'], + help=None, hidden=False) + proj.add_option('--foo', init=None, + type=str, check=check_foo, + help='help for string foo', hidden=False) + proj.add_option('--bar', init=0, + type=int, check=check_bar, + help='help for int bar', hidden=False) + proj.add_option('--qux', init=None, + type=int, check=check_qux, + help='help for int qux', hidden=False) + proj.add_option('VAR', init='', + type=str, check=check_var, + help='help for string VAR', hidden=False) + + build = Build(proj, '.') + build.configure(['--alias'], {'VAR': 'value'}) + self.assertEqual(build.vars, + { + 'FOO': ('B', 'command-line'), + 'BAR': (1, 'command-line'), + 'QUX': (None, 'default'), + 'VAR': ('value', 'environment'), + }) diff --git a/attoconf/tests/test_help.py b/attoconf/tests/test_help.py new file mode 100644 index 0000000..ea42c28 --- /dev/null +++ b/attoconf/tests/test_help.py @@ -0,0 +1,195 @@ +# Copyright 2013 Ben Longbons <b.r.longbons@gmail.com> +# +# This file is part of attoconf. +# +# attoconf is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# attoconf is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with attoconf. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import print_function, division, absolute_import + +import unittest + +from attoconf.help import Help, HelpSection, put_line_in_width + +from cStringIO import StringIO + +class TestHelpSection(unittest.TestCase): + def test_basic(self): + sec = HelpSection() + sec.add_text('foo', False) + sec.add_option('--foo', 'FOO', True) + sec.add_text('bar', True) + sec.add_option('--bar', 'BAR', False) + self.assertEqual(sec.headers, [(False, 'foo'), (True, 'bar')]) + self.assertEqual(sec.options, [(True, '--foo', 'FOO'), (False, '--bar', 'BAR')]) + + out = StringIO() + sec.print(out, False, float('inf')) + self.assertEqual(out.getvalue(), '''foo + --bar BAR +''') + + out = StringIO() + sec.print(out, True, float('inf')) + self.assertEqual(out.getvalue(), '''foo +bar + --foo FOO + --bar BAR +''') + + def test_width(self): + out = StringIO() + put_line_in_width(out, 'foo bar baz', float('inf'), 0) + self.assertEqual(out.getvalue(), 'foo bar baz\n') + + out = StringIO() + put_line_in_width(out, 'foo bar baz', 10, 0) + self.assertEqual(out.getvalue(), 'foo bar\nbaz\n') + + out = StringIO() + put_line_in_width(out, 'foo bar baz', 10, 2) + self.assertEqual(out.getvalue(), 'foo bar\n baz\n') + + out = StringIO() + put_line_in_width(out, ' foo bar baz', 10, 0) + self.assertEqual(out.getvalue(), ' foo bar\nbaz\n') + + out = StringIO() + put_line_in_width(out, ' foo bar baz', 10, 0) + self.assertEqual(out.getvalue(), ' foo\nbar baz\n') + + out = StringIO() + put_line_in_width(out, ' foo bar baz', 10, 3) + self.assertEqual(out.getvalue(), ' foo\n bar baz\n') + + out = StringIO() + put_line_in_width(out, ' foo bar baz', 10, 4) + self.assertEqual(out.getvalue(), ' foo\n bar\n baz\n') + + out = StringIO() + put_line_in_width(out, 'really-long-string', float('inf'), 0) + self.assertEqual(out.getvalue(), 'really-long-string\n') + + out = StringIO() + put_line_in_width(out, ' really-long-string', float('inf'), 0) + self.assertEqual(out.getvalue(), ' really-long-string\n') + + out = StringIO() + put_line_in_width(out, 'really-long-string', 10, 0) + self.assertEqual(out.getvalue(), 'really-long-string\n') + + out = StringIO() + put_line_in_width(out, ' really-long-string', 10, 0) + self.assertEqual(out.getvalue(), ' really-long-string\n') + + out = StringIO() + put_line_in_width(out, 'short really-long-string', float('inf'), 0) + self.assertEqual(out.getvalue(), 'short really-long-string\n') + + out = StringIO() + put_line_in_width(out, ' short really-long-string', float('inf'), 0) + self.assertEqual(out.getvalue(), ' short really-long-string\n') + + out = StringIO() + put_line_in_width(out, 'short really-long-string', 10, 0) + self.assertEqual(out.getvalue(), 'short\nreally-long-string\n') + + out = StringIO() + put_line_in_width(out, ' short really-long-string', 10, 0) + self.assertEqual(out.getvalue(), ' short\nreally-long-string\n') + + out = StringIO() + put_line_in_width(out, 'short really-long-string', 10, 1) + self.assertEqual(out.getvalue(), 'short\n really-long-string\n') + + out = StringIO() + put_line_in_width(out, ' short really-long-string', 10, 1) + self.assertEqual(out.getvalue(), ' short\n really-long-string\n') + + def test_print(self): + sec = HelpSection() + sec.add_option('--abcdefghijklmnopqrstuvwxyz', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', False) + + out = StringIO() + sec.print(out, False, 80) + self.assertEqual(out.getvalue(), ''' + --abcdefghijklmnopqrstuvwxyz + ABCDEFGHIJKLMNOPQRSTUVWXYZ +'''[1:]) + + sec.add_option('--foo', 'FOO', False) + sec.add_option('--frob', 'FROB', False) + + out = StringIO() + sec.print(out, False, 80) + self.assertEqual(out.getvalue(), ''' + --abcdefghijklmnopqrstuvwxyz + ABCDEFGHIJKLMNOPQRSTUVWXYZ + --foo FOO + --frob FROB +'''[1:]) + + out = StringIO() + sec.print(out, False, float('inf')) + self.assertEqual(out.getvalue(), ''' + --abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ + --foo FOO + --frob FROB +'''[1:]) + + +class TestHelp(unittest.TestCase): + def test_print(self): + help = Help() + help.add_option('--invisible', 'You can\'t see this', True) + help.add_text('General:', False) + help.add_option('--help', 'show this', False) + help.add_option('--version', 'show that', True) + help.add_text('Other stuff:', False) + help.add_text('with long header:', True) + help.add_option('--foo-lets-make-it-long-just-because', 'I\'m bored', False) + help.add_text('Visibility:', False) + help.add_option('--sneaky', 'This may be surprising. It also works as a demonstration of multi-line wrapping, just because.', True) + + out = StringIO() + help.print(out, True, 80) + self.assertEqual(out.getvalue(), ''' + --invisible You can't see this + +General: + --help show this + --version show that + +Other stuff: +with long header: + --foo-lets-make-it-long-just-because + I'm bored + +Visibility: + --sneaky This may be surprising. It also works as a demonstration of + multi-line wrapping, just because. + +'''[1:]) + + out = StringIO() + help.print(out, False, 80) + self.assertEqual(out.getvalue(), ''' +General: + --help show this + +Other stuff: + --foo-lets-make-it-long-just-because + I'm bored + +'''[1:]) diff --git a/attoconf/tests/test_version.py b/attoconf/tests/test_version.py new file mode 100644 index 0000000..1ce855d --- /dev/null +++ b/attoconf/tests/test_version.py @@ -0,0 +1,94 @@ +# Copyright 2013 Ben Longbons <b.r.longbons@gmail.com> +# +# This file is part of attoconf. +# +# attoconf is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# attoconf is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with attoconf. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import print_function, division, absolute_import + +import unittest + +from attoconf.version import require_version, string as version_string + +class TestVersion(unittest.TestCase): + def test_parse(self): + # don't do this + name, version, dist = version_string.split(' ', 2) + self.assertEqual(name, 'attoconf') + major, minor, patch = [int(x) for x in version.split('.')] + assert dist.startswith('(') + assert dist.endswith(')') + dist = dist[1:-1] + assert '(' not in dist + assert ')' not in dist + assert '\n' not in dist + from attoconf._version import distributor + self.assertEqual(dist, distributor) + + def test_check(self): + from attoconf._version import major, minor, patch + with self.assertRaises(SystemExit): + require_version(major - 1, minor - 1, patch - 1) + with self.assertRaises(SystemExit): + require_version(major - 1, minor - 1, patch + 0) + with self.assertRaises(SystemExit): + require_version(major - 1, minor - 1, patch + 1) + with self.assertRaises(SystemExit): + require_version(major - 1, minor + 0, patch - 1) + with self.assertRaises(SystemExit): + require_version(major - 1, minor + 0, patch + 0) + with self.assertRaises(SystemExit): + require_version(major - 1, minor + 0, patch + 1) + with self.assertRaises(SystemExit): + require_version(major - 1, minor + 1, patch - 1) + with self.assertRaises(SystemExit): + require_version(major - 1, minor + 1, patch + 0) + with self.assertRaises(SystemExit): + require_version(major - 1, minor + 1, patch + 1) + if 1: + require_version(major + 0, minor - 1, patch - 1) + if 1: + require_version(major + 0, minor - 1, patch + 0) + if 1: + require_version(major + 0, minor - 1, patch + 1) + if 1: + require_version(major + 0, minor + 0, patch - 1) + if 1: + require_version(major + 0, minor + 0, patch + 0) + with self.assertRaises(SystemExit): + require_version(major + 0, minor + 0, patch + 1) + with self.assertRaises(SystemExit): + require_version(major + 0, minor + 1, patch - 1) + with self.assertRaises(SystemExit): + require_version(major + 0, minor + 1, patch + 0) + with self.assertRaises(SystemExit): + require_version(major + 0, minor + 1, patch + 1) + with self.assertRaises(SystemExit): + require_version(major + 1, minor - 1, patch - 1) + with self.assertRaises(SystemExit): + require_version(major + 1, minor - 1, patch + 0) + with self.assertRaises(SystemExit): + require_version(major + 1, minor - 1, patch + 1) + with self.assertRaises(SystemExit): + require_version(major + 1, minor + 0, patch - 1) + with self.assertRaises(SystemExit): + require_version(major + 1, minor + 0, patch + 0) + with self.assertRaises(SystemExit): + require_version(major + 1, minor + 0, patch + 1) + with self.assertRaises(SystemExit): + require_version(major + 1, minor + 1, patch - 1) + with self.assertRaises(SystemExit): + require_version(major + 1, minor + 1, patch + 0) + with self.assertRaises(SystemExit): + require_version(major + 1, minor + 1, patch + 1) diff --git a/demo-project/configure b/demo-project/configure index 8f09a35..dc29870 100755 --- a/demo-project/configure +++ b/demo-project/configure @@ -23,7 +23,7 @@ import os import sys from attoconf.version import require_version, string as version_string -require_version(0, 0) +require_version(0, 1) from attoconf.core import Project, Build diff --git a/test-everything.sh b/test-everything.sh new file mode 100755 index 0000000..e16866b --- /dev/null +++ b/test-everything.sh @@ -0,0 +1,2 @@ +#!/bin/sh +python -m unittest discover "$@" |