# *****************************************************************************
# conduct - CONvenient Construction Tool
#
# This program 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 2 of the License, or (at your option) any later
# version.
#
# This program 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
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Alexander Lenz <alexander.lenz@posteo.de>
# Georg Brandl <georg@python.org>
#
# *****************************************************************************
import re
from os import path
from collections import Iterable
import conduct
from conduct.util.container import OrderedAttrDict
[docs]class Parameter(object):
'''
Some kind of parameter.
No default means mandatory.
'''
def __init__(self, type=str, description='Undescribed', default=None):
self.type = type
self.description = description
self.default = default
self.classname = None
if default is not None:
self.validate(default)
[docs] def validate(self, value):
self.type(value)
[docs]class Referencer(object):
def __init__(self, fmt):
self.fmt = fmt
[docs] def evaluate(self, chain, valType=str):
result = self.fmt.format(chain=Dataholder(chain.params),
steps=Dataholder(chain.steps),
app=conduct.app,
cfg=OrderedAttrDict(conduct.app.cfg),
sysinfo=OrderedAttrDict(conduct.app.sysinfo),
buildinfo=OrderedAttrDict(conduct.app.buildinfo),
)
return valType(result)
[docs]class Dataholder(object):
def __init__(self, modelDict):
self._modelDict = modelDict
def __getattr__(self, name):
if name in self._modelDict:
return self._modelDict[name]
# validators for parameter's type
[docs]def convdoc(conv):
if isinstance(conv, type):
return conv.__name__
return conv.__doc__ or ''
[docs]class listof(object):
def __init__(self, conv):
self.__doc__ = 'a list of %s' % convdoc(conv)
self.conv = conv
def __call__(self, val=None):
val = val if val is not None else []
if not isinstance(val, (list, tuple)):
raise ValueError('value needs to be a list')
return list(map(self.conv, val))
[docs]class nonemptylistof(object):
def __init__(self, conv):
self.__doc__ = 'a non-empty list of %s' % convdoc(conv)
self.conv = conv
def __call__(self, val=None):
if val is None:
return [self.conv()]
if not isinstance(val, (list, tuple)) or len(val) < 1:
raise ValueError('value needs to be a nonempty list')
return list(map(self.conv, val))
[docs]def nonemptystring(s):
"""a non-empty string"""
if not isinstance(s, str) or s == '':
raise ValueError('must be a non-empty string!')
return s
[docs]class tupleof(object):
def __init__(self, *types):
if not types:
raise ValueError('tupleof() needs some types as arguments')
self.__doc__ = 'a tuple of (' + ', '.join(map(convdoc, types)) + ')'
self.types = [typeconv for typeconv in types]
def __call__(self, val=None):
if val is None:
return tuple(type() for type in self.types)
if not isinstance(val, (list, tuple)) or not len(self.types) == len(val):
raise ValueError('value needs to be a %d-tuple' % len(self.types))
return tuple(t(v) for (t, v) in zip(self.types, val))
[docs]def limits(val=None):
"""a tuple of lower and upper limit"""
val = val if val is not None else (0, 0)
if not isinstance(val, (list, tuple)) or len(val) != 2:
raise ValueError('value must be a list or tuple and have 2 elements')
ll = float(val[0])
ul = float(val[1])
if not ll <= ul:
raise ValueError('upper limit must be greater than lower limit')
return (ll, ul)
[docs]class dictof(object):
def __init__(self, keyconv, valconv):
self.__doc__ = 'a dict of %s keys and %s values' % \
(convdoc(keyconv), convdoc(valconv))
self.keyconv = keyconv
self.valconv = valconv
def __call__(self, val=None):
val = val if val is not None else {}
if not isinstance(val, dict):
raise ValueError('value needs to be a dict')
ret = {}
for k, v in val.items():
ret[self.keyconv(k)] = self.valconv(v)
return ret
[docs]class intrange(object):
def __init__(self, fr, to):
fr = int(fr)
to = int(to)
if not fr <= to:
raise ValueError('intrange must fulfill from <= to, given was '
'[%f, %f]' % (fr, to))
self.__doc__ = 'an integer in the range [%d, %d]' % (fr, to)
self.fr = fr
self.to = to
def __call__(self, val=None):
if val is None:
return self.fr
val = int(val)
if not self.fr <= val <= self.to:
raise ValueError('value needs to fulfill %d <= x <= %d' %
(self.fr, self.to))
return val
[docs]class floatrange(object):
def __init__(self, fr, to=None):
fr = float(fr)
if to is not None:
to = float(to)
if not fr <= to:
raise ValueError('floatrange must fulfill from <= to, given was '
'[%f, %f]' % (fr, to))
self.__doc__ = 'a float in the range [%f, %f]' % (fr, to)
else:
self.__doc__ = 'a float >= %f' % fr
self.fr = fr
self.to = to
def __call__(self, val=None):
if val is None:
return self.fr
val = float(val)
if self.to is not None:
if not self.fr <= val <= self.to:
raise ValueError('value needs to fulfill %d <= x <= %d' %
(self.fr, self.to))
else:
if not self.fr <= val:
raise ValueError('value needs to fulfill %d <= x' % self.fr)
return val
[docs]class oneof(object):
def __init__(self, *vals):
self.__doc__ = 'one of ' + ', '.join(map(repr, vals))
if len(vals) == 1 and isinstance(vals[0], Iterable):
self.vals = vals[0]
else:
self.vals = vals
def __call__(self, val=None):
if val is None:
return self.vals[0]
if val not in self.vals:
raise ValueError('invalid value: %r, must be one of %s' %
(val, ', '.join(map(repr, self.vals))))
return val
[docs]class oneofdict(object):
def __init__(self, vals):
self.__doc__ = 'one of ' + ', '.join(map(repr, vals.values()))
self.vals = vals
def __call__(self, val=None):
if val in self.vals:
val = self.vals[val]
elif val not in self.vals.values():
raise ValueError('invalid value: %s, must be one of %s' %
(val, ', '.join(map(repr, self.vals.values()))))
return val
[docs]class none_or(object):
def __init__(self, conv):
self.__doc__ = 'None or %s' % convdoc(conv)
self.conv = conv
def __call__(self, val=None):
if val is None:
return None
return self.conv(val)
# see http://stackoverflow.com/questions/3217682/checking-validity-of-email-in-django-python
# for source
mailaddress_re = re.compile(
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+([A-Z]{2,99}|XN[A-Z0-9-]+)\.?$', # domain
re.IGNORECASE)
[docs]def mailaddress(val=None):
"""a valid mail address"""
if val in ('', None):
return ''
parts = val.split('@')
parts[-1] = parts[-1].encode('idna').decode('ascii')
val = '@'.join(parts)
if '>' in val and not val.strip().endswith('>'):
raise ValueError('%r is not a valid email address' % val)
if not mailaddress_re.match(val.strip().partition('<')[-1].rpartition('>')[0] or val):
raise ValueError('%r is not a valid email address' % val)
return val
[docs]def absolute_path(val=''):
"""an absolute file path"""
val = str(val)
if path.isabs(val):
return val
raise ValueError('%r is not a valid absolute path (should start with %r)' %
(val, path.sep))
[docs]def relative_path(val=''):
"""a relative path, may not use ../../.. tricks"""
val = path.normpath(str(val))
if path.isabs(val):
raise ValueError('%r is not a valid relative path (should NOT start '
'with %r)' % (val, path.sep))
if val[:2] != '..':
return val
raise ValueError('%r is not a valid relative path (traverses outside)' % val)
[docs]def expanded_path(val=''):
return path.expanduser(path.expandvars(val))
[docs]def subdir(val=''):
"""a relative subdir (a string NOT containing any path.sep)"""
val = str(val)
for sep in [path.sep, '\\', '/']:
if sep in val:
raise ValueError('%r is not a valid subdirectory (contains a %r)' %
(val, sep))
return val
[docs]def anytype(val=None):
"""any value"""
return val
ipv4_re = re.compile(
r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}'
r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
)
[docs]def ipv4(val='0.0.0.0'):
"""a IP v4 address"""
if val in ('', None):
return ''
val = str(val)
res = ipv4_re.match(val)
if not res or res.group() != res.string:
raise ValueError('%r is not a valid IPv4 address' % val)
return val
[docs]def host(val=''):
"""a host[:port] value"""
if not isinstance(val, str):
raise ValueError('must be a string!')
if val.count(':') > 1:
raise ValueError('%r is not in the form host_name[:port]')
if ':' in val:
_, p = val.split(':')
try:
p = int(p)
if not 0 < p < 65536:
raise ValueError()
except ValueError:
raise ValueError('%r does not contain a valid port number' % val)
return
[docs]def callableobj(obj):
if callable(obj):
return obj
raise ValueError('%r is not callable' % obj)
[docs]class referencer_or(object):
def __init__(self, conv):
self.__doc__ = 'Referencer or %s' % convdoc(conv)
self.conv = conv
def __call__(self, val=None):
if isinstance(val, str):
return Referencer(val)
return self.conv(val)