820 lines
32 KiB
Python
820 lines
32 KiB
Python
# -*- coding: utf-8 -*-
|
|
"sets the options of the configuration objects Config object itself"
|
|
# Copyright (C) 2012-2019 Team tiramisu (see AUTHORS for all contributors)
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Lesser General Public License as published by the
|
|
# Free Software Foundation, either version 3 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 Lesser General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
# ____________________________________________________________
|
|
from .error import (RequirementError, PropertiesOptionError,
|
|
ConstError, ConfigError, display_list)
|
|
from .i18n import _
|
|
|
|
|
|
"""If cache and expire is enable, time before cache is expired.
|
|
This delay start first time value/setting is set in cache, even if
|
|
user access several time to value/setting
|
|
"""
|
|
EXPIRATION_TIME = 5
|
|
"""List of default properties (you can add new one if needed).
|
|
|
|
For common properties and personalise properties, if a propery is set for
|
|
an Option and for the Config together, Setting raise a PropertiesOptionError
|
|
|
|
* Common properties:
|
|
|
|
hidden
|
|
option with this property can only get value in read only mode. This
|
|
option is not available in read write mode.
|
|
|
|
disabled
|
|
option with this property cannot be set/get
|
|
|
|
frozen
|
|
cannot set value for option with this properties if 'frozen' is set in
|
|
config
|
|
|
|
mandatory
|
|
should set value for option with this properties if 'mandatory' is set in
|
|
config
|
|
|
|
|
|
* Special property:
|
|
|
|
permissive
|
|
option with 'permissive' cannot raise PropertiesOptionError for properties
|
|
set in permissive
|
|
config with 'permissive', whole option in this config cannot raise
|
|
PropertiesOptionError for properties set in permissive
|
|
|
|
* Special Config properties:
|
|
|
|
cache
|
|
if set, enable cache settings and values
|
|
|
|
expire
|
|
if set, settings and values in cache expire after ``expiration_time``
|
|
|
|
everything_frozen
|
|
whole option in config are frozen (even if option have not frozen
|
|
property)
|
|
|
|
empty
|
|
raise mandatory PropertiesOptionError if multi or leader have empty value
|
|
|
|
validator
|
|
launch validator set by user in option (this property has no effect
|
|
for internal validator)
|
|
|
|
warnings
|
|
display warnings during validation
|
|
|
|
demoting_error_warning
|
|
all value errors are convert to warning (ValueErrorWarning)
|
|
"""
|
|
DEFAULT_PROPERTIES = frozenset(['cache', 'validator', 'warnings'])
|
|
SPECIAL_PROPERTIES = {'frozen', 'mandatory', 'empty', 'force_store_value'}
|
|
|
|
"""Config can be in two defaut mode:
|
|
|
|
read_only
|
|
you can get all variables not disabled but you cannot set any variables
|
|
if a value has a callback without any value, callback is launch and value
|
|
of this variable can change
|
|
you cannot access to mandatory variable without values
|
|
|
|
read_write
|
|
you can get all variables not disabled and not hidden
|
|
you can set all variables not frozen
|
|
"""
|
|
RO_APPEND = frozenset(['frozen', 'disabled', 'validator', 'everything_frozen',
|
|
'mandatory', 'empty', 'force_store_value'])
|
|
RO_REMOVE = frozenset(['permissive', 'hidden'])
|
|
RW_APPEND = frozenset(['frozen', 'disabled', 'validator', 'hidden',
|
|
'force_store_value'])
|
|
RW_REMOVE = frozenset(['permissive', 'everything_frozen', 'mandatory',
|
|
'empty'])
|
|
|
|
|
|
FORBIDDEN_SET_PROPERTIES = frozenset(['force_store_value'])
|
|
FORBIDDEN_SET_PERMISSIVES = frozenset(['force_default_on_freeze',
|
|
'force_metaconfig_on_freeze',
|
|
'force_store_value'])
|
|
|
|
|
|
static_set = frozenset()
|
|
|
|
|
|
class OptionBag:
|
|
__slots__ = ('option', # current option
|
|
'path',
|
|
'index',
|
|
'config_bag',
|
|
'ori_option', # original option (for example useful for symlinkoption)
|
|
'properties', # properties of current option
|
|
'apply_requires', # apply requires or not for this option
|
|
'fromconsistency' # history for consistency
|
|
)
|
|
|
|
def __init__(self):
|
|
self.option = None
|
|
self.fromconsistency = []
|
|
|
|
def set_option(self,
|
|
option,
|
|
path,
|
|
index,
|
|
config_bag):
|
|
if path is None:
|
|
path = option.impl_getpath()
|
|
self.path = path
|
|
self.index = index
|
|
self.option = option
|
|
self.config_bag = config_bag
|
|
|
|
def __getattr__(self, key):
|
|
if key == 'properties':
|
|
settings = self.config_bag.context.cfgimpl_get_settings()
|
|
self.properties = settings.getproperties(self,
|
|
apply_requires=self.apply_requires)
|
|
return self.properties
|
|
elif key == 'ori_option':
|
|
return self.option
|
|
elif key == 'apply_requires':
|
|
return True
|
|
raise KeyError('unknown key {} for OptionBag'.format(key)) # pragma: no cover
|
|
|
|
def __delattr__(self, key):
|
|
if key in ['properties', 'permissives']:
|
|
try:
|
|
super().__delattr__(key)
|
|
except AttributeError:
|
|
pass
|
|
return
|
|
raise KeyError('unknown key {} for ConfigBag'.format(key)) # pragma: no cover
|
|
|
|
def copy(self):
|
|
option_bag = OptionBag()
|
|
for key in self.__slots__:
|
|
if key == 'properties' and self.config_bag is undefined:
|
|
continue
|
|
setattr(option_bag, key, getattr(self, key))
|
|
return option_bag
|
|
|
|
|
|
class ConfigBag:
|
|
__slots__ = ('context', # link to the current context
|
|
'properties', # properties for current context
|
|
'true_properties', # properties for current context
|
|
'permissives', # permissives for current context
|
|
'expiration_time' # EXPIRATION_TIME
|
|
)
|
|
|
|
def __init__(self, context, **kwargs):
|
|
self.context = context
|
|
for key, value in kwargs.items():
|
|
setattr(self, key, value)
|
|
|
|
def __getattr__(self, key):
|
|
if key == 'properties':
|
|
settings = self.context.cfgimpl_get_settings()
|
|
self.properties = settings.get_context_properties()
|
|
return self.properties
|
|
if key == 'permissives':
|
|
settings = self.context.cfgimpl_get_settings()
|
|
self.permissives = settings.get_context_permissives()
|
|
return self.permissives
|
|
if key == 'true_properties':
|
|
return self.properties
|
|
if key == 'expiration_time':
|
|
self.expiration_time = EXPIRATION_TIME
|
|
return self.expiration_time
|
|
raise KeyError('unknown key {} for ConfigBag'.format(key)) # pragma: no cover
|
|
|
|
def remove_warnings(self):
|
|
self.properties = frozenset(self.properties - {'warnings'})
|
|
|
|
def remove_validation(self):
|
|
self.properties = frozenset(self.properties - {'validator'})
|
|
|
|
def unrestraint(self):
|
|
self.true_properties = self.properties
|
|
self.properties = frozenset(['cache'])
|
|
|
|
def set_permissive(self):
|
|
self.properties = frozenset(self.properties | {'permissive'})
|
|
|
|
def __delattr__(self, key):
|
|
if key in ['properties', 'permissives']:
|
|
try:
|
|
super().__delattr__(key)
|
|
except AttributeError:
|
|
pass
|
|
return
|
|
raise KeyError('unknown key {} for ConfigBag'.format(key)) # pragma: no cover
|
|
|
|
# def __setattr__(self, key, value):
|
|
# super().__setattr__(key, value)
|
|
|
|
def copy(self):
|
|
kwargs = {}
|
|
for key in self.__slots__:
|
|
if key in ['properties', 'permissives', 'true_properties'] and \
|
|
not hasattr(self.context, '_impl_settings'):
|
|
# not for GroupConfig
|
|
continue
|
|
kwargs[key] = getattr(self, key)
|
|
return ConfigBag(**kwargs)
|
|
|
|
|
|
# ____________________________________________________________
|
|
class _NameSpace(object):
|
|
"""convenient class that emulates a module
|
|
and builds constants (that is, unique names)
|
|
when attribute is added, we cannot delete it
|
|
"""
|
|
|
|
def __setattr__(self,
|
|
name,
|
|
value):
|
|
if name in self.__dict__:
|
|
raise ConstError(_("can't rebind {0}").format(name))
|
|
self.__dict__[name] = value
|
|
|
|
def __delattr__(self,
|
|
name):
|
|
raise ConstError(_("can't unbind {0}").format(name))
|
|
|
|
|
|
class GroupModule(_NameSpace):
|
|
"emulates a module to manage unique group (OptionDescription) names"
|
|
class GroupType(str):
|
|
"""allowed normal group (OptionDescription) names
|
|
*normal* means : groups that are not leader
|
|
"""
|
|
pass
|
|
|
|
class DefaultGroupType(GroupType):
|
|
"""groups that are default (typically 'default')"""
|
|
pass
|
|
|
|
class LeadershipGroupType(GroupType):
|
|
"""allowed normal group (OptionDescription) names
|
|
*leadership* means : groups that have the 'leadership' attribute set
|
|
"""
|
|
pass
|
|
|
|
|
|
class OwnerModule(_NameSpace):
|
|
"""emulates a module to manage unique owner names.
|
|
|
|
owners are living in `Config._cfgimpl_value_owners`
|
|
"""
|
|
class Owner(str):
|
|
"""allowed owner names
|
|
"""
|
|
pass
|
|
|
|
class DefaultOwner(Owner):
|
|
"""groups that are default (typically 'default')"""
|
|
pass
|
|
|
|
def addowner(self, name):
|
|
"""
|
|
:param name: the name of the new owner
|
|
"""
|
|
setattr(owners, name, owners.Owner(name))
|
|
|
|
|
|
# ____________________________________________________________
|
|
# populate groups
|
|
groups = GroupModule()
|
|
"""groups.default
|
|
default group set when creating a new optiondescription"""
|
|
groups.default = groups.DefaultGroupType('default')
|
|
|
|
"""groups.leadership
|
|
leadership group is a special optiondescription, all suboptions should
|
|
be multi option and all values should have same length, to find
|
|
leader's option, the optiondescription's name should be same than de
|
|
leader's option"""
|
|
groups.leadership = groups.LeadershipGroupType('leadership')
|
|
|
|
""" groups.family
|
|
example of group, no special behavior with this group's type"""
|
|
groups.family = groups.GroupType('family')
|
|
|
|
|
|
# ____________________________________________________________
|
|
# populate owners with default attributes
|
|
owners = OwnerModule()
|
|
"""default
|
|
is the config owner after init time"""
|
|
owners.default = owners.DefaultOwner('default')
|
|
"""user
|
|
is the generic is the generic owner"""
|
|
owners.user = owners.Owner('user')
|
|
"""forced
|
|
special owner when value is forced"""
|
|
owners.forced = owners.Owner('forced')
|
|
|
|
|
|
forbidden_owners = (owners.default, owners.forced)
|
|
|
|
|
|
# ____________________________________________________________
|
|
class Undefined(object):
|
|
def __str__(self): # pragma: no cover
|
|
return 'Undefined'
|
|
|
|
__repr__ = __str__
|
|
|
|
|
|
undefined = Undefined()
|
|
|
|
|
|
# ____________________________________________________________
|
|
class Settings(object):
|
|
"``config.Config()``'s configuration options settings"
|
|
__slots__ = ('_p_',
|
|
'_pp_',
|
|
'__weakref__',
|
|
'ro_append',
|
|
'ro_remove',
|
|
'rw_append',
|
|
'rw_remove',
|
|
'default_properties')
|
|
|
|
def __init__(self,
|
|
properties,
|
|
permissives):
|
|
"""
|
|
initializer
|
|
|
|
:param context: the root config
|
|
:param storage: the storage type
|
|
|
|
- dictionary -> in memory
|
|
- sqlite3 -> persistent
|
|
"""
|
|
# generic owner
|
|
self._p_ = properties
|
|
self._pp_ = permissives
|
|
self.default_properties = DEFAULT_PROPERTIES
|
|
self.ro_append = RO_APPEND
|
|
self.ro_remove = RO_REMOVE
|
|
self.rw_append = RW_APPEND
|
|
self.rw_remove = RW_REMOVE
|
|
|
|
# ____________________________________________________________
|
|
# get properties and permissive methods
|
|
|
|
def get_context_properties(self):
|
|
is_cached, props = self._p_.getcache(None,
|
|
None,
|
|
None,
|
|
{},
|
|
{},
|
|
'context_props')
|
|
if not is_cached:
|
|
props = self._p_.getproperties(None,
|
|
self.default_properties)
|
|
self._p_.setcache(None,
|
|
None,
|
|
props,
|
|
{},
|
|
props)
|
|
return props
|
|
|
|
def getproperties(self,
|
|
option_bag,
|
|
apply_requires=True):
|
|
"""
|
|
"""
|
|
opt = option_bag.option
|
|
config_bag = option_bag.config_bag
|
|
path = option_bag.path
|
|
index = option_bag.index
|
|
if opt.impl_is_symlinkoption():
|
|
opt = opt.impl_getopt()
|
|
path = opt.impl_getpath()
|
|
|
|
if apply_requires:
|
|
props = config_bag.properties
|
|
is_cached, props = self._p_.getcache(path,
|
|
config_bag.expiration_time,
|
|
index,
|
|
props,
|
|
{},
|
|
'self_props')
|
|
else:
|
|
is_cached = False
|
|
if not is_cached:
|
|
props = self._p_.getproperties(path,
|
|
opt.impl_getproperties())
|
|
if apply_requires:
|
|
props |= self.apply_requires(option_bag,
|
|
False)
|
|
props -= self.getpermissives(opt,
|
|
path)
|
|
if apply_requires:
|
|
self._p_.setcache(path,
|
|
index,
|
|
props,
|
|
props,
|
|
config_bag.properties)
|
|
return props
|
|
|
|
def get_context_permissives(self):
|
|
return self.getpermissives(None, None)
|
|
|
|
def getpermissives(self,
|
|
opt,
|
|
path):
|
|
if opt and opt.impl_is_symlinkoption():
|
|
opt = opt.impl_getopt()
|
|
path = opt.impl_getpath()
|
|
return self._pp_.getpermissives(path)
|
|
|
|
def apply_requires(self,
|
|
option_bag,
|
|
readable):
|
|
"""carries out the jit (just in time) requirements between options
|
|
|
|
a requirement is a tuple of this form that comes from the option's
|
|
requirements validation::
|
|
|
|
(option, expected, action, inverse, transitive, same_action)
|
|
|
|
let's have a look at all the tuple's items:
|
|
|
|
- **option** is the target option's
|
|
|
|
- **expected** is the target option's value that is going to trigger
|
|
an action
|
|
|
|
- **action** is the (property) action to be accomplished if the target
|
|
option happens to have the expected value
|
|
|
|
- if **inverse** is `True` and if the target option's value does not
|
|
apply, then the property action must be removed from the option's
|
|
properties list (wich means that the property is inverted)
|
|
|
|
- **transitive**: but what happens if the target option cannot be
|
|
accessed ? We don't kown the target option's value. Actually if some
|
|
property in the target option is not present in the permissive, the
|
|
target option's value cannot be accessed. In this case, the
|
|
**action** have to be applied to the option. (the **action** property
|
|
is then added to the option).
|
|
|
|
- **same_action**: actually, if **same_action** is `True`, the
|
|
transitivity is not accomplished. The transitivity is accomplished
|
|
only if the target option **has the same property** that the demanded
|
|
action. If the target option's value is not accessible because of
|
|
another reason, because of a property of another type, then an
|
|
exception :exc:`~error.RequirementError` is raised.
|
|
|
|
And at last, if no target option matches the expected values, the
|
|
action will not add to the option's properties list.
|
|
|
|
:param opt: the option on wich the requirement occurs
|
|
:type opt: `option.Option()`
|
|
:param path: the option's path in the config
|
|
:type path: str
|
|
"""
|
|
current_requires = option_bag.option.impl_getrequires()
|
|
|
|
# filters the callbacks
|
|
if readable:
|
|
calc_properties = {}
|
|
else:
|
|
calc_properties = set()
|
|
|
|
if not current_requires:
|
|
return calc_properties
|
|
|
|
context = option_bag.config_bag.context
|
|
all_properties = None
|
|
for requires in current_requires:
|
|
for require in requires:
|
|
exps, action, inverse, transitive, same_action, operator = require
|
|
breaked = False
|
|
for option, expected in exps:
|
|
if option.issubdyn():
|
|
option = option.to_dynoption(option_bag.option.rootpath,
|
|
option_bag.option.impl_getsuffix())
|
|
reqpath = option.impl_getpath()
|
|
#FIXME too later!
|
|
if reqpath.startswith(option_bag.path + '.'):
|
|
raise RequirementError(_("malformed requirements "
|
|
"imbrication detected for option:"
|
|
" '{0}' with requirement on: "
|
|
"'{1}'").format(option_bag.path, reqpath))
|
|
idx = None
|
|
is_indexed = False
|
|
if option.impl_is_follower():
|
|
idx = option_bag.index
|
|
if idx is None:
|
|
continue
|
|
elif option.impl_is_leader() and option_bag.index is None:
|
|
continue
|
|
elif option.impl_is_multi() and option_bag.index is not None:
|
|
is_indexed = True
|
|
config_bag = option_bag.config_bag.copy()
|
|
soption_bag = OptionBag()
|
|
soption_bag.set_option(option,
|
|
reqpath,
|
|
idx,
|
|
config_bag)
|
|
if option_bag.option == option:
|
|
soption_bag.config_bag.unrestraint()
|
|
soption_bag.config_bag.remove_validation()
|
|
soption_bag.apply_requires = False
|
|
else:
|
|
soption_bag.config_bag.properties = soption_bag.config_bag.true_properties
|
|
soption_bag.config_bag.set_permissive()
|
|
try:
|
|
value = context.getattr(reqpath,
|
|
soption_bag)
|
|
except PropertiesOptionError as err:
|
|
properties = err.proptype
|
|
# if not transitive, properties must be verify in current requires
|
|
# otherwise if same_action, property must be in properties
|
|
# otherwise add property in returned properties (if operator is 'and')
|
|
if not transitive:
|
|
if all_properties is None:
|
|
all_properties = []
|
|
for requires_ in current_requires:
|
|
for require_ in requires_:
|
|
all_properties.append(require_[1])
|
|
if not set(properties) - set(all_properties):
|
|
continue
|
|
if same_action and action not in properties:
|
|
if len(properties) == 1:
|
|
prop_msg = _('property')
|
|
else:
|
|
prop_msg = _('properties')
|
|
raise RequirementError(_('cannot access to option "{0}" because '
|
|
'required option "{1}" has {2} {3}'
|
|
'').format(option_bag.option.impl_get_display_name(),
|
|
option.impl_get_display_name(),
|
|
prop_msg,
|
|
display_list(list(properties), add_quote=True)))
|
|
# transitive action, add action
|
|
if operator != 'and':
|
|
if readable:
|
|
for msg in self.apply_requires(err._option_bag,
|
|
True).values():
|
|
calc_properties.setdefault(action, []).extend(msg)
|
|
else:
|
|
calc_properties.add(action)
|
|
breaked = True
|
|
break
|
|
else:
|
|
if is_indexed:
|
|
value = value[option_bag.index]
|
|
if (not inverse and value in expected or
|
|
inverse and value not in expected):
|
|
if operator != 'and':
|
|
if readable:
|
|
if not inverse:
|
|
msg = _('the value of "{0}" is {1}')
|
|
else:
|
|
msg = _('the value of "{0}" is not {1}')
|
|
calc_properties.setdefault(action, []).append(
|
|
msg.format(option.impl_get_display_name(),
|
|
display_list(expected, 'or', add_quote=True)))
|
|
else:
|
|
calc_properties.add(action)
|
|
breaked = True
|
|
break
|
|
elif operator == 'and':
|
|
break
|
|
else:
|
|
if operator == 'and':
|
|
calc_properties.add(action)
|
|
continue
|
|
if breaked:
|
|
break
|
|
return calc_properties
|
|
|
|
#____________________________________________________________
|
|
# set methods
|
|
def set_context_properties(self,
|
|
properties,
|
|
context):
|
|
self._p_.setproperties(None,
|
|
properties)
|
|
context.cfgimpl_reset_cache(None)
|
|
|
|
def setproperties(self,
|
|
path,
|
|
properties,
|
|
option_bag,
|
|
context):
|
|
"""save properties for specified path
|
|
(never save properties if same has option properties)
|
|
"""
|
|
# should have index !!!
|
|
opt = option_bag.option
|
|
if opt.impl_getrequires() is not None:
|
|
not_allowed_props = properties & \
|
|
getattr(opt, '_calc_properties', static_set)
|
|
if not_allowed_props:
|
|
raise ValueError(_('cannot set property {} for option "{}" this property is '
|
|
'calculated').format(display_list(list(not_allowed_props),
|
|
add_quote=True),
|
|
opt.impl_get_display_name()))
|
|
if opt.impl_is_symlinkoption():
|
|
raise TypeError(_("can't assign property to the symlinkoption \"{}\""
|
|
"").format(opt.impl_get_display_name()))
|
|
if ('force_default_on_freeze' in properties or 'force_metaconfig_on_freeze' in properties) and \
|
|
'frozen' not in properties and \
|
|
opt.impl_is_leader():
|
|
raise ConfigError(_('a leader ({0}) cannot have '
|
|
'"force_default_on_freeze" or "force_metaconfig_on_freeze" property without "frozen"'
|
|
'').format(opt.impl_get_display_name()))
|
|
self._p_.setproperties(path,
|
|
properties)
|
|
# values too because of follower values could have a PropertiesOptionError has value
|
|
context.cfgimpl_reset_cache(option_bag)
|
|
del option_bag.properties
|
|
|
|
def set_context_permissives(self,
|
|
permissives):
|
|
self.setpermissives(None,
|
|
permissives)
|
|
|
|
def setpermissives(self,
|
|
option_bag,
|
|
permissives):
|
|
"""
|
|
enables us to put the permissives in the storage
|
|
|
|
:param path: the option's path
|
|
:param type: str
|
|
:param opt: if an option object is set, the path is extracted.
|
|
it is better (faster) to set the path parameter
|
|
instead of passing a :class:`tiramisu.option.Option()` object.
|
|
"""
|
|
if not isinstance(permissives, frozenset):
|
|
raise TypeError(_('permissive must be a frozenset'))
|
|
if option_bag is not None:
|
|
opt = option_bag.option
|
|
if opt and opt.impl_is_symlinkoption():
|
|
raise TypeError(_("can't assign permissive to the symlinkoption \"{}\""
|
|
"").format(opt.impl_get_display_name()))
|
|
path = option_bag.path
|
|
else:
|
|
path = None
|
|
forbidden_permissives = FORBIDDEN_SET_PERMISSIVES & permissives
|
|
if forbidden_permissives:
|
|
raise ConfigError(_('cannot add those permissives: {0}').format(
|
|
' '.join(forbidden_permissives)))
|
|
self._pp_.setpermissives(path, permissives)
|
|
if option_bag is not None:
|
|
option_bag.config_bag.context.cfgimpl_reset_cache(option_bag)
|
|
|
|
#____________________________________________________________
|
|
# reset methods
|
|
|
|
def reset(self,
|
|
option_bag,
|
|
context):
|
|
if option_bag is None:
|
|
opt = None
|
|
path = None
|
|
else:
|
|
opt = option_bag.option
|
|
assert not opt.impl_is_symlinkoption(), _("can't reset properties to "
|
|
"the symlinkoption \"{}\""
|
|
"").format(opt.impl_get_display_name())
|
|
path = option_bag.path
|
|
self._p_.delproperties(path)
|
|
context.cfgimpl_reset_cache(option_bag)
|
|
|
|
def reset_permissives(self,
|
|
option_bag,
|
|
context):
|
|
if option_bag is None:
|
|
opt = None
|
|
path = None
|
|
else:
|
|
opt = option_bag.option
|
|
assert not opt.impl_is_symlinkoption(), _("can't reset permissives to "
|
|
"the symlinkoption \"{}\""
|
|
"").format(opt.impl_get_display_name())
|
|
path = option_bag.path
|
|
self._pp_.delpermissive(path)
|
|
context.cfgimpl_reset_cache(option_bag)
|
|
|
|
#____________________________________________________________
|
|
# validate properties
|
|
def calc_raises_properties(self,
|
|
option_properties,
|
|
config_properties,
|
|
config_permissives):
|
|
properties = option_properties & config_properties - SPECIAL_PROPERTIES
|
|
# remove global permissive properties
|
|
if properties and ('permissive' in config_properties):
|
|
properties -= config_permissives
|
|
# at this point an option should not remain in properties
|
|
return properties
|
|
|
|
def validate_properties(self,
|
|
option_bag):
|
|
"""
|
|
validation upon the properties related to `opt`
|
|
|
|
:param opt: an option or an option description object
|
|
:param force_permissive: behaves as if the permissive property
|
|
was present
|
|
"""
|
|
config_bag = option_bag.config_bag
|
|
if not config_bag.properties: # pragma: no cover
|
|
return
|
|
properties = self.calc_raises_properties(option_bag.properties,
|
|
config_bag.properties,
|
|
config_bag.permissives)
|
|
if properties != frozenset():
|
|
raise PropertiesOptionError(option_bag,
|
|
properties,
|
|
self)
|
|
|
|
def validate_mandatory(self,
|
|
value,
|
|
option_bag):
|
|
if 'mandatory' in option_bag.config_bag.properties:
|
|
values = option_bag.config_bag.context.cfgimpl_get_values()
|
|
is_mandatory = False
|
|
if ('permissive' in option_bag.config_bag.properties) and \
|
|
'mandatory' in option_bag.config_bag.permissives:
|
|
pass
|
|
elif 'mandatory' in option_bag.properties and values.isempty(option_bag.option,
|
|
value,
|
|
index=option_bag.index):
|
|
is_mandatory = True
|
|
if 'empty' in option_bag.properties and values.isempty(option_bag.option,
|
|
value,
|
|
force_allow_empty_list=True,
|
|
index=option_bag.index):
|
|
is_mandatory = True
|
|
if is_mandatory:
|
|
raise PropertiesOptionError(option_bag,
|
|
['mandatory'],
|
|
self)
|
|
|
|
def validate_frozen(self,
|
|
option_bag):
|
|
if option_bag.config_bag.properties and \
|
|
('everything_frozen' in option_bag.config_bag.properties or
|
|
'frozen' in option_bag.properties) and \
|
|
not (('permissive' in option_bag.config_bag.properties) and
|
|
'frozen' in option_bag.config_bag.permissives):
|
|
raise PropertiesOptionError(option_bag,
|
|
['frozen'],
|
|
self)
|
|
return False
|
|
#____________________________________________________________
|
|
# read only/read write
|
|
|
|
def _read(self,
|
|
remove,
|
|
append,
|
|
context):
|
|
props = self._p_.getproperties(None,
|
|
self.default_properties)
|
|
modified = False
|
|
if remove & props:
|
|
props = props - remove
|
|
modified = True
|
|
if append & props != append:
|
|
props = props | append
|
|
modified = True
|
|
if modified:
|
|
self.set_context_properties(frozenset(props),
|
|
context)
|
|
|
|
def read_only(self,
|
|
context):
|
|
"convenience method to freeze, hide and disable"
|
|
self._read(self.ro_remove,
|
|
self.ro_append,
|
|
context)
|
|
|
|
def read_write(self,
|
|
context):
|
|
"convenience method to freeze, hide and disable"
|
|
self._read(self.rw_remove,
|
|
self.rw_append,
|
|
context)
|