summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Longbons <b.r.longbons@gmail.com>2013-08-06 14:44:13 -0700
committerBen Longbons <b.r.longbons@gmail.com>2013-08-06 14:45:49 -0700
commitb26aadbf16f4873411f77f08ebdd228528c5723b (patch)
tree6d8fc832aed437a159fc1ee162f826c486671428
parent46e9b987ff689c1acfe29c7b980298408d1b95a6 (diff)
downloadattobuild-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.py2
-rw-r--r--attoconf/core.py3
-rw-r--r--attoconf/lib/arches.py10
-rw-r--r--attoconf/lib/c.py16
-rw-r--r--attoconf/lib/install.py25
-rw-r--r--attoconf/lib/lex.py4
-rw-r--r--attoconf/lib/yacc.py4
-rw-r--r--attoconf/tests/test_core.py13
-rw-r--r--attoconf/tests/test_types.py62
-rw-r--r--attoconf/types.py95
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