from pytest import raises from lxml.etree import DTD from os.path import isfile from copy import copy from rougail import RougailConvert, RougailConfig from rougail.error import DictConsistencyError def parse_dtd_subelement(obj_name, content, default_required=False): if not content: return if content.type == 'pcdata': if obj_name in ['value', 'param']: type_ = 'any' else: type_ = 'str' yield {'name': 'text', 'type': type_, 'required': False} elif content.name: yield {'name': content.name, 'type': None, 'required': default_required} if content.left: if content.left.type == 'pcdata': # choice yield {'name': 'text', 'type': 'any', 'required': False} else: yield {'name': content.left.name, 'type': None, 'required': default_required} if content.right: if content.occur == 'once': if content.right and content.right.name: yield {'name': content.right.name, 'type': None, 'required': default_required} else: yield from parse_dtd_subelement(obj_name, content.right, default_required=False) elif content.occur == 'mult': yield from parse_dtd_subelement(obj_name, content.right, default_required=False) elif content.occur == 'plus': yield from parse_dtd_subelement(obj_name, content.right, default_required=False) else: raise Exception('pffff') def parse_dtd(elt_name, elts, space=0): elt = elts.get(elt_name) if not elt: return '' schema = '' if space: schema += " " * space + "type: seq\n" schema += " " * space + "sequence:\n" space = space + 2 schema += " " * space + "- type: map\n" space = space + 2 schema += " " * space + "mapping:\n" if elt_name == 'variables': # type subspace = space + 2 schema += " " * subspace + "variable:\n" schema += parse_dtd('variable', elts, subspace + 2) # variables if subspace < 50: schema += " " * subspace + "family:\n" schema += parse_dtd('family', elts, subspace + 2) subspace = subspace + 8 schema += " " * subspace + "variables:\n" schema += parse_dtd(elt_name, elts, subspace + 2) else: schema += " " * space + "type: map\n" schema += " " * space + "mapping:\n" space = space + 2 if elt_name != 'variables': if 'subelts' in elt: for subelt in elt['subelts']: schema += " " * space + subelt['name'] + ":\n" subspace = space + 2 schema += " " * subspace + 'required: ' + {False: 'false', True: 'true'}[subelt['required']] + "\n" if subelt['type']: schema += " " * subspace + 'type: ' + subelt['type'] + "\n" if subelt['name'] != 'family' or subspace < 26: schema += parse_dtd(subelt['name'], elts, subspace) attributes = {attr.name: attr for attr in elt['elt'].iterattributes()} for name, dtd_attr in attributes.items(): enum = list(dtd_attr.itervalues()) if set(enum) == {'True', 'False'} or set(enum) == {'True', 'False', 'nil'}: # it's a boolean type_ = 'bool' elif enum == ['number']: continue elif f'{name}_type' in attributes and list(attributes[f'{name}_type'].itervalues()) == ['number']: type_ = 'int' else: type_ = 'str' schema += " " * space + name + ':\n' subspace = space + 2 schema += " " * subspace + "type: " + type_ + '\n' if type_ != 'bool' and enum: enumspace = subspace + 2 schema += " " * subspace + 'enum:\n' for value in enum: schema += " " * enumspace + '- "' + value + '"\n' return schema def test_valid_dtd_schema_equal(): with open(RougailConfig['dtdfilename'], 'r') as dtdfd: dtd = DTD(dtdfd) elts = {} for dtd_elt in dtd.iterelements(): subelts = tuple(parse_dtd_subelement(dtd_elt.name, dtd_elt.content)) ret = {'elt': dtd_elt} if subelts: ret['subelts'] = subelts elts[dtd_elt.name] = ret schema = parse_dtd('rougail', elts) if not isfile(RougailConfig['yamlschema_filename']): with open(RougailConfig['yamlschema_filename'], 'w') as ymldf: ymldf.write(schema) with open(RougailConfig['yamlschema_filename'], 'r') as ymldf: ori_schema = ymldf.read() assert schema == ori_schema, f'DTD and YAML schema ({RougailConfig["yamlschema_filename"]}) are different' def test_no_dtd(): cfg = RougailConfig.copy() cfg['dtdfilename'] = 'notexists.dtd' with raises(IOError): eolobj = RougailConvert(cfg) def test_mode_invalid_default(): # default variable mode is not in modes_level RougailConfig['dictionaries_dir'] = ['tests/personalize_mode/dictionary'] RougailConfig['modes_level'] = ('level1', 'level2') eolobj = RougailConvert() with raises(DictConsistencyError) as err: eolobj.reflexion() assert err.value.errno == 72 def test_mode_invalid_default_family(): # default family mode is not in modes_level RougailConfig['dictionaries_dir'] = ['tests/personalize_mode/dictionary'] RougailConfig['modes_level'] = ('level1', 'level2') RougailConfig['default_variable_mode'] = 'level1' eolobj = RougailConvert() with raises(DictConsistencyError) as err: eolobj.reflexion() assert err.value.errno == 73 def test_personalize_mode(): RougailConfig['dictionaries_dir'] = ['tests/personalize_mode/dictionary'] RougailConfig['modes_level'] = ('level1', 'level2') RougailConfig['default_variable_mode'] = 'level1' RougailConfig['default_family_mode'] = 'level1' eolobj = RougailConvert() eolobj.save(None) def test_personalize_mode_unknown(): # a variable has an unknown mode RougailConfig['dictionaries_dir'] = ['tests/personalize_mode/dictionary'] RougailConfig['modes_level'] = ('level1',) RougailConfig['default_variable_mode'] = 'level1' RougailConfig['default_family_mode'] = 'level1' eolobj = RougailConvert() with raises(DictConsistencyError) as err: eolobj.reflexion() assert err.value.errno == 71