rougail/src/rougail/loader.py

547 lines
21 KiB
Python
Raw Normal View History

2020-07-06 20:58:11 +02:00
"""loader
2019-11-23 08:17:35 +01:00
flattened XML specific
"""
2020-07-07 18:12:16 +02:00
from os.path import join, isfile
from lxml.etree import DTD
import imp
2019-11-23 08:17:35 +01:00
2019-12-21 12:45:01 +01:00
from tiramisu import (StrOption, OptionDescription, DynOptionDescription, PortOption,
IntOption, ChoiceOption, BoolOption, SymLinkOption, IPOption,
NetworkOption, NetmaskOption, DomainnameOption, BroadcastOption,
URLOption, EmailOption, FilenameOption, UsernameOption, DateOption,
PasswordOption, BoolOption, MACOption, Leadership, submulti,
2020-07-07 18:12:16 +02:00
Params, ParamSelfOption, ParamOption, ParamDynOption, ParamValue, Calculation, calc_value)
2019-11-23 08:17:35 +01:00
2019-11-24 20:25:09 +01:00
from .config import dtdfilename
2019-11-23 08:17:35 +01:00
from .i18n import _
from .utils import normalize_family
2020-07-06 20:58:11 +02:00
from .annotator import VARIABLE_NAMESPACE
2020-07-07 18:12:16 +02:00
from .error import CreoleLoaderError
2019-11-23 08:17:35 +01:00
2020-02-18 22:12:55 +01:00
FUNC_TO_DICT = ['valid_not_equal']
class ConvertDynOptionDescription(DynOptionDescription):
def convert_suffix_to_path(self, suffix):
2020-03-04 15:37:08 +01:00
if not isinstance(suffix, str):
suffix = str(suffix)
return normalize_family(suffix,
2019-12-22 08:45:03 +01:00
check_name=False)
2020-07-16 09:50:01 +02:00
def convert_boolean(value):
prop = {'True': True,
'False': False,
'None': None}
if value not in prop:
raise Exception('unknown value {} while trying to cast {} to boolean'.format(value, obj))
return prop[value]
def convert_tiramisu_value(value, type_):
2019-11-24 20:25:09 +01:00
"""
convertit les variables dans le bon type si nécessaire
"""
2020-07-07 18:12:16 +02:00
if value is None:
return value
2020-07-16 09:50:01 +02:00
func = CONVERT_OPTION[type_].get('func', None)
2019-11-26 20:33:24 +01:00
if func is None:
return value
2019-11-24 20:25:09 +01:00
if isinstance(value, list):
return [func(val) for val in value]
else:
return func(value)
2020-07-16 09:50:01 +02:00
CONVERT_OPTION = {'number': dict(opttype=IntOption, func=int),
2019-11-23 08:17:35 +01:00
'choice': dict(opttype=ChoiceOption),
2019-11-27 14:03:34 +01:00
'string': dict(opttype=StrOption),
2019-11-23 08:17:35 +01:00
'password': dict(opttype=PasswordOption),
'mail': dict(opttype=EmailOption),
2020-07-16 09:50:01 +02:00
'boolean': dict(opttype=BoolOption, initkwargs={'default': True}, func=convert_boolean),
2019-11-23 08:17:35 +01:00
'symlink': dict(opttype=SymLinkOption),
'filename': dict(opttype=FilenameOption),
'date': dict(opttype=DateOption),
'unix_user': dict(opttype=UsernameOption),
'ip': dict(opttype=IPOption, initkwargs={'allow_reserved': True}),
'local_ip': dict(opttype=IPOption, initkwargs={'private_only': True, 'warnings_only': True}),
'netmask': dict(opttype=NetmaskOption),
'network': dict(opttype=NetworkOption),
'broadcast': dict(opttype=BroadcastOption),
2019-11-26 20:33:24 +01:00
'netbios': dict(opttype=DomainnameOption, initkwargs={'type': 'netbios', 'warnings_only': True}),
'domain': dict(opttype=DomainnameOption, initkwargs={'type': 'domainname', 'allow_ip': True, 'allow_without_dot': True}),
'domain_strict': dict(opttype=DomainnameOption, initkwargs={'type': 'domainname', 'allow_ip': False}),
'hostname': dict(opttype=DomainnameOption, initkwargs={'type': 'hostname', 'allow_ip': True}),
'hostname_strict': dict(opttype=DomainnameOption, initkwargs={'type': 'hostname', 'allow_ip': False}),
2019-11-23 08:17:35 +01:00
'web_address': dict(opttype=URLOption, initkwargs={'allow_ip': True, 'allow_without_dot': True}),
'port': dict(opttype=PortOption, initkwargs={'allow_private': True}),
'mac': dict(opttype=MACOption),
'cidr': dict(opttype=IPOption, initkwargs={'cidr': True}),
'network_cidr': dict(opttype=NetworkOption, initkwargs={'cidr': True}),
2019-11-23 08:17:35 +01:00
}
2020-07-16 09:50:01 +02:00
class BaseElt:
2019-11-23 08:17:35 +01:00
def __init__(self, attrib):
self.attrib = attrib
2020-07-16 09:50:01 +02:00
class Common:
def populate_properties(self, child):
if child.get('type') == 'calculation':
kwargs = {'condition': child.attrib['source'],
'expected': ParamValue(child.attrib.get('expected'))}
if child.attrib['inverse'] == 'True':
kwargs['reverse_condition'] = ParamValue(True)
self.attrib['properties'].append((ParamValue(child.text), kwargs))
else:
self.attrib['properties'].append(child.text)
def build_properties(self):
for index, prop in enumerate(self.attrib['properties']):
if isinstance(prop, tuple):
action, kwargs = prop
kwargs['condition'] = ParamOption(self.storage.get(kwargs['condition']).get(), todict=True)
prop = Calculation(calc_value,
Params(action,
kwargs=kwargs))
self.attrib['properties'][index] = prop
if self.attrib['properties']:
self.attrib['properties'] = tuple(self.attrib['properties'])
else:
del self.attrib['properties']
2020-07-07 18:12:16 +02:00
class PopulateTiramisuObjects:
2019-11-23 08:17:35 +01:00
def __init__(self):
self.storage = ElementStorage()
self.booleans = []
def parse_dtd(self, dtdfilename):
"""Loads the Creole DTD
:raises IOError: if the DTD is not found
:param dtdfilename: the full filename of the Creole DTD
"""
if not isfile(dtdfilename):
raise IOError(_("no such DTD file: {}").format(dtdfilename))
with open(dtdfilename, 'r') as dtdfd:
dtd = DTD(dtdfd)
for elt in dtd.iterelements():
if elt.name == 'variable':
for attr in elt.iterattributes():
if set(attr.itervalues()) == set(['True', 'False']):
self.booleans.append(attr.name)
2020-07-16 09:50:01 +02:00
def make_tiramisu_objects(self, xmlroot, eosfunc):
self.eosfunc = imp.load_source('eosfunc', eosfunc)
family = self.get_root_family()
for xmlelt in self.reorder_family(xmlroot):
self.iter_family(xmlelt,
family,
None,
)
2020-07-07 18:12:16 +02:00
def get_root_family(self):
2020-07-16 09:50:01 +02:00
family = Family(BaseElt({'name': 'baseoption',
'doc': 'baseoption'}),
2020-07-07 18:12:16 +02:00
self.booleans,
self.storage,
self.eosfunc,
)
self.storage.add('.', family)
2019-11-23 08:17:35 +01:00
return family
2020-07-07 18:12:16 +02:00
def reorder_family(self, xmlroot):
xmlelts = []
for xmlelt in xmlroot:
# VARIABLE_NAMESPACE family has to be loaded before any other family
# because `extra` family could use `VARIABLE_NAMESPACE` variables.
if xmlelt.attrib['name'] == VARIABLE_NAMESPACE:
xmlelts.insert(0, xmlelt)
2019-11-23 08:17:35 +01:00
else:
2020-07-07 18:12:16 +02:00
xmlelts.append(xmlelt)
return xmlelts
2019-11-23 08:17:35 +01:00
2020-07-07 18:12:16 +02:00
def iter_family(self,
child,
family,
subpath,
):
if child.tag in ['family', 'leader']:
2020-07-16 09:50:01 +02:00
function = self.populate_family
2019-11-23 08:17:35 +01:00
elif child.tag == 'variable':
2020-07-16 09:50:01 +02:00
function = self.populate_variable
2019-11-26 20:33:24 +01:00
elif child.tag == 'property':
2020-07-16 09:50:01 +02:00
# property already imported with family
return
2019-11-27 15:03:05 +01:00
else:
raise Exception('unknown tag {}'.format(child.tag))
2020-07-16 09:50:01 +02:00
function(family,
child,
subpath,
)
2019-11-26 20:33:24 +01:00
2020-07-07 18:12:16 +02:00
def populate_family(self,
parent_family,
elt,
subpath,
):
path = self.build_path(subpath,
elt,
)
family = Family(elt,
self.booleans,
self.storage,
self.eosfunc,
2020-07-16 09:50:01 +02:00
elt.tag == 'leader',
2020-07-07 18:12:16 +02:00
)
self.storage.add(path, family)
parent_family.add(family)
if len(elt) != 0:
for child in elt:
self.iter_family(child,
family,
path,
)
2020-07-16 09:50:01 +02:00
def populate_variable(self,
family,
elt,
subpath,
):
2020-07-07 18:12:16 +02:00
is_follower = False
is_leader = False
if family.is_leader:
if elt.attrib['name'] != family.attrib['name']:
is_follower = True
else:
is_leader = True
path = self.build_path(subpath,
elt,
)
variable = Variable(elt,
self.booleans,
self.storage,
is_follower,
is_leader,
self.eosfunc,
)
self.storage.add(path, variable)
family.add(variable)
def build_path(self,
subpath,
elt,
):
if subpath is None:
return elt.attrib['name']
return subpath + '.' + elt.attrib['name']
2019-11-23 08:17:35 +01:00
2019-11-26 20:33:24 +01:00
class ElementStorage:
2019-11-23 08:17:35 +01:00
def __init__(self):
self.paths = {}
def add(self, path, elt):
if path in self.paths:
raise CreoleLoaderError(_('path already loaded {}').format(path))
self.paths[path] = elt
def get(self, path):
if path not in self.paths:
raise CreoleLoaderError(_('there is no element for path {}').format(path))
return self.paths[path]
2019-11-26 20:33:24 +01:00
class Variable(Common):
def __init__(self, elt, booleans, storage, is_follower, is_leader, eosfunc):
2019-11-23 08:17:35 +01:00
self.option = None
self.informations = {}
self.attrib = {}
2020-07-09 18:48:51 +02:00
convert_option = CONVERT_OPTION[elt.attrib['type']]
if 'initkwargs' in convert_option:
self.attrib.update(convert_option['initkwargs'])
2020-07-07 18:12:16 +02:00
if elt.attrib['type'] != 'symlink':
self.attrib['properties'] = []
2019-11-27 15:03:05 +01:00
self.attrib['validators'] = []
2019-11-23 08:17:35 +01:00
self.eosfunc = eosfunc
2019-11-26 20:33:24 +01:00
self.storage = storage
2020-07-07 18:12:16 +02:00
self.booleans = booleans
self.populate_attrib(elt)
self.is_follower = is_follower
self.is_leader = is_leader
self.object_type = convert_option['opttype']
2020-07-16 09:50:01 +02:00
if 'multi' in self.attrib and self.attrib['multi']:
self.attrib['default'] = []
if self.is_follower:
self.attrib['default_multi'] = []
self.parse_children(elt)
if self.is_follower:
if self.attrib['multi'] is True:
self.attrib['multi'] = submulti
else:
self.attrib['multi'] = True
2020-07-07 18:12:16 +02:00
if elt.attrib['type'] == 'symlink':
self.attrib['opt'] = storage.get(self.attrib['opt'])
2020-07-16 09:50:01 +02:00
def parse_children(self,
elt,
):
if elt.attrib['type'] == 'choice':
values = []
for child in elt:
if child.tag == 'property':
self.populate_properties(child)
elif child.tag == 'value':
if child.attrib.get('type') == 'calculation':
self.calc_value(child)
else:
self.populate_value(child, elt)
elif child.tag == 'check':
self.populate_check(child)
elif child.tag == 'choice':
value = child.text
if child.attrib['type'] == 'number':
value = int(value)
values.append(value)
if elt.attrib['type'] == 'choice':
self.attrib['values'] = tuple(values)
2020-07-07 18:12:16 +02:00
def populate_attrib(self,
elt,
):
2019-11-23 08:17:35 +01:00
for key, value in elt.attrib.items():
2020-07-07 18:12:16 +02:00
if key == 'multi' and elt.attrib['type'] == 'symlink':
continue
2020-07-16 09:50:01 +02:00
if key in self.booleans:
2019-11-23 08:17:35 +01:00
if value == 'True':
value = True
elif value == 'False':
value = False
else:
raise CreoleLoaderError(_('unknown value {} for {}').format(value, key))
2020-07-16 09:50:01 +02:00
if key in ['help', 'test', 'separator']:
2019-11-26 20:33:24 +01:00
self.add_information(key, value)
2020-07-07 18:12:16 +02:00
elif key != 'type':
2019-11-26 20:33:24 +01:00
self.attrib[key] = value
2020-07-07 18:12:16 +02:00
2020-07-16 09:50:01 +02:00
def calc_value(self, child):
args = []
kwargs = {}
if child.text is not None and child.text.strip():
function = child.text.strip()
else:
function = child.attrib['name']
for param in child:
self.parse_param(args,
kwargs,
param,
)
self.attrib['default'] = {'function': getattr(self.eosfunc, function),
'args': args,
'kwargs': kwargs,
'warnings_only': False}
def populate_value(self, child, elt):
if "type" in child.attrib:
type_ = child.attrib['type']
else:
type_ = elt.attrib['type']
value = convert_tiramisu_value(child.text, type_)
if self.is_follower:
if self.attrib['multi'] is True:
self.attrib['default_multi'].append(value)
2020-07-07 18:12:16 +02:00
else:
2020-07-16 09:50:01 +02:00
self.attrib['default_multi'] = value
elif self.attrib['multi'] is True:
self.attrib['default'].append(value)
if not self.is_leader:
self.attrib['default_multi'] = value
else:
self.attrib['default'] = value
2020-07-07 18:12:16 +02:00
def populate_check(self, child):
2020-07-16 09:50:01 +02:00
args = [ParamSelfOption()]
kwargs = {}
for param in child:
self.parse_param(args,
kwargs,
param,
)
self.attrib['validators'].append({'function': getattr(self.eosfunc, child.attrib['name']),
'args': args,
'kwargs': kwargs,
'warnings_only': convert_boolean(child.attrib['warnings_only'])})
def parse_param(self,
args,
kwargs,
param,
):
2019-11-27 14:03:34 +01:00
if param.attrib['type'] == 'string':
2020-07-16 09:50:01 +02:00
value = ParamValue(param.text)
2019-11-27 14:03:34 +01:00
elif param.attrib['type'] == 'number':
2020-07-16 09:50:01 +02:00
value = ParamValue(int(param.text))
elif param.attrib['type'] == 'variable':
transitive = convert_boolean(param.attrib.get('transitive', 'False'))
value = {'option': param.text,
'notraisepropertyerror': transitive,
'todict': param.text in FUNC_TO_DICT,
}
if 'suffix' in param.attrib:
value['suffix'] = param.attrib['suffix']
2019-11-27 14:03:34 +01:00
else:
raise CreoleLoaderError(_('unknown param type {}').format(param.attrib['type']))
2020-07-16 09:50:01 +02:00
if 'name' not in param.attrib:
args.append(value)
else:
kwargs[param.attrib['name']] = value
2019-11-23 08:17:35 +01:00
2019-11-27 15:03:05 +01:00
def build_calculator(self, key):
2020-07-16 09:50:01 +02:00
def build_param(param):
option = self.storage.get(param['option']).get()
if 'suffix' in param:
family = '.'.join(param['option'].split('.')[:-1])
return ParamDynOption(option,
param['suffix'],
self.storage.get(family).get(),
notraisepropertyerror=param['notraisepropertyerror'],
todict=param['todict'],
)
return ParamOption(option,
notraisepropertyerror=param['notraisepropertyerror'],
todict=param['todict'],
)
return param
def build_calculation(value):
args = []
kwargs = {}
for param in value['args']:
if isinstance(param, dict):
param = build_param(param)
args.append(param)
for key, param in value['kwargs'].items():
if isinstance(param, dict):
param = build_param(param)
kwargs[key] = param
return Calculation(value['function'],
Params(tuple(args),
kwargs=kwargs,
),
warnings_only=value['warnings_only'],
)
2019-11-27 15:03:05 +01:00
if key in self.attrib:
values = self.attrib[key]
if isinstance(values, list):
2020-07-16 09:50:01 +02:00
self.attrib[key] = []
for value in values:
if isinstance(value, dict):
value = build_calculation(value)
self.attrib[key].append(value)
2019-11-27 15:03:05 +01:00
else:
2020-07-16 09:50:01 +02:00
if isinstance(values, dict):
values = build_calculation(values)
self.attrib[key] = values
2019-11-27 15:03:05 +01:00
2020-07-16 09:50:01 +02:00
def add_information(self, key, value):
if key in self.informations:
raise CreoleLoaderError(_('key already exists in information {}').format(key))
self.informations[key] = value
2019-11-27 14:03:34 +01:00
2019-11-23 08:17:35 +01:00
def get(self):
if self.option is None:
if self.object_type is SymLinkOption:
self.attrib['opt'] = self.attrib['opt'].get()
2019-11-26 20:33:24 +01:00
else:
self.build_properties()
2019-11-27 15:03:05 +01:00
self.build_calculator('default')
self.build_calculator('validators')
if not self.attrib['validators']:
del self.attrib['validators']
2019-11-23 08:17:35 +01:00
try:
option = self.object_type(**self.attrib)
except Exception as err:
import traceback
traceback.print_exc()
name = self.attrib['name']
2020-07-07 18:12:16 +02:00
raise CreoleLoaderError(_('cannot create option "{}": {}').format(name, err))
2019-11-23 08:17:35 +01:00
for key, value in self.informations.items():
option.impl_set_information(key, value)
self.option = option
return self.option
2019-11-26 20:33:24 +01:00
class Family(Common):
2020-07-07 18:12:16 +02:00
def __init__(self,
elt,
booleans,
storage,
eosfunc,
2020-07-16 09:50:01 +02:00
is_leader=False,
2020-07-07 18:12:16 +02:00
):
2019-11-23 08:17:35 +01:00
self.option = None
self.attrib = {}
2019-11-26 20:33:24 +01:00
self.is_leader = False
2020-07-07 18:12:16 +02:00
self.informations = {}
2019-11-23 08:17:35 +01:00
self.children = []
2019-11-26 20:33:24 +01:00
self.storage = storage
self.eosfunc = eosfunc
2019-11-23 08:17:35 +01:00
self.attrib['properties'] = []
for key, value in elt.attrib.items():
2020-07-07 18:12:16 +02:00
if key == 'help':
2019-11-26 20:33:24 +01:00
self.add_information(key, value)
2019-11-23 08:17:35 +01:00
else:
self.attrib[key] = value
2020-07-16 09:50:01 +02:00
if not isinstance(elt, BaseElt) and len(elt) != 0:
for child in elt:
if child.tag == 'property':
self.populate_properties(child)
self.is_leader = is_leader
2019-11-23 08:17:35 +01:00
def add(self, child):
self.children.append(child)
def add_information(self, key, value):
if key in self.informations and not (key == 'icon' and self.informations[key] is None):
raise CreoleLoaderError(_('key already exists in information {}').format(key))
self.informations[key] = value
def get(self):
if self.option is None:
self.attrib['children'] = []
for child in self.children:
self.attrib['children'].append(child.get())
2019-11-26 20:33:24 +01:00
self.build_properties()
2019-11-23 08:17:35 +01:00
try:
if 'dynamic' in self.attrib:
dynamic = self.storage.get(self.attrib['dynamic']).get()
del self.attrib['dynamic']
self.attrib['suffixes'] = Calculation(self.eosfunc.calc_value,
Params((ParamOption(dynamic),)))
option = ConvertDynOptionDescription(**self.attrib)
elif not self.is_leader:
2019-11-23 08:17:35 +01:00
option = OptionDescription(**self.attrib)
else:
option = Leadership(**self.attrib)
except Exception as err:
2020-07-07 18:12:16 +02:00
print(self.attrib)
raise CreoleLoaderError(_('cannot create optiondescription "{}": {}').format(self.attrib['name'], err))
2019-11-23 08:17:35 +01:00
for key, value in self.informations.items():
option.impl_set_information(key, value)
self.option = option
return self.option
2019-12-02 10:31:55 +01:00
def load(xmlroot: str,
dtd_path: str,
funcs_path: str):
tiramisu_objects = PopulateTiramisuObjects()
tiramisu_objects.parse_dtd(dtd_path)
tiramisu_objects.make_tiramisu_objects(xmlroot,
funcs_path)
return tiramisu_objects.storage.paths['.'].get()