diff options
author | Ben Longbons <b.r.longbons@gmail.com> | 2013-08-06 14:44:13 -0700 |
---|---|---|
committer | Ben Longbons <b.r.longbons@gmail.com> | 2013-08-06 14:45:49 -0700 |
commit | b26aadbf16f4873411f77f08ebdd228528c5723b (patch) | |
tree | 6d8fc832aed437a159fc1ee162f826c486671428 | |
parent | 46e9b987ff689c1acfe29c7b980298408d1b95a6 (diff) | |
download | attobuild-b26aadbf16f4873411f77f08ebdd228528c5723b.tar.gz attobuild-b26aadbf16f4873411f77f08ebdd228528c5723b.tar.bz2 attobuild-b26aadbf16f4873411f77f08ebdd228528c5723b.tar.xz attobuild-b26aadbf16f4873411f77f08ebdd228528c5723b.zip |
Move all types to their own module
-rw-r--r-- | attoconf/_version.py | 2 | ||||
-rw-r--r-- | attoconf/core.py | 3 | ||||
-rw-r--r-- | attoconf/lib/arches.py | 10 | ||||
-rw-r--r-- | attoconf/lib/c.py | 16 | ||||
-rw-r--r-- | attoconf/lib/install.py | 25 | ||||
-rw-r--r-- | attoconf/lib/lex.py | 4 | ||||
-rw-r--r-- | attoconf/lib/yacc.py | 4 | ||||
-rw-r--r-- | attoconf/tests/test_core.py | 13 | ||||
-rw-r--r-- | attoconf/tests/test_types.py | 62 | ||||
-rw-r--r-- | attoconf/types.py | 95 |
10 files changed, 178 insertions, 56 deletions
diff --git a/attoconf/_version.py b/attoconf/_version.py index 46ab4ff..cf56cb9 100644 --- a/attoconf/_version.py +++ b/attoconf/_version.py @@ -11,7 +11,7 @@ minor = 3 # Incremented if there is a bugfix release. # Might not be contiguous. -patch = 0 +patch = 1 # Reserved for distributors and forks. # Contains arbitrary text, but no parentheses or newlines. diff --git a/attoconf/core.py b/attoconf/core.py index 0134474..39a3298 100644 --- a/attoconf/core.py +++ b/attoconf/core.py @@ -95,6 +95,7 @@ class Project(object): ''' if name in self.options: raise KeyError(name) + assert type.__module__ == 'attoconf.types', '%s.%s' % (type.__module__, type.__name__) self.options[name] = Option(init=init, type=type) if check is not None: self.checks.append( @@ -125,6 +126,8 @@ class Project(object): raise ValueError self.help.print(sys.stdout, hidden) sys.exit() +# sneaky +Project.do_help.im_func.__module__ = 'attoconf.types' class Build(object): ''' A Build is a directory and set of options applied to a Project. diff --git a/attoconf/lib/arches.py b/attoconf/lib/arches.py index bafef38..c168292 100644 --- a/attoconf/lib/arches.py +++ b/attoconf/lib/arches.py @@ -18,15 +18,7 @@ from __future__ import print_function, division, absolute_import from ..classy import ClassyProject - -def triple(s): - # Triples do not, in fact, follow a regular pattern. - # Some have only two segments, some appear to have four ... - # Also, sometimes a wrong thing is used as a triple. - # All we *really* care about is generating the tool names. - if s.startswith('-') or s.endswith('-') or '-' not in s[1:-1]: - raise ValueError('Probably not a triple') - return s +from ..types import triple def host(build, HOST): if HOST is None: diff --git a/attoconf/lib/c.py b/attoconf/lib/c.py index 75bf931..c4eefdb 100644 --- a/attoconf/lib/c.py +++ b/attoconf/lib/c.py @@ -19,10 +19,10 @@ from __future__ import print_function, division, absolute_import import errno import os -from shlex import split as shell import subprocess from .arches import Arches2 +from ..types import ShellList class TestError(Exception): pass @@ -229,11 +229,11 @@ class Link(Arches2): def vars(self): super(Link, self).vars() self.add_option('LDFLAGS', init=[], - type=shell, check=ldflags, + type=ShellList, check=ldflags, help='linker flags, e.g. -L<lib dir> if you have libraries in a nonstandard directory <lib dir>', hidden=False) self.add_option('LIBS', init=[], - type=shell, check=libs, + type=ShellList, check=libs, help='libraries to pass to the linker, e.g. -l<library>', hidden=False) @@ -242,7 +242,7 @@ class Preprocess(Arches2): def vars(self): super(Preprocess, self).vars() self.add_option('CPPFLAGS', init=[], - type=shell, check=cppflags, + type=ShellList, check=cppflags, help='C/C++/Objective C preprocessor flags, e.g. -I<include dir> if you have headers in a nonstandard directory <include dir>', hidden=False) @@ -251,10 +251,10 @@ class C(Link, Preprocess): def vars(self): super(C, self).vars() self.add_option('CC', init=['gcc'], - type=shell, check=cc, + type=ShellList, check=cc, help='C compiler command', hidden=False) self.add_option('CFLAGS', init=['-O2', '-g'], - type=shell, check=cflags, + type=ShellList, check=cflags, help='C compiler flags', hidden=False) class Cxx(Link, Preprocess): @@ -262,8 +262,8 @@ class Cxx(Link, Preprocess): def vars(self): super(Cxx, self).vars() self.add_option('CXX', init=['g++'], - type=shell, check=cxx, + type=ShellList, check=cxx, help='C++ compiler command', hidden=False) self.add_option('CXXFLAGS', init=['-O2', '-g'], - type=shell, check=cxxflags, + type=ShellList, check=cxxflags, help='C++ compiler flags', hidden=False) diff --git a/attoconf/lib/install.py b/attoconf/lib/install.py index 3e135e8..d8d6cf5 100644 --- a/attoconf/lib/install.py +++ b/attoconf/lib/install.py @@ -20,28 +20,7 @@ from __future__ import print_function, division, absolute_import import os from ..classy import ClassyProject -from ..core import trim_trailing_slashes - - -def word(s): - if ' ' in s: - raise ValueError('not a word: %s' % s) - return s - - -def version(s): - if s.startswith('v'): - s = s[1:] - [int(b) for b in s.split('.')] - return s - - -def filepath(s): - s = trim_trailing_slashes(s) - # must be absolute *and* canonical - if s != os.path.abspath(s): - raise ValueError('Not an absolute, canonical pathname: %s' % s) - return s +from ..types import shell_word, version, filepath def exec_prefix(build, EPREFIX): @@ -208,7 +187,7 @@ class Install(ClassyProject): def general(self): super(Install, self).general() self.add_option('--package', init=self.package, - type=word, check=None, + type=shell_word, check=None, help='Short name of this package (don\'t change!)', hidden=True) self.add_option('--package-version', init=self.package_version, diff --git a/attoconf/lib/lex.py b/attoconf/lib/lex.py index d25455b..5f8ff43 100644 --- a/attoconf/lib/lex.py +++ b/attoconf/lib/lex.py @@ -18,7 +18,7 @@ from __future__ import print_function, division, absolute_import from ..classy import ClassyProject -from .c import shell +from ..types import ShellList def flex(build, FLEX): # TODO actually test it @@ -29,6 +29,6 @@ class Flex(ClassyProject): def vars(self): super(Flex, self).vars() self.add_option('FLEX', init=['flex'], - type=shell, check=flex, + type=ShellList, check=flex, help='Lexical analyzer command', hidden=False) diff --git a/attoconf/lib/yacc.py b/attoconf/lib/yacc.py index c8ddb03..885bbd3 100644 --- a/attoconf/lib/yacc.py +++ b/attoconf/lib/yacc.py @@ -18,7 +18,7 @@ from __future__ import print_function, division, absolute_import from ..classy import ClassyProject -from .c import shell +from ..types import ShellList def bison(build, BISON): # TODO actually test it @@ -29,6 +29,6 @@ class Bison(ClassyProject): def vars(self): super(Bison, self).vars() self.add_option('BISON', init=['bison'], - type=shell, check=bison, + type=ShellList, check=bison, help='Lexical analyzer command', hidden=False) diff --git a/attoconf/tests/test_core.py b/attoconf/tests/test_core.py index f9854e8..c10e8c7 100644 --- a/attoconf/tests/test_core.py +++ b/attoconf/tests/test_core.py @@ -20,6 +20,7 @@ from __future__ import print_function, division, absolute_import import unittest from attoconf.core import Project, Build +from attoconf.types import uint, shell_word from cStringIO import StringIO import sys @@ -50,10 +51,10 @@ class TestProject(unittest.TestCase): help='display help you should never ever ever care about', hidden=True) proj.add_option('--foo', init='asdf', - type=str, check=None, + type=shell_word, check=None, help='set frob target', hidden=False) proj.add_option('--bar', init=None, - type=str, check=None, + type=shell_word, check=None, help='set frob source', hidden=False, help_def='FOO') @@ -107,16 +108,16 @@ General: 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, + type=shell_word, check=check_foo, help='help for string foo', hidden=False) proj.add_option('--bar', init=0, - type=int, check=check_bar, + type=uint, check=check_bar, help='help for int bar', hidden=False) proj.add_option('--qux', init=None, - type=int, check=check_qux, + type=uint, check=check_qux, help='help for int qux', hidden=False) proj.add_option('VAR', init='', - type=str, check=check_var, + type=shell_word, check=check_var, help='help for string VAR', hidden=False) build = Build(proj, '.') diff --git a/attoconf/tests/test_types.py b/attoconf/tests/test_types.py new file mode 100644 index 0000000..662c0a3 --- /dev/null +++ b/attoconf/tests/test_types.py @@ -0,0 +1,62 @@ +# 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.types import enum, ShellList + +class TestEnum(unittest.TestCase): + def test_stuff(self): + foobar = enum('foo', 'bar') + foobar('foo') + foobar('bar') + with self.assertRaisesRegexp(ValueError, "'baz' not in {foo, bar}"): + foobar('baz') + +class TestShell(unittest.TestCase): + def test_str(self): + sh0 = ShellList('\\ ') + self.assertEqual("' '", str(sh0)) + self.assertEqual([' '], sh0.list) + sh1 = ShellList(' foo ') + self.assertEqual('foo', str(sh1)) + self.assertEqual(['foo'], sh1.list) + sh2 = ShellList(' "foo bar " baz') + self.assertEqual("'foo bar ' baz", str(sh2)) + self.assertEqual(['foo bar ', 'baz'], sh2.list) + sh3 = ShellList(""" "foo\\ bar\\"" 'baz\\ qux' ''\\''' frob\\ it """) + self.assertEqual("""'foo\\ bar"' 'baz\\ qux' ''"'"'' 'frob it'""", str(sh3)) + self.assertEqual(['foo\\ bar"', 'baz\\ qux', "'", 'frob it'], sh3.list) + + def test_list(self): + sh1 = ShellList(['foo']) + self.assertEqual('foo', str(sh1)) + sh2 = ShellList(['foo bar ', 'baz']) + self.assertEqual("'foo bar ' baz", str(sh2)) + sh3 = ShellList(['foo\\ bar"', 'baz\\ qux', "'", 'frob it']) + self.assertEqual('\'foo\\ bar"\' \'baz\\ qux\' \'\'"\'"\'\' \'frob it\'', str(sh3)) + + def test_add(self): + sh0 = ShellList('') + self.assertEqual(str(sh0 + sh0), '') + self.assertEqual((sh0 + sh0).list, []) + sh1 = ShellList(['foo bar', 'baz']) + self.assertEqual(str(sh0 + sh1), "'foo bar' baz") + self.assertEqual((sh0 + sh1).list, sh1.list) + self.assertEqual(str(sh1 + sh1), "'foo bar' baz 'foo bar' baz") diff --git a/attoconf/types.py b/attoconf/types.py index 6ddd37f..f4cd178 100644 --- a/attoconf/types.py +++ b/attoconf/types.py @@ -17,9 +17,94 @@ from __future__ import print_function, division, absolute_import -def enum(*args): - def enum_type(s): - if s in args: +import os +from pipes import quote as shell_quote +from shlex import split as shell_split + +from .core import trim_trailing_slashes + + +class IntRange(object): + def __init__(self, min, max): + self.min = min + self.max = max + + def __call__(self, s): + i = int(s) + if self.min <= i <= self.max: + return i + raise ValueError('%d is out of range' % i) + +sint = IntRange(float('-inf'), float('inf')) +uint = IntRange(0, float('inf')) + + +class enum(object): + __slots__ = ('args',) + + def __init__(self, *args): + self.args = args + + def __call__(self, s): + if s in self.args: return s - raise ValueError('%r not in {%s}' % (s, ', '.join(args))) - return enum_type + raise ValueError('%r not in {%s}' % (s, ', '.join(self.args))) + + +class ShellList(object): + ''' An argument type representing a sequence of 0 or more arguments + ''' + __slots__ = ('list',) + def __init__(self, arg): + if isinstance(arg, str): + self.list = shell_split(arg) + elif isinstance(arg, list): + self.list = arg[:] + elif isinstance(arg, ShellList): + self.list = arg.list[:] + else: + raise TypeError('arg is an instance of %s' % type(arg).__name__) + + def __str__(self): + return ' '.join(shell_quote(a) for a in self.list) + + def __add__(self, other): + if isinstance(other, str): + other = shell_split(other) + elif isinstance(other, ShellList): + other = other.list + elif not isinstance(other, list): + raise TypeError('arg is an instance of %s' % type(arg).__name__) + return ShellList(self.list + other) + + +def shell_word(s): + if s != shell_quote(s): + raise ValueError('not a word: %r' % s) + return s + + +def version(s): + if s.startswith('v'): + s = s[1:] + for b in s.split('.'): + int(b) + return s + + +def filepath(s): + s = trim_trailing_slashes(s) + # must be absolute *and* canonical + if s != os.path.abspath(s): + raise ValueError('Not an absolute, canonical pathname: %s' % s) + return s + + +def triple(s): + # Triples do not, in fact, follow a regular pattern. + # Some have only two segments, some appear to have four ... + # Also, sometimes a wrong thing is used as a triple. + # All we *really* care about is generating the tool names. + if s.startswith('-') or s.endswith('-') or '-' not in s[1:-1]: + raise ValueError('Probably not a triple') + return s |