Merge pull request #60 from a-ovchinnikov/develop
More refactoring
diff --git a/reclass/__init__.py b/reclass/__init__.py
index 2167a30..d5f3410 100644
--- a/reclass/__init__.py
+++ b/reclass/__init__.py
@@ -19,7 +19,7 @@
storage_class = StorageBackendLoader(storage_type).load()
return MemcacheProxy(storage_class(nodes_uri, classes_uri, compose_node_name, **kwargs))
-def get_path_mangler(storage_type,**kwargs):
+def get_path_mangler(storage_type, **kwargs):
return StorageBackendLoader(storage_type).path_mangler()
def output(data, fmt, pretty_print=False, no_refs=False):
diff --git a/reclass/core.py b/reclass/core.py
index 6dac5c3..3e0ab34 100644
--- a/reclass/core.py
+++ b/reclass/core.py
@@ -23,7 +23,6 @@
from six import iteritems
from reclass.settings import Settings
-from reclass.output.yaml_outputter import ExplicitDumper
from reclass.datatypes import Entity, Classes, Parameters, Exports
from reclass.errors import MappingFormatError, ClassNameResolveError, ClassNotFound, InvQueryClassNameResolveError, InvQueryClassNotFound, InvQueryError, InterpolationError, ResolveError
from reclass.values.parser import Parser
@@ -39,7 +38,8 @@
self._settings = settings
self._input_data = input_data
if self._settings.ignore_class_notfound:
- self._cnf_r = re.compile('|'.join([x for x in self._settings.ignore_class_notfound_regexp]))
+ self._cnf_r = re.compile(
+ '|'.join(self._settings.ignore_class_notfound_regexp))
@staticmethod
def _get_timestamp():
@@ -114,7 +114,9 @@
for klass in entity.classes.as_list():
# class name contain reference
- if klass.count('$') > 0:
+ num_references = klass.count(self._settings.reference_sentinels[0]) +\
+ klass.count(self._settings.export_sentinels[0])
+ if num_references > 0:
try:
klass = str(self._parser.parse(klass, self._settings).render(merge_base.parameters.as_dict(), {}))
except ResolveError as e:
@@ -152,8 +154,16 @@
def _get_automatic_parameters(self, nodename, environment):
if self._settings.automatic_parameters:
- return Parameters({ '_reclass_': { 'name': { 'full': nodename, 'short': nodename.split('.')[0] },
- 'environment': environment } }, self._settings, '__auto__')
+ pars = {
+ '_reclass_': {
+ 'name': {
+ 'full': nodename,
+ 'short': nodename.split('.')[0]
+ },
+ 'environment': environment
+ }
+ }
+ return Parameters(pars, self._settings, '__auto__')
else:
return Parameters({}, self._settings, '')
@@ -162,13 +172,12 @@
for nodename in self._storage.enumerate_nodes():
try:
node_base = self._storage.get_node(nodename, self._settings)
- if node_base.environment == None:
+ if node_base.environment is None:
node_base.environment = self._settings.default_environment
except yaml.scanner.ScannerError as e:
if self._settings.inventory_ignore_failed_node:
continue
- else:
- raise
+ raise
if all_envs or node_base.environment == environment:
try:
@@ -220,7 +229,8 @@
raise
def _nodeinfo_as_dict(self, nodename, entity):
- ret = {'__reclass__' : {'node': entity.name, 'name': nodename,
+ ret = {'__reclass__' : {'node': entity.name,
+ 'name': nodename,
'uri': entity.uri,
'environment': entity.environment,
'timestamp': Core._get_timestamp()
diff --git a/reclass/datatypes/applications.py b/reclass/datatypes/applications.py
index 90ae54c..4f6ee10 100644
--- a/reclass/datatypes/applications.py
+++ b/reclass/datatypes/applications.py
@@ -28,18 +28,14 @@
def __init__(self, iterable=None,
negation_prefix=DEFAULT_NEGATION_PREFIX):
- self._negation_prefix = negation_prefix
+ self.negation_prefix = negation_prefix
self._offset = len(negation_prefix)
self._negations = []
super(Applications, self).__init__(iterable)
- @property
- def negation_prefix(self):
- return self._negation_prefix
-
def append_if_new(self, item):
self._assert_is_string(item)
- if item.startswith(self._negation_prefix):
+ if item.startswith(self.negation_prefix):
item = item[self._offset:]
self._negations.append(item)
try:
@@ -64,6 +60,6 @@
def __repr__(self):
contents = self._items + \
- ['%s%s' % (self._negation_prefix, i) for i in self._negations]
+ ['%s%s' % (self.negation_prefix, i) for i in self._negations]
return "%s(%r, %r)" % (self.__class__.__name__, contents,
- str(self._negation_prefix))
+ str(self.negation_prefix))
diff --git a/reclass/datatypes/classes.py b/reclass/datatypes/classes.py
index 5270e28..fa9cbcf 100644
--- a/reclass/datatypes/classes.py
+++ b/reclass/datatypes/classes.py
@@ -57,7 +57,7 @@
def _assert_is_string(self, item):
if not isinstance(item, six.string_types):
- raise TypeError('%s instances can only contain strings, '\
+ raise TypeError('%s instances can only contain strings, '
'not %s' % (self.__class__.__name__, type(item)))
def _assert_valid_characters(self, item):
diff --git a/reclass/datatypes/entity.py b/reclass/datatypes/entity.py
index 8133de5..2e0e1e4 100644
--- a/reclass/datatypes/entity.py
+++ b/reclass/datatypes/entity.py
@@ -35,7 +35,6 @@
self._environment = environment
name = property(lambda s: s._name)
- short_name = property(lambda s: s._short_name)
uri = property(lambda s: s._uri)
classes = property(lambda s: s._classes)
applications = property(lambda s: s._applications)
@@ -61,10 +60,10 @@
return received_value
def merge(self, other):
- self._classes.merge_unique(other._classes)
- self._applications.merge_unique(other._applications)
- self._parameters.merge(other._parameters)
- self._exports.merge(other._exports)
+ self._classes.merge_unique(other.classes)
+ self._applications.merge_unique(other.applications)
+ self._parameters.merge(other.parameters)
+ self._exports.merge(other.exports)
self._name = other.name
self._uri = other.uri
self._parameters._uri = other.uri
@@ -91,12 +90,12 @@
def __eq__(self, other):
return isinstance(other, type(self)) \
- and self._applications == other._applications \
- and self._classes == other._classes \
- and self._parameters == other._parameters \
- and self._exports == other._exports \
- and self._name == other._name \
- and self._uri == other._uri
+ and self._applications == other.applications \
+ and self._classes == other.classes \
+ and self._parameters == other.parameters \
+ and self._exports == other.exports \
+ and self._name == other.name \
+ and self._uri == other.uri
def __ne__(self, other):
return not self.__eq__(other)
diff --git a/reclass/datatypes/exports.py b/reclass/datatypes/exports.py
index 04ab200..984a15a 100644
--- a/reclass/datatypes/exports.py
+++ b/reclass/datatypes/exports.py
@@ -23,9 +23,6 @@
def __init__(self, mapping, settings, uri):
super(Exports, self).__init__(mapping, settings, uri)
- def __repr__(self):
- return '%s(%r)' % (self.__class__.__name__, self._base)
-
def delete_key(self, key):
self._base.pop(key, None)
self._unrendered.pop(key, None)
diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py
index ee404ce..bab2a28 100644
--- a/reclass/datatypes/parameters.py
+++ b/reclass/datatypes/parameters.py
@@ -105,24 +105,23 @@
e.context = DictPath(self._settings.delimiter)
raise
+ def _get_wrapped(self, position, value):
+ try:
+ return self._wrap_value(value)
+ except InterpolationError as e:
+ e.context.add_ancestor(str(position))
+ raise
+
def _wrap_list(self, source):
l = ParameterList(uri=self._uri)
for (k, v) in enumerate(source):
- try:
- l.append(self._wrap_value(v))
- except InterpolationError as e:
- e.context.add_ancestor(str(k))
- raise
+ l.append(self._get_wrapped(k, v))
return l
def _wrap_dict(self, source):
d = ParameterDict(uri=self._uri)
for (k, v) in iteritems(source):
- try:
- d[k] = self._wrap_value(v)
- except InterpolationError as e:
- e.context.add_ancestor(str(k))
- raise
+ d[k] = self._get_wrapped(k, v)
return d
def _update_value(self, cur, new):
diff --git a/reclass/settings.py b/reclass/settings.py
index 3e223cc..b7f5252 100644
--- a/reclass/settings.py
+++ b/reclass/settings.py
@@ -5,69 +5,61 @@
from __future__ import print_function
from __future__ import unicode_literals
-import copy
-import reclass.values.parser_funcs
-from reclass.defaults import *
+import reclass.defaults as defaults
-from six import string_types
+from six import string_types, iteritems
+
class Settings(object):
+ known_opts = {
+ 'allow_scalar_over_dict': defaults.OPT_ALLOW_SCALAR_OVER_DICT,
+ 'allow_scalar_over_list': defaults.OPT_ALLOW_SCALAR_OVER_LIST,
+ 'allow_list_over_scalar': defaults.OPT_ALLOW_LIST_OVER_SCALAR,
+ 'allow_dict_over_scalar': defaults.OPT_ALLOW_DICT_OVER_SCALAR,
+ 'allow_none_override': defaults.OPT_ALLOW_NONE_OVERRIDE,
+ 'automatic_parameters': defaults.AUTOMATIC_RECLASS_PARAMETERS,
+ 'default_environment': defaults.DEFAULT_ENVIRONMENT,
+ 'delimiter': defaults.PARAMETER_INTERPOLATION_DELIMITER,
+ 'dict_key_override_prefix':
+ defaults.PARAMETER_DICT_KEY_OVERRIDE_PREFIX,
+ 'dict_key_constant_prefix':
+ defaults.PARAMETER_DICT_KEY_CONSTANT_PREFIX,
+ 'escape_character': defaults.ESCAPE_CHARACTER,
+ 'export_sentinels': defaults.EXPORT_SENTINELS,
+ 'inventory_ignore_failed_node':
+ defaults.OPT_INVENTORY_IGNORE_FAILED_NODE,
+ 'inventory_ignore_failed_render':
+ defaults.OPT_INVENTORY_IGNORE_FAILED_RENDER,
+ 'reference_sentinels': defaults.REFERENCE_SENTINELS,
+ 'ignore_class_notfound': defaults.OPT_IGNORE_CLASS_NOTFOUND,
+ 'strict_constant_parameters':
+ defaults.OPT_STRICT_CONSTANT_PARAMETERS,
+ 'ignore_class_notfound_regexp':
+ defaults.OPT_IGNORE_CLASS_NOTFOUND_REGEXP,
+ 'ignore_class_notfound_warning':
+ defaults.OPT_IGNORE_CLASS_NOTFOUND_WARNING,
+ 'ignore_overwritten_missing_referencesdefaults.':
+ defaults.OPT_IGNORE_OVERWRITTEN_MISSING_REFERENCES,
+ 'group_errors': defaults.OPT_GROUP_ERRORS,
+ 'compose_node_name': defaults.OPT_COMPOSE_NODE_NAME,
+ }
+
def __init__(self, options={}):
- self.allow_scalar_over_dict = options.get('allow_scalar_over_dict', OPT_ALLOW_SCALAR_OVER_DICT)
- self.allow_scalar_over_list = options.get('allow_scalar_over_list', OPT_ALLOW_SCALAR_OVER_LIST)
- self.allow_list_over_scalar = options.get('allow_list_over_scalar', OPT_ALLOW_LIST_OVER_SCALAR)
- self.allow_dict_over_scalar = options.get('allow_dict_over_scalar', OPT_ALLOW_DICT_OVER_SCALAR)
- self.allow_none_override = options.get('allow_none_override', OPT_ALLOW_NONE_OVERRIDE)
- self.automatic_parameters = options.get('automatic_parameters', AUTOMATIC_RECLASS_PARAMETERS)
- self.default_environment = options.get('default_environment', DEFAULT_ENVIRONMENT)
- self.delimiter = options.get('delimiter', PARAMETER_INTERPOLATION_DELIMITER)
- self.dict_key_override_prefix = options.get('dict_key_override_prefix', PARAMETER_DICT_KEY_OVERRIDE_PREFIX)
- self.dict_key_constant_prefix = options.get('dict_key_constant_prefix', PARAMETER_DICT_KEY_CONSTANT_PREFIX)
- self.dict_key_prefixes = [ str(self.dict_key_override_prefix), str(self.dict_key_constant_prefix) ]
- self.escape_character = options.get('escape_character', ESCAPE_CHARACTER)
- self.export_sentinels = options.get('export_sentinels', EXPORT_SENTINELS)
- self.inventory_ignore_failed_node = options.get('inventory_ignore_failed_node', OPT_INVENTORY_IGNORE_FAILED_NODE)
- self.inventory_ignore_failed_render = options.get('inventory_ignore_failed_render', OPT_INVENTORY_IGNORE_FAILED_RENDER)
- self.reference_sentinels = options.get('reference_sentinels', REFERENCE_SENTINELS)
- self.ignore_class_notfound = options.get('ignore_class_notfound', OPT_IGNORE_CLASS_NOTFOUND)
- self.strict_constant_parameters = options.get('strict_constant_parameters', OPT_STRICT_CONSTANT_PARAMETERS)
- self.compose_node_name = options.get('compose_node_name', OPT_COMPOSE_NODE_NAME)
+ for opt_name, opt_value in iteritems(self.known_opts):
+ setattr(self, opt_name, options.get(opt_name, opt_value))
- self.ignore_class_notfound_regexp = options.get('ignore_class_notfound_regexp', OPT_IGNORE_CLASS_NOTFOUND_REGEXP)
+ self.dict_key_prefixes = [str(self.dict_key_override_prefix),
+ str(self.dict_key_constant_prefix)]
if isinstance(self.ignore_class_notfound_regexp, string_types):
- self.ignore_class_notfound_regexp = [ self.ignore_class_notfound_regexp ]
-
- self.ignore_class_notfound_warning = options.get('ignore_class_notfound_warning', OPT_IGNORE_CLASS_NOTFOUND_WARNING)
- self.ignore_overwritten_missing_references = options.get('ignore_overwritten_missing_references', OPT_IGNORE_OVERWRITTEN_MISSING_REFERENCES)
-
- self.group_errors = options.get('group_errors', OPT_GROUP_ERRORS)
-
- self.ref_parser = reclass.values.parser_funcs.get_ref_parser(self.escape_character, self.reference_sentinels, self.export_sentinels)
- self.simple_ref_parser = reclass.values.parser_funcs.get_simple_ref_parser(self.escape_character, self.reference_sentinels, self.export_sentinels)
+ self.ignore_class_notfound_regexp = [
+ self.ignore_class_notfound_regexp]
def __eq__(self, other):
- return isinstance(other, type(self)) \
- and self.allow_scalar_over_dict == other.allow_scalar_over_dict \
- and self.allow_scalar_over_list == other.allow_scalar_over_list \
- and self.allow_list_over_scalar == other.allow_list_over_scalar \
- and self.allow_dict_over_scalar == other.allow_dict_over_scalar \
- and self.allow_none_override == other.allow_none_override \
- and self.automatic_parameters == other.automatic_parameters \
- and self.default_environment == other.default_environment \
- and self.delimiter == other.delimiter \
- and self.dict_key_override_prefix == other.dict_key_override_prefix \
- and self.dict_key_constant_prefix == other.dict_key_constant_prefix \
- and self.escape_character == other.escape_character \
- and self.export_sentinels == other.export_sentinels \
- and self.inventory_ignore_failed_node == other.inventory_ignore_failed_node \
- and self.inventory_ignore_failed_render == other.inventory_ignore_failed_render \
- and self.reference_sentinels == other.reference_sentinels \
- and self.ignore_class_notfound == other.ignore_class_notfound \
- and self.ignore_class_notfound_regexp == other.ignore_class_notfound_regexp \
- and self.ignore_class_notfound_warning == other.ignore_class_notfound_warning \
- and self.strict_constant_parameters == other.strict_constant_parameters \
- and self.compose_node_name == other.compose_node_name
+ if isinstance(other, type(self)):
+ return all(getattr(self, opt) == getattr(other, opt)
+ for opt in self.known_opts)
+ return False
def __copy__(self):
cls = self.__class__
diff --git a/reclass/storage/loader.py b/reclass/storage/loader.py
index aab554a..0a66a66 100644
--- a/reclass/storage/loader.py
+++ b/reclass/storage/loader.py
@@ -32,5 +32,6 @@
def path_mangler(self, name='path_mangler'):
function = getattr(self._module, name, None)
if function is None:
- raise AttributeError('Storage backend class {0} does not export "{1}"'.format(self._name, name))
+ raise AttributeError('Storage backend class {0} does not export '
+ '"{1}"'.format(self._name, name))
return function
diff --git a/reclass/values/invitem.py b/reclass/values/invitem.py
index 5461612..adb1cb6 100644
--- a/reclass/values/invitem.py
+++ b/reclass/values/invitem.py
@@ -135,6 +135,7 @@
def __init__(self, newitem, settings):
super(InvItem, self).__init__(newitem.render(None, None), settings)
self.needs_all_envs = False
+ self.has_inv_query = True
self.ignore_failed_render = (
self._settings.inventory_ignore_failed_render)
self._parse_expression(self.contents)
@@ -179,10 +180,6 @@
raise ExpressionError(msg % self._expr_type, tbFlag=False)
@property
- def has_inv_query(self):
- return True
-
- @property
def has_references(self):
return len(self._question.refs) > 0
diff --git a/reclass/values/item.py b/reclass/values/item.py
index ee46995..45aeb77 100644
--- a/reclass/values/item.py
+++ b/reclass/values/item.py
@@ -22,6 +22,7 @@
def __init__(self, item, settings):
self._settings = settings
self.contents = item
+ self.has_inv_query = False
def allRefs(self):
return True
@@ -30,10 +31,6 @@
def has_references(self):
return False
- @property
- def has_inv_query(self):
- return False
-
def is_container(self):
return False
@@ -60,6 +57,10 @@
def __init__(self, items, settings):
super(ItemWithReferences, self).__init__(items, settings)
+ try:
+ iter(self.contents)
+ except TypeError:
+ self.contents = [self.contents]
self.assembleRefs()
@property
@@ -81,6 +82,7 @@
if item.allRefs is False:
self.allRefs = False
+
class ContainerItem(Item):
def is_container(self):
diff --git a/reclass/values/parser.py b/reclass/values/parser.py
index 914e825..27e6d2d 100644
--- a/reclass/values/parser.py
+++ b/reclass/values/parser.py
@@ -17,37 +17,54 @@
from reclass.errors import ParseError
from reclass.values.parser_funcs import STR, REF, INV
+import reclass.values.parser_funcs as parsers
class Parser(object):
+ def __init__(self):
+ self._ref_parser = None
+ self._simple_parser = None
+ self._old_settings = None
+
+ @property
+ def ref_parser(self):
+ if self._ref_parser is None or self._settings != self._old_settings:
+ self._ref_parser = parsers.get_ref_parser(self._settings)
+ self._old_settings = self._settings
+ return self._ref_parser
+
+ @property
+ def simple_ref_parser(self):
+ if self._simple_parser is None or self._settings != self._old_settings:
+ self._simple_parser = parsers.get_simple_ref_parser(self._settings)
+ self._old_settings = self._settings
+ return self._simple_parser
+
def parse(self, value, settings):
- self._settings = settings
- dollars = value.count('$')
- if dollars == 0:
- # speed up: only use pyparsing if there is a $ in the string
- return ScaItem(value, self._settings)
- elif dollars == 1:
- # speed up: try a simple reference
+ def full_parse():
try:
- tokens = self._settings.simple_ref_parser.leaveWhitespace().parseString(value).asList()
- except pp.ParseException:
- # fall back on the full parser
- try:
- tokens = self._settings.ref_parser.leaveWhitespace().parseString(value).asList()
- except pp.ParseException as e:
- raise ParseError(e.msg, e.line, e.col, e.lineno)
- else:
- # use the full parser
- try:
- tokens = self._settings.ref_parser.leaveWhitespace().parseString(value).asList()
+ return self.ref_parser.parseString(value).asList()
except pp.ParseException as e:
raise ParseError(e.msg, e.line, e.col, e.lineno)
+ self._settings = settings
+ sentinel_count = (value.count(settings.reference_sentinels[0]) +
+ value.count(settings.export_sentinels[0]))
+ if sentinel_count == 0:
+ # speed up: only use pyparsing if there are sentinels in the value
+ return ScaItem(value, self._settings)
+ elif sentinel_count == 1: # speed up: try a simple reference
+ try:
+ tokens = self.simple_ref_parser.parseString(value).asList()
+ except pp.ParseException:
+ tokens = full_parse() # fall back on the full parser
+ else:
+ tokens = full_parse() # use the full parser
+
items = self._create_items(tokens)
if len(items) == 1:
return items[0]
- else:
- return CompItem(items, self._settings)
+ return CompItem(items, self._settings)
_create_dict = { STR: (lambda s, v: ScaItem(v, s._settings)),
REF: (lambda s, v: s._create_ref(v)),
@@ -64,5 +81,4 @@
items = [ ScaItem(v, self._settings) for t, v in tokens ]
if len(items) == 1:
return InvItem(items[0], self._settings)
- else:
- return InvItem(CompItem(items), self._settings)
+ return InvItem(CompItem(items), self._settings)
diff --git a/reclass/values/parser_funcs.py b/reclass/values/parser_funcs.py
index 50babd0..f702910 100644
--- a/reclass/values/parser_funcs.py
+++ b/reclass/values/parser_funcs.py
@@ -34,6 +34,8 @@
ALL_ENVS = '+AllEnvs'
+s_end = pp.StringEnd()
+
def _tag_with(tag, transform=lambda x:x):
def inner(tag, string, location, tokens):
token = transform(tokens[0])
@@ -41,8 +43,6 @@
return functools.partial(inner, tag)
def get_expression_parser():
-
- s_end = pp.StringEnd()
sign = pp.Optional(pp.Literal('-'))
number = pp.Word(pp.nums)
dpoint = pp.Literal('.')
@@ -80,12 +80,11 @@
line = options + expr + s_end
return line
-def get_ref_parser(escape_character, reference_sentinels, export_sentinels):
- _ESCAPE = escape_character
+def get_ref_parser(settings):
+ _ESCAPE = settings.escape_character
_DOUBLE_ESCAPE = _ESCAPE + _ESCAPE
- _REF_OPEN = reference_sentinels[0]
- _REF_CLOSE = reference_sentinels[1]
+ _REF_OPEN, _REF_CLOSE = settings.reference_sentinels
_REF_CLOSE_FIRST = _REF_CLOSE[0]
_REF_ESCAPE_OPEN = _ESCAPE + _REF_OPEN
_REF_ESCAPE_CLOSE = _ESCAPE + _REF_CLOSE
@@ -93,8 +92,7 @@
_REF_DOUBLE_ESCAPE_CLOSE = _DOUBLE_ESCAPE + _REF_CLOSE
_REF_EXCLUDES = _ESCAPE + _REF_OPEN + _REF_CLOSE
- _INV_OPEN = export_sentinels[0]
- _INV_CLOSE = export_sentinels[1]
+ _INV_OPEN, _INV_CLOSE = settings.export_sentinels
_INV_CLOSE_FIRST = _INV_CLOSE[0]
_INV_ESCAPE_OPEN = _ESCAPE + _INV_OPEN
_INV_ESCAPE_CLOSE = _ESCAPE + _INV_CLOSE
@@ -142,20 +140,20 @@
string = pp.MatchFirst([double_escape, ref_escape_open, inv_escape_open, content]).setParseAction(_tag_with(STR))
item = reference | export | string
- line = pp.OneOrMore(item) + pp.StringEnd()
- return line
+ line = pp.OneOrMore(item) + s_end
+ return line.leaveWhitespace()
-def get_simple_ref_parser(escape_character, reference_sentinels, export_sentinels):
- _ESCAPE = escape_character
- _REF_OPEN = reference_sentinels[0]
- _REF_CLOSE = reference_sentinels[1]
- _INV_OPEN = export_sentinels[0]
- _INV_CLOSE = export_sentinels[1]
- _EXCLUDES = _ESCAPE + _REF_OPEN + _REF_CLOSE + _INV_OPEN + _INV_CLOSE
- string = pp.CharsNotIn(_EXCLUDES).setParseAction(_tag_with(STR))
- ref_open = pp.Literal(_REF_OPEN).suppress()
- ref_close = pp.Literal(_REF_CLOSE).suppress()
+def get_simple_ref_parser(settings):
+
+ ESCAPE = settings.escape_character
+ REF_OPEN, REF_CLOSE = settings.reference_sentinels
+ INV_OPEN, INV_CLOSE = settings.export_sentinels
+ EXCLUDES = ESCAPE + REF_OPEN + REF_CLOSE + INV_OPEN + INV_CLOSE
+
+ string = pp.CharsNotIn(EXCLUDES).setParseAction(_tag_with(STR))
+ ref_open = pp.Literal(REF_OPEN).suppress()
+ ref_close = pp.Literal(REF_CLOSE).suppress()
reference = (ref_open + pp.Group(string) + ref_close).setParseAction(_tag_with(REF))
- line = pp.StringStart() + pp.Optional(string) + reference + pp.Optional(string) + pp.StringEnd()
- return line
+ line = pp.StringStart() + pp.Optional(string) + reference + pp.Optional(string) + s_end
+ return line.leaveWhitespace()
diff --git a/reclass/values/refitem.py b/reclass/values/refitem.py
index df713e1..64bf450 100644
--- a/reclass/values/refitem.py
+++ b/reclass/values/refitem.py
@@ -16,12 +16,14 @@
def assembleRefs(self, context={}):
super(RefItem, self).assembleRefs(context)
try:
- strings = [str(i.render(context, None)) for i in self.contents]
- value = "".join(strings)
- self._refs.append(value)
+ self._refs.append(self._flatten_contents(context))
except ResolveError as e:
self.allRefs = False
+ def _flatten_contents(self, context, inventory=None):
+ result = [str(i.render(context, inventory)) for i in self.contents]
+ return "".join(result)
+
def _resolve(self, ref, context):
path = DictPath(self._settings.delimiter, ref)
try:
@@ -30,11 +32,10 @@
raise ResolveError(ref)
def render(self, context, inventory):
- if len(self.contents) == 1:
- return self._resolve(self.contents[0].render(context, inventory),
- context)
- strings = [str(i.render(context, inventory)) for i in self.contents]
- return self._resolve("".join(strings), context)
+ #strings = [str(i.render(context, inventory)) for i in self.contents]
+ #return self._resolve("".join(strings), context)
+ return self._resolve(self._flatten_contents(context, inventory),
+ context)
def __str__(self):
strings = [str(i) for i in self.contents]
diff --git a/reclass/values/tests/test_compitem.py b/reclass/values/tests/test_compitem.py
index 71a6f0e..c3ee690 100644
--- a/reclass/values/tests/test_compitem.py
+++ b/reclass/values/tests/test_compitem.py
@@ -71,6 +71,14 @@
self.assertTrue(composite.has_references)
self.assertEquals(composite.get_references(), expected_refs)
+ def test_string_representation(self):
+ composite = CompItem(Value(1, SETTINGS, ''), SETTINGS)
+ expected = '1'
+
+ result = str(composite)
+
+ self.assertEquals(result, expected)
+
def test_render_single_item(self):
val1 = Value('${foo}', SETTINGS, '')
@@ -106,20 +114,6 @@
self.assertEquals(result, composite2)
- def test_merge_over_merge_list_not_allowed(self):
- val1 = Value(None, SETTINGS, '')
- listitem = ListItem([1], SETTINGS)
- composite = CompItem([val1], SETTINGS)
-
- self.assertRaises(RuntimeError, composite.merge_over, listitem)
-
- def test_merge_dict_dict_not_allowed(self):
- val1 = Value(None, SETTINGS, '')
- dictitem = DictItem({'foo': 'bar'}, SETTINGS)
- composite = CompItem([val1], SETTINGS)
-
- self.assertRaises(RuntimeError, composite.merge_over, dictitem)
-
def test_merge_other_types_not_allowed(self):
other = type('Other', (object,), {'type': 34})
val1 = Value(None, SETTINGS, '')
diff --git a/reclass/values/tests/test_item.py b/reclass/values/tests/test_item.py
new file mode 100644
index 0000000..4b91f6e
--- /dev/null
+++ b/reclass/values/tests/test_item.py
@@ -0,0 +1,48 @@
+from reclass.settings import Settings
+from reclass.values.value import Value
+from reclass.values.compitem import CompItem
+from reclass.values.scaitem import ScaItem
+from reclass.values.valuelist import ValueList
+from reclass.values.listitem import ListItem
+from reclass.values.dictitem import DictItem
+from reclass.values.item import ContainerItem
+from reclass.values.item import ItemWithReferences
+import unittest
+from mock import MagicMock
+
+SETTINGS = Settings()
+
+
+class TestItemWithReferences(unittest.TestCase):
+
+ def test_assembleRef_allrefs(self):
+ phonyitem = MagicMock()
+ phonyitem.has_references = True
+ phonyitem.get_references = lambda *x: [1]
+
+ iwr = ItemWithReferences([phonyitem], {})
+
+ self.assertEquals(iwr.get_references(), [1])
+ self.assertTrue(iwr.allRefs)
+
+ def test_assembleRef_partial(self):
+ phonyitem = MagicMock()
+ phonyitem.has_references = True
+ phonyitem.allRefs = False
+ phonyitem.get_references = lambda *x: [1]
+
+ iwr = ItemWithReferences([phonyitem], {})
+
+ self.assertEquals(iwr.get_references(), [1])
+ self.assertFalse(iwr.allRefs)
+
+
+class TestContainerItem(unittest.TestCase):
+
+ def test_render(self):
+ container = ContainerItem('foo', SETTINGS)
+
+ self.assertEquals(container.render(None, None), 'foo')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/reclass/values/tests/test_listitem.py b/reclass/values/tests/test_listitem.py
new file mode 100644
index 0000000..618b779
--- /dev/null
+++ b/reclass/values/tests/test_listitem.py
@@ -0,0 +1,31 @@
+from reclass.settings import Settings
+from reclass.values.value import Value
+from reclass.values.compitem import CompItem
+from reclass.values.scaitem import ScaItem
+from reclass.values.valuelist import ValueList
+from reclass.values.listitem import ListItem
+from reclass.values.dictitem import DictItem
+import unittest
+
+SETTINGS = Settings()
+
+class TestListItem(unittest.TestCase):
+
+ def test_merge_over_merge_list(self):
+ listitem1 = ListItem([1], SETTINGS)
+ listitem2 = ListItem([2], SETTINGS)
+ expected = ListItem([1, 2], SETTINGS)
+
+ result = listitem2.merge_over(listitem1)
+
+ self.assertEquals(result.contents, expected.contents)
+
+ def test_merge_other_types_not_allowed(self):
+ other = type('Other', (object,), {'type': 34})
+ val1 = Value(None, SETTINGS, '')
+ listitem = ListItem(val1, SETTINGS)
+
+ self.assertRaises(RuntimeError, listitem.merge_over, other)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/reclass/values/tests/test_refitem.py b/reclass/values/tests/test_refitem.py
new file mode 100644
index 0000000..6581478
--- /dev/null
+++ b/reclass/values/tests/test_refitem.py
@@ -0,0 +1,57 @@
+from reclass import errors
+
+from reclass.settings import Settings
+from reclass.values.value import Value
+from reclass.values.compitem import CompItem
+from reclass.values.scaitem import ScaItem
+from reclass.values.valuelist import ValueList
+from reclass.values.listitem import ListItem
+from reclass.values.dictitem import DictItem
+from reclass.values.refitem import RefItem
+import unittest
+from mock import MagicMock
+
+SETTINGS = Settings()
+
+class TestRefItem(unittest.TestCase):
+
+ def test_assembleRefs_ok(self):
+ phonyitem = MagicMock()
+ phonyitem.render = lambda x, k: 'bar'
+ phonyitem.has_references = True
+ phonyitem.get_references = lambda *x: ['foo']
+
+ iwr = RefItem([phonyitem], {})
+
+ self.assertEquals(iwr.get_references(), ['foo', 'bar'])
+ self.assertTrue(iwr.allRefs)
+
+ def test_assembleRefs_failedrefs(self):
+ phonyitem = MagicMock()
+ phonyitem.render.side_effect = errors.ResolveError('foo')
+ phonyitem.has_references = True
+ phonyitem.get_references = lambda *x: ['foo']
+
+ iwr = RefItem([phonyitem], {})
+
+ self.assertEquals(iwr.get_references(), ['foo'])
+ self.assertFalse(iwr.allRefs)
+
+ def test__resolve_ok(self):
+ reference = RefItem('', Settings({'delimiter': ':'}))
+
+ result = reference._resolve('foo:bar', {'foo':{'bar': 1}})
+
+ self.assertEquals(result, 1)
+
+ def test__resolve_fails(self):
+ refitem = RefItem('', Settings({'delimiter': ':'}))
+ context = {'foo':{'bar': 1}}
+ reference = 'foo:baz'
+
+ self.assertRaises(errors.ResolveError, refitem._resolve, reference,
+ context)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/reclass/values/tests/test_scaitem.py b/reclass/values/tests/test_scaitem.py
new file mode 100644
index 0000000..b6d038d
--- /dev/null
+++ b/reclass/values/tests/test_scaitem.py
@@ -0,0 +1,38 @@
+from reclass.settings import Settings
+from reclass.values.value import Value
+from reclass.values.compitem import CompItem
+from reclass.values.scaitem import ScaItem
+from reclass.values.valuelist import ValueList
+from reclass.values.listitem import ListItem
+from reclass.values.dictitem import DictItem
+import unittest
+
+SETTINGS = Settings()
+
+class TestScaItem(unittest.TestCase):
+
+ def test_merge_over_merge_scalar(self):
+ scalar1 = ScaItem([1], SETTINGS)
+ scalar2 = ScaItem([2], SETTINGS)
+
+ result = scalar2.merge_over(scalar1)
+
+ self.assertEquals(result.contents, scalar2.contents)
+
+ def test_merge_over_merge_composite(self):
+ scalar1 = CompItem(Value(1, SETTINGS, ''), SETTINGS)
+ scalar2 = ScaItem([2], SETTINGS)
+
+ result = scalar2.merge_over(scalar1)
+
+ self.assertEquals(result.contents, scalar2.contents)
+
+ def test_merge_other_types_not_allowed(self):
+ other = type('Other', (object,), {'type': 34})
+ val1 = Value(None, SETTINGS, '')
+ scalar = ScaItem(val1, SETTINGS)
+
+ self.assertRaises(RuntimeError, scalar.merge_over, other)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/reclass/values/value.py b/reclass/values/value.py
index affd944..451617e 100644
--- a/reclass/values/value.py
+++ b/reclass/values/value.py
@@ -22,15 +22,15 @@
def __init__(self, value, settings, uri, parse_string=True):
self._settings = settings
- self._uri = uri
+ self.uri = uri
self.overwrite = False
- self._constant = False
+ self.constant = False
if isinstance(value, string_types):
if parse_string:
try:
self._item = self._parser.parse(value, self._settings)
except InterpolationError as e:
- e.uri = self._uri
+ e.uri = self.uri
raise
else:
self._item = ScaItem(value, self._settings)
@@ -41,18 +41,6 @@
else:
self._item = ScaItem(value, self._settings)
- @property
- def uri(self):
- return self._uri
-
- @property
- def constant(self):
- return self._constant
-
- @constant.setter
- def constant(self, constant):
- self._constant = constant
-
def item_type(self):
return self._item.type
@@ -78,8 +66,7 @@
def needs_all_envs(self):
if self._item.has_inv_query:
return self._item.needs_all_envs
- else:
- return False
+ return False
def ignore_failed_render(self):
return self._item.ignore_failed_render
@@ -102,7 +89,7 @@
try:
return self._item.render(context, inventory)
except InterpolationError as e:
- e.uri = self._uri
+ e.uri = self.uri
raise
@property
diff --git a/reclass/values/valuelist.py b/reclass/values/valuelist.py
index 86563fa..e8c3a0c 100644
--- a/reclass/values/valuelist.py
+++ b/reclass/values/valuelist.py
@@ -22,9 +22,9 @@
self.allRefs = True
self._values = [value]
self._inv_refs = []
- self._has_inv_query = False
+ self.has_inv_query = False
self.ignore_failed_render = False
- self._is_complex = False
+ self.is_complex = False
self._update()
@property
@@ -42,40 +42,32 @@
def _update(self):
self.assembleRefs()
self._check_for_inv_query()
- self._is_complex = False
+ self.is_complex = False
item_type = self._values[0].item_type()
for v in self._values:
if v.is_complex or v.constant or v.overwrite or v.item_type() != item_type:
- self._is_complex = True
+ self.is_complex = True
@property
def has_references(self):
return len(self._refs) > 0
- @property
- def has_inv_query(self):
- return self._has_inv_query
-
def get_inv_references(self):
return self._inv_refs
- @property
- def is_complex(self):
- return self._is_complex
-
def get_references(self):
return self._refs
def _check_for_inv_query(self):
- self._has_inv_query = False
+ self.has_inv_query = False
self.ignore_failed_render = True
for value in self._values:
if value.has_inv_query:
self._inv_refs.extend(value.get_inv_references())
- self._has_inv_query = True
+ self.has_inv_query = True
if value.ignore_failed_render() is False:
self.ignore_failed_render = False
- if self._has_inv_query is False:
+ if self.has_inv_query is False:
self.ignore_failed_render = False
def assembleRefs(self, context={}):
diff --git a/reclass/version.py b/reclass/version.py
index 6d7d7eb..63fda26 100644
--- a/reclass/version.py
+++ b/reclass/version.py
@@ -12,12 +12,14 @@
from __future__ import unicode_literals
RECLASS_NAME = 'reclass'
-DESCRIPTION = 'merge data by recursive descent down an ancestry hierarchy (forked extended version)'
+DESCRIPTION = ('merge data by recursive descent down an ancestry hierarchy '
+ '(forked extended version)')
VERSION = '1.5.6'
AUTHOR = 'martin f. krafft / Andrew Pickford / salt-formulas community'
AUTHOR_EMAIL = 'salt-formulas@freelists.org'
MAINTAINER = 'salt-formulas community'
MAINTAINER_EMAIL = 'salt-formulas@freelists.org'
-COPYRIGHT = 'Copyright © 2007–14 martin f. krafft, extensions © 2017 Andrew Pickford, extensions © salt-formulas community'
+COPYRIGHT = ('Copyright © 2007–14 martin f. krafft, extensions © 2017 Andrew'
+ ' Pickford, extensions © salt-formulas community')
LICENCE = 'Artistic Licence 2.0'
URL = 'https://github.com/salt-formulas/reclass'