| 1 | # -*- coding: utf-8 -*-
|
|---|
| 2 | """
|
|---|
| 3 | Created on Tue Apr 2 18:31:47 2013
|
|---|
| 4 |
|
|---|
| 5 | @author: pietro
|
|---|
| 6 | """
|
|---|
| 7 | from __future__ import (nested_scopes, generators, division, absolute_import,
|
|---|
| 8 | with_statement, print_function, unicode_literals)
|
|---|
| 9 | import re
|
|---|
| 10 |
|
|---|
| 11 | from grass.pygrass.modules.interface.docstring import docstring_property
|
|---|
| 12 | from grass.pygrass.modules.interface.read import GETTYPE, element2dict, DOC
|
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 | def _check_value(param, value):
|
|---|
| 16 | """Function to check the correctness of a value and
|
|---|
| 17 | return the checked value and the original.
|
|---|
| 18 | """
|
|---|
| 19 | must_val = 'The Parameter <%s>, must be one of the following values: %r'
|
|---|
| 20 | req = 'The Parameter <%s>, require: %s, get: %s instead: %r\n%s'
|
|---|
| 21 | string = (type(b''), type(u''))
|
|---|
| 22 |
|
|---|
| 23 | def raiseexcpet(exc, param, ptype, value):
|
|---|
| 24 | """Function to modifa the error message"""
|
|---|
| 25 | msg = req % (param.name, param.typedesc, ptype, value, str(exc))
|
|---|
| 26 | if isinstance(exc, ValueError):
|
|---|
| 27 | raise ValueError(msg)
|
|---|
| 28 | elif isinstance(exc, TypeError):
|
|---|
| 29 | raise TypeError(msg)
|
|---|
| 30 | else:
|
|---|
| 31 | exc.message = msg
|
|---|
| 32 | raise exc
|
|---|
| 33 |
|
|---|
| 34 | def check_string(value):
|
|---|
| 35 | """Function to check that a string parameter is already a string"""
|
|---|
| 36 | if param.type in string:
|
|---|
| 37 | if type(value) in (int, float):
|
|---|
| 38 | value = str(value)
|
|---|
| 39 | if type(value) not in string:
|
|---|
| 40 | msg = ("The Parameter <%s> require a string,"
|
|---|
| 41 | " %s instead is provided: %r")
|
|---|
| 42 | raise ValueError(msg % (param.name, type(value), value))
|
|---|
| 43 | return value
|
|---|
| 44 |
|
|---|
| 45 | # return None if None
|
|---|
| 46 | if value is None:
|
|---|
| 47 | return param.default, param.default
|
|---|
| 48 |
|
|---|
| 49 | # find errors with multiple parmeters
|
|---|
| 50 | if isinstance(value, (list, tuple)):
|
|---|
| 51 | if param.keydescvalues:
|
|---|
| 52 | return (([value, ], value) if isinstance(value, tuple)
|
|---|
| 53 | else (value, value))
|
|---|
| 54 | if param.multiple:
|
|---|
| 55 | # everything looks fine, so check each value
|
|---|
| 56 | try:
|
|---|
| 57 | return [param.type(check_string(val)) for val in value], value
|
|---|
| 58 | except Exception as exc:
|
|---|
| 59 | raiseexcpet(exc, param, param.type, value)
|
|---|
| 60 | else:
|
|---|
| 61 | msg = 'The Parameter <%s> does not accept multiple inputs'
|
|---|
| 62 | raise TypeError(msg % param.name)
|
|---|
| 63 |
|
|---|
| 64 | if param.keydescvalues:
|
|---|
| 65 | msg = 'The Parameter <%s> require multiple inputs in the form: %s'
|
|---|
| 66 | raise TypeError(msg % (param.name, param.keydescvalues))
|
|---|
| 67 |
|
|---|
| 68 | if param.typedesc == 'all':
|
|---|
| 69 | return value, value
|
|---|
| 70 |
|
|---|
| 71 | # check string before trying to convert value to the correct type
|
|---|
| 72 | check_string(value)
|
|---|
| 73 | # the value is a scalar
|
|---|
| 74 | try:
|
|---|
| 75 | newvalue = param.type(value)
|
|---|
| 76 | except Exception as exc:
|
|---|
| 77 | raiseexcpet(exc, param, type(value), value)
|
|---|
| 78 |
|
|---|
| 79 | # check values
|
|---|
| 80 | if hasattr(param, 'values'):
|
|---|
| 81 | if param.type in (float, int):
|
|---|
| 82 | # check for value in range
|
|---|
| 83 | if ((param.min is not None and newvalue < param.min) or
|
|---|
| 84 | (param.max is not None and newvalue > param.max)):
|
|---|
| 85 | err_str = ('The Parameter <%s>, must be between: '
|
|---|
| 86 | '%g<=value<=%g, %r is outside.')
|
|---|
| 87 | raise ValueError(err_str % (param.name, param.min,
|
|---|
| 88 | param.max, newvalue))
|
|---|
| 89 | # check if value is in the list of valid values
|
|---|
| 90 | if param.values is not None and newvalue not in param.values:
|
|---|
| 91 | raise ValueError(must_val % (param.name, param.values))
|
|---|
| 92 | return (([newvalue, ] if (param.multiple or param.keydescvalues)
|
|---|
| 93 | else newvalue), value)
|
|---|
| 94 |
|
|---|
| 95 |
|
|---|
| 96 | # TODO add documentation
|
|---|
| 97 | class Parameter(object):
|
|---|
| 98 | """The Parameter object store all information about a parameter of a
|
|---|
| 99 | GRASS GIS module. ::
|
|---|
| 100 |
|
|---|
| 101 | >>> param = Parameter(diz=dict(name='int_number', required='yes',
|
|---|
| 102 | ... multiple='no', type='integer',
|
|---|
| 103 | ... values=[2, 4, 6, 8]))
|
|---|
| 104 | >>> param.value = 2
|
|---|
| 105 | >>> param.value
|
|---|
| 106 | 2
|
|---|
| 107 | >>> param.value = 3
|
|---|
| 108 | Traceback (most recent call last):
|
|---|
| 109 | ...
|
|---|
| 110 | ValueError: The Parameter <int_number>, must be one of the following values: [2, 4, 6, 8]
|
|---|
| 111 |
|
|---|
| 112 | ...
|
|---|
| 113 | """
|
|---|
| 114 | def __init__(self, xparameter=None, diz=None):
|
|---|
| 115 | self._value = None
|
|---|
| 116 | self._rawvalue = None
|
|---|
| 117 | self.min = None
|
|---|
| 118 | self.max = None
|
|---|
| 119 | diz = element2dict(xparameter) if xparameter is not None else diz
|
|---|
| 120 | if diz is None:
|
|---|
| 121 | raise TypeError('Xparameter or diz are required')
|
|---|
| 122 | self.name = diz['name']
|
|---|
| 123 | self.required = True if diz['required'] == 'yes' else False
|
|---|
| 124 | self.multiple = True if diz['multiple'] == 'yes' else False
|
|---|
| 125 | # check the type
|
|---|
| 126 | if diz['type'] in GETTYPE:
|
|---|
| 127 | self.type = GETTYPE[diz['type']]
|
|---|
| 128 | self.typedesc = diz['type']
|
|---|
| 129 | else:
|
|---|
| 130 | raise TypeError('New type: %s, ignored' % diz['type'])
|
|---|
| 131 |
|
|---|
| 132 | self.description = diz.get('description', None)
|
|---|
| 133 | self.keydesc, self.keydescvalues = diz.get('keydesc', (None, None))
|
|---|
| 134 |
|
|---|
| 135 | #
|
|---|
| 136 | # values
|
|---|
| 137 | #
|
|---|
| 138 | if 'values' in diz:
|
|---|
| 139 | try:
|
|---|
| 140 | # Check for integer ranges: "3-30" or float ranges: "0.0-1.0"
|
|---|
| 141 | isrange = re.match("(?P<min>-*\d+.*\d*)*-(?P<max>\d+.*\d*)*",
|
|---|
| 142 | diz['values'][0])
|
|---|
| 143 | if isrange:
|
|---|
| 144 | mn, mx = isrange.groups()
|
|---|
| 145 | self.min = None if mn is None else float(mn)
|
|---|
| 146 | self.max = None if mx is None else float(mx)
|
|---|
| 147 | self.values = None
|
|---|
| 148 | self.isrange = diz['values'][0]
|
|---|
| 149 | # No range was found
|
|---|
| 150 | else:
|
|---|
| 151 | self.values = [self.type(i) for i in diz['values']]
|
|---|
| 152 | self.isrange = False
|
|---|
| 153 | except TypeError:
|
|---|
| 154 | self.values = [self.type(i) for i in diz['values']]
|
|---|
| 155 | self.isrange = False
|
|---|
| 156 |
|
|---|
| 157 | #
|
|---|
| 158 | # default
|
|---|
| 159 | #
|
|---|
| 160 | if 'default' in diz and diz['default']:
|
|---|
| 161 | if self.multiple or self.keydescvalues:
|
|---|
| 162 | self.default = [self.type(v)
|
|---|
| 163 | for v in diz['default'].split(',')]
|
|---|
| 164 | else:
|
|---|
| 165 | self.default = self.type(diz['default'])
|
|---|
| 166 | else:
|
|---|
| 167 | self.default = None
|
|---|
| 168 | self._value, self._rawvalue = self.default, self.default
|
|---|
| 169 | self.guisection = diz.get('guisection', None)
|
|---|
| 170 |
|
|---|
| 171 | #
|
|---|
| 172 | # gisprompt
|
|---|
| 173 | #
|
|---|
| 174 | if 'gisprompt' in diz and diz['gisprompt']:
|
|---|
| 175 | self.typedesc = diz['gisprompt'].get('prompt', '')
|
|---|
| 176 | self.input = False if diz['gisprompt']['age'] == 'new' else True
|
|---|
| 177 | else:
|
|---|
| 178 | self.input = True
|
|---|
| 179 |
|
|---|
| 180 | def _get_value(self):
|
|---|
| 181 | return self._value
|
|---|
| 182 |
|
|---|
| 183 | def _set_value(self, value):
|
|---|
| 184 | self._value, self._rawvalue = _check_value(self, value)
|
|---|
| 185 |
|
|---|
| 186 | # here the property function is used to transform value in an attribute
|
|---|
| 187 | # in this case we define which function must be use to get/set the value
|
|---|
| 188 | value = property(fget=_get_value, fset=_set_value,
|
|---|
| 189 | doc="Parameter value transformed and validated.")
|
|---|
| 190 |
|
|---|
| 191 | @property
|
|---|
| 192 | def rawvalue(self):
|
|---|
| 193 | """Parameter value as insert by user without transformation"""
|
|---|
| 194 | return self._rawvalue
|
|---|
| 195 |
|
|---|
| 196 | def get_bash(self):
|
|---|
| 197 | """Return the BASH representation of the parameter. ::
|
|---|
| 198 |
|
|---|
| 199 | >>> param = Parameter(diz=dict(name='int_number', required='yes',
|
|---|
| 200 | ... multiple='no', type='integer',
|
|---|
| 201 | ... values=[2, 4, 6, 8], default=8))
|
|---|
| 202 | >>> param.get_bash()
|
|---|
| 203 | u'int_number=8'
|
|---|
| 204 |
|
|---|
| 205 | ..
|
|---|
| 206 | """
|
|---|
| 207 | sep = ','
|
|---|
| 208 | if isinstance(self.rawvalue, (list, tuple)):
|
|---|
| 209 | value = sep.join([sep.join([str(v) for v in val])
|
|---|
| 210 | if isinstance(val, tuple) else str(val)
|
|---|
| 211 | for val in self.rawvalue])
|
|---|
| 212 | else:
|
|---|
| 213 | value = str(self.rawvalue)
|
|---|
| 214 | return "%s=%s" % (self.name, value)
|
|---|
| 215 |
|
|---|
| 216 | def get_python(self):
|
|---|
| 217 | """Return a string with the Python representation of the parameter. ::
|
|---|
| 218 |
|
|---|
| 219 | >>> param = Parameter(diz=dict(name='int_number', required='yes',
|
|---|
| 220 | ... multiple='no', type='integer',
|
|---|
| 221 | ... values=[2, 4, 6, 8], default=8))
|
|---|
| 222 | >>> param.get_python()
|
|---|
| 223 | u'int_number=8'
|
|---|
| 224 |
|
|---|
| 225 | ..
|
|---|
| 226 | """
|
|---|
| 227 | if self.value is None:
|
|---|
| 228 | return ''
|
|---|
| 229 | return """%s=%r""" % (self.name, self.value)
|
|---|
| 230 |
|
|---|
| 231 | def __str__(self):
|
|---|
| 232 | """Return the BASH representation of the GRASS module parameter."""
|
|---|
| 233 | return self.get_bash()
|
|---|
| 234 |
|
|---|
| 235 | def __repr__(self):
|
|---|
| 236 | """Return the python representation of the GRASS module parameter."""
|
|---|
| 237 | str_repr = "Parameter <%s> (required:%s, type:%s, multiple:%s)"
|
|---|
| 238 | mtype = ('raster', 'vector') # map type
|
|---|
| 239 | return str_repr % (self.name,
|
|---|
| 240 | "yes" if self.required else "no",
|
|---|
| 241 | self.type if self.type in mtype else self.typedesc,
|
|---|
| 242 | "yes" if self.multiple else "no")
|
|---|
| 243 |
|
|---|
| 244 | @docstring_property(__doc__)
|
|---|
| 245 | def __doc__(self):
|
|---|
| 246 | """Return the docstring of the parameter
|
|---|
| 247 |
|
|---|
| 248 | {name}: {default}{required}{multi}{ptype}
|
|---|
| 249 | {description}{values}"",
|
|---|
| 250 |
|
|---|
| 251 | ::
|
|---|
| 252 |
|
|---|
| 253 | >>> param = Parameter(diz=dict(name='int_number',
|
|---|
| 254 | ... description="Set an number",
|
|---|
| 255 | ... required='yes',
|
|---|
| 256 | ... multiple='no', type='integer',
|
|---|
| 257 | ... values=[2, 4, 6, 8], default=8))
|
|---|
| 258 | >>> print(param.__doc__)
|
|---|
| 259 | int_number: 8, required, integer
|
|---|
| 260 | Set an number
|
|---|
| 261 | Values: 2, 4, 6, 8
|
|---|
| 262 | ..
|
|---|
| 263 | """
|
|---|
| 264 | if hasattr(self, 'values'):
|
|---|
| 265 | if self.isrange:
|
|---|
| 266 | vals = self.isrange
|
|---|
| 267 | else:
|
|---|
| 268 | vals = ', '.join([repr(val) for val in self.values])
|
|---|
| 269 | else:
|
|---|
| 270 | vals = False
|
|---|
| 271 | if self.keydescvalues:
|
|---|
| 272 | keydescvals = "\n (%s)" % ', '.join(self.keydescvalues)
|
|---|
| 273 | return DOC['param'].format(name=self.name,
|
|---|
| 274 | default=repr(self.default) + ', ' if self.default else '',
|
|---|
| 275 | required='required, ' if self.required else 'optional, ',
|
|---|
| 276 | multi='multi' if self.multiple else '',
|
|---|
| 277 | ptype=self.typedesc, description=self.description,
|
|---|
| 278 | values='\n Values: {0}'.format(vals) if vals else '',
|
|---|
| 279 | keydescvalues= keydescvals if self.keydescvalues else '')
|
|---|