From 33cdeedca419dc7ec7a72b3e0c3e264b9386df63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tina=20M=C3=BCller?= Date: Tue, 23 Mar 2021 19:59:00 +0100 Subject: [PATCH] Add YAML 1.2 tags Example code: import yaml class MyCoreLoader(yaml.BaseLoader): pass class MyJSONLoader(yaml.BaseLoader): pass class MyCoreDumper(yaml.CommonDumper): pass class MyJSONDumper(yaml.CommonDumper): pass MyCoreLoader.init_tags('core') MyJSONLoader.init_tags('json') MyCoreDumper.init_tags('core') MyJSONDumper.init_tags('json') input = """ - TRUE - yes - ~ - true #- .inf #- 23 #- #empty #- !!str #empty #- 010 #- 0o10 #- 0b100 #- 0x20 #- -0x20 #- 1_000 #- 3:14 #- 0011 #- +0 #- 0001.23 #- !!str +0.3e3 #- +0.3e3 #- &x foo #- *x #- 1e27 #- 1x+27 """ print('--------------------------------------------- BaseLoader') data = yaml.load(input, Loader=yaml.BaseLoader) print(data) print('--------------------------------------------- SafeLoader') data = yaml.load(input, Loader=yaml.SafeLoader) print(data) print('--------------------------------------------- CoreLoader') data = yaml.load(input, Loader=MyCoreLoader) print(data) print('--------------------------------------------- JSONLoader') data = yaml.load(input, Loader=MyJSONLoader) print(data) print('--------------------------------------------- SafeDumper') out = yaml.dump(data, Dumper=yaml.SafeDumper) print(out) print('--------------------------------------------- MyCoreDumper') out = yaml.dump(data, Dumper=MyCoreDumper) print(out) print('--------------------------------------------- MyJSONDumper') out = yaml.dump(data, Dumper=MyJSONDumper) print(out) --- lib/yaml/constructor.py | 352 +++++++++++++++++++++--------------- lib/yaml/dumper.py | 25 ++- lib/yaml/loader.py | 8 +- lib/yaml/representer.py | 176 ++++++++++-------- lib/yaml/resolver.py | 116 +++++++----- tests/data/core.schema | 235 ++++++++++++++++++++++++ tests/data/core.schema-skip | 4 + tests/data/json.schema | 195 ++++++++++++++++++++ tests/data/json.schema-skip | 6 + tests/lib/test_schema.py | 40 +++- 10 files changed, 891 insertions(+), 266 deletions(-) create mode 100644 tests/data/core.schema create mode 100644 tests/data/core.schema-skip create mode 100644 tests/data/json.schema create mode 100644 tests/data/json.schema-skip diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py index 619acd30..db93140d 100644 --- a/lib/yaml/constructor.py +++ b/lib/yaml/constructor.py @@ -5,7 +5,7 @@ 'FullConstructor', 'UnsafeConstructor', 'Constructor', - 'ConstructorError' + 'ConstructorError', ] from .error import * @@ -18,6 +18,12 @@ class ConstructorError(MarkedYAMLError): class BaseConstructor: + inf_value = 1e300 + while inf_value != inf_value*inf_value: + inf_value *= inf_value + nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99). + + yaml_constructors = {} yaml_multi_constructors = {} @@ -168,55 +174,6 @@ def add_multi_constructor(cls, tag_prefix, multi_constructor): cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy() cls.yaml_multi_constructors[tag_prefix] = multi_constructor -class SafeConstructor(BaseConstructor): - - def construct_scalar(self, node): - if isinstance(node, MappingNode): - for key_node, value_node in node.value: - if key_node.tag == 'tag:yaml.org,2002:value': - return self.construct_scalar(value_node) - return super().construct_scalar(node) - - def flatten_mapping(self, node): - merge = [] - index = 0 - while index < len(node.value): - key_node, value_node = node.value[index] - if key_node.tag == 'tag:yaml.org,2002:merge': - del node.value[index] - if isinstance(value_node, MappingNode): - self.flatten_mapping(value_node) - merge.extend(value_node.value) - elif isinstance(value_node, SequenceNode): - submerge = [] - for subnode in value_node.value: - if not isinstance(subnode, MappingNode): - raise ConstructorError("while constructing a mapping", - node.start_mark, - "expected a mapping for merging, but found %s" - % subnode.id, subnode.start_mark) - self.flatten_mapping(subnode) - submerge.append(subnode.value) - submerge.reverse() - for value in submerge: - merge.extend(value) - else: - raise ConstructorError("while constructing a mapping", node.start_mark, - "expected a mapping or list of mappings for merging, but found %s" - % value_node.id, value_node.start_mark) - elif key_node.tag == 'tag:yaml.org,2002:value': - key_node.tag = 'tag:yaml.org,2002:str' - index += 1 - else: - index += 1 - if merge: - node.value = merge + node.value - - def construct_mapping(self, node, deep=False): - if isinstance(node, MappingNode): - self.flatten_mapping(node) - return super().construct_mapping(node, deep=deep) - def construct_yaml_null(self, node): self.construct_scalar(node) return None @@ -234,42 +191,45 @@ def construct_yaml_bool(self, node): value = self.construct_scalar(node) return self.bool_values[value.lower()] - def construct_yaml_int(self, node): + def construct_yaml_str(self, node): + return self.construct_scalar(node) + + def construct_yaml_seq(self, node): + data = [] + yield data + data.extend(self.construct_sequence(node)) + + def construct_yaml_map(self, node): + data = {} + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_undefined(self, node): + raise ConstructorError(None, None, + "could not determine a constructor for the tag %r" % node.tag, + node.start_mark) + + def construct_yaml_int_core(self, node): value = self.construct_scalar(node) - value = value.replace('_', '') sign = +1 if value[0] == '-': sign = -1 if value[0] in '+-': value = value[1:] + if value == '0': return 0 - elif value.startswith('0b'): - return sign*int(value[2:], 2) + elif value.startswith('0o'): + return sign*int(value[2:], 8) elif value.startswith('0x'): return sign*int(value[2:], 16) - elif value[0] == '0': - return sign*int(value, 8) - elif ':' in value: - digits = [int(part) for part in value.split(':')] - digits.reverse() - base = 1 - value = 0 - for digit in digits: - value += digit*base - base *= 60 - return sign*value else: return sign*int(value) - inf_value = 1e300 - while inf_value != inf_value*inf_value: - inf_value *= inf_value - nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99). - - def construct_yaml_float(self, node): + def construct_yaml_float_core(self, node): value = self.construct_scalar(node) - value = value.replace('_', '').lower() + value = value.lower() sign = +1 if value[0] == '-': sign = -1 @@ -279,18 +239,48 @@ def construct_yaml_float(self, node): return sign*self.inf_value elif value == '.nan': return self.nan_value - elif ':' in value: - digits = [float(part) for part in value.split(':')] - digits.reverse() - base = 1 - value = 0.0 - for digit in digits: - value += digit*base - base *= 60 - return sign*value else: return sign*float(value) + def construct_yaml_int_json(self, node): + value = self.construct_scalar(node) + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + + if value == '0': + return 0 + else: + return sign*int(value) + + def construct_yaml_float_json(self, node): + value = self.construct_scalar(node) + value = value.lower() + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + return sign*float(value) + + + @classmethod + def init_constructors(cls, tagset): + if tagset not in _constructors: + return + for key in _constructors[tagset]: + callback = _constructors[tagset][key] + if (key is None): + cls.add_constructor(key, callback) + else: + cls.add_constructor('tag:yaml.org,2002:' + key, callback) + + +# SafeConstructor implements YAML 1.1 +class SafeConstructor(BaseConstructor): + def construct_yaml_binary(self, node): try: value = self.construct_scalar(node).encode('ascii') @@ -399,19 +389,104 @@ def construct_yaml_set(self, node): value = self.construct_mapping(node) data.update(value) - def construct_yaml_str(self, node): - return self.construct_scalar(node) + def construct_scalar(self, node): + if isinstance(node, MappingNode): + for key_node, value_node in node.value: + if key_node.tag == 'tag:yaml.org,2002:value': + return self.construct_scalar(value_node) + return super().construct_scalar(node) - def construct_yaml_seq(self, node): - data = [] - yield data - data.extend(self.construct_sequence(node)) + def flatten_mapping(self, node): + merge = [] + index = 0 + while index < len(node.value): + key_node, value_node = node.value[index] + if key_node.tag == 'tag:yaml.org,2002:merge': + del node.value[index] + if isinstance(value_node, MappingNode): + self.flatten_mapping(value_node) + merge.extend(value_node.value) + elif isinstance(value_node, SequenceNode): + submerge = [] + for subnode in value_node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing a mapping", + node.start_mark, + "expected a mapping for merging, but found %s" + % subnode.id, subnode.start_mark) + self.flatten_mapping(subnode) + submerge.append(subnode.value) + submerge.reverse() + for value in submerge: + merge.extend(value) + else: + raise ConstructorError("while constructing a mapping", node.start_mark, + "expected a mapping or list of mappings for merging, but found %s" + % value_node.id, value_node.start_mark) + elif key_node.tag == 'tag:yaml.org,2002:value': + key_node.tag = 'tag:yaml.org,2002:str' + index += 1 + else: + index += 1 + if merge: + node.value = merge + node.value - def construct_yaml_map(self, node): - data = {} - yield data - value = self.construct_mapping(node) - data.update(value) + def construct_yaml_int(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '') + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '0': + return 0 + elif value.startswith('0b'): + return sign*int(value[2:], 2) + elif value.startswith('0x'): + return sign*int(value[2:], 16) + elif value[0] == '0': + return sign*int(value, 8) + elif ':' in value: + digits = [int(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*int(value) + + def construct_yaml_float(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '').lower() + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '.inf': + return sign*self.inf_value + elif value == '.nan': + return self.nan_value + elif ':' in value: + digits = [float(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0.0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*float(value) + + def construct_mapping(self, node, deep=False): + if isinstance(node, MappingNode): + self.flatten_mapping(node) + return super().construct_mapping(node, deep=deep) def construct_yaml_object(self, node, cls): data = cls.__new__(cls) @@ -423,61 +498,46 @@ def construct_yaml_object(self, node, cls): state = self.construct_mapping(node) data.__dict__.update(state) - def construct_undefined(self, node): - raise ConstructorError(None, None, - "could not determine a constructor for the tag %r" % node.tag, - node.start_mark) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:null', - SafeConstructor.construct_yaml_null) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:bool', - SafeConstructor.construct_yaml_bool) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:int', - SafeConstructor.construct_yaml_int) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:float', - SafeConstructor.construct_yaml_float) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:binary', - SafeConstructor.construct_yaml_binary) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:timestamp', - SafeConstructor.construct_yaml_timestamp) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:omap', - SafeConstructor.construct_yaml_omap) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:pairs', - SafeConstructor.construct_yaml_pairs) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:set', - SafeConstructor.construct_yaml_set) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:str', - SafeConstructor.construct_yaml_str) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:seq', - SafeConstructor.construct_yaml_seq) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:map', - SafeConstructor.construct_yaml_map) -SafeConstructor.add_constructor(None, - SafeConstructor.construct_undefined) +_constructors = { + 'yaml11': { + 'str': BaseConstructor.construct_yaml_str, + 'seq': BaseConstructor.construct_yaml_seq, + 'map': BaseConstructor.construct_yaml_map, + None: BaseConstructor.construct_undefined, + 'int': SafeConstructor.construct_yaml_int, + 'float': SafeConstructor.construct_yaml_float, + 'null': BaseConstructor.construct_yaml_null, + 'bool': BaseConstructor.construct_yaml_bool, + 'binary': SafeConstructor.construct_yaml_binary, + 'timestamp': SafeConstructor.construct_yaml_timestamp, + 'omap': SafeConstructor.construct_yaml_omap, + 'pairs': SafeConstructor.construct_yaml_pairs, + 'set': SafeConstructor.construct_yaml_set, + }, + 'core': { + 'str': BaseConstructor.construct_yaml_str, + 'seq': BaseConstructor.construct_yaml_seq, + 'map': BaseConstructor.construct_yaml_map, + None: BaseConstructor.construct_undefined, + 'int': BaseConstructor.construct_yaml_int_core, + 'float': BaseConstructor.construct_yaml_float_core, + 'null': BaseConstructor.construct_yaml_null, + 'bool': BaseConstructor.construct_yaml_bool, + }, + 'json': { + 'str': BaseConstructor.construct_yaml_str, + 'seq': BaseConstructor.construct_yaml_seq, + 'map': BaseConstructor.construct_yaml_map, + None: BaseConstructor.construct_undefined, + 'int': BaseConstructor.construct_yaml_int_json, + 'float': BaseConstructor.construct_yaml_float_json, + 'null': BaseConstructor.construct_yaml_null, + 'bool': BaseConstructor.construct_yaml_bool, + }, +} + +SafeConstructor.init_constructors('yaml11') class FullConstructor(SafeConstructor): # 'extend' is blacklisted because it is used by diff --git a/lib/yaml/dumper.py b/lib/yaml/dumper.py index 6aadba55..e78e749e 100644 --- a/lib/yaml/dumper.py +++ b/lib/yaml/dumper.py @@ -1,5 +1,5 @@ -__all__ = ['BaseDumper', 'SafeDumper', 'Dumper'] +__all__ = ['BaseDumper', 'SafeDumper', 'Dumper', 'CommonDumper'] from .emitter import * from .serializer import * @@ -42,6 +42,29 @@ def __init__(self, stream, default_flow_style=default_flow_style, sort_keys=sort_keys) Resolver.__init__(self) +class CommonDumper(Emitter, Serializer, CommonRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + CommonRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + BaseResolver.__init__(self) + + @classmethod + def init_tags(cls, tagset): + cls.init_representers(tagset) + cls.init_resolvers(tagset) + class Dumper(Emitter, Serializer, Representer, Resolver): def __init__(self, stream, diff --git a/lib/yaml/loader.py b/lib/yaml/loader.py index e90c1122..8e2dc6f9 100644 --- a/lib/yaml/loader.py +++ b/lib/yaml/loader.py @@ -1,5 +1,6 @@ -__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader'] +__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader', + ] from .reader import * from .scanner import * @@ -18,6 +19,11 @@ def __init__(self, stream): BaseConstructor.__init__(self) BaseResolver.__init__(self) + @classmethod + def init_tags(cls, tagset): + cls.init_constructors(tagset) + cls.init_resolvers(tagset) + class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver): def __init__(self, stream): diff --git a/lib/yaml/representer.py b/lib/yaml/representer.py index 808ca06d..aa9360d7 100644 --- a/lib/yaml/representer.py +++ b/lib/yaml/representer.py @@ -1,6 +1,6 @@ __all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer', - 'RepresenterError'] + 'RepresenterError', 'CommonRepresenter'] from .error import * from .nodes import * @@ -131,29 +131,30 @@ def represent_mapping(self, tag, mapping, flow_style=None): def ignore_aliases(self, data): return False -class SafeRepresenter(BaseRepresenter): + def represent_str(self, data): + return self.represent_scalar('tag:yaml.org,2002:str', data) - def ignore_aliases(self, data): - if data is None: - return True - if isinstance(data, tuple) and data == (): - return True - if isinstance(data, (str, bytes, bool, int, float)): - return True + def represent_list(self, data): + #pairs = (len(data) > 0 and isinstance(data, list)) + #if pairs: + # for item in data: + # if not isinstance(item, tuple) or len(item) != 2: + # pairs = False + # break + #if not pairs: + return self.represent_sequence('tag:yaml.org,2002:seq', data) + #value = [] + #for item_key, item_value in data: + # value.append(self.represent_mapping(u'tag:yaml.org,2002:map', + # [(item_key, item_value)])) + #return SequenceNode(u'tag:yaml.org,2002:pairs', value) + + def represent_dict(self, data): + return self.represent_mapping('tag:yaml.org,2002:map', data) def represent_none(self, data): return self.represent_scalar('tag:yaml.org,2002:null', 'null') - def represent_str(self, data): - return self.represent_scalar('tag:yaml.org,2002:str', data) - - def represent_binary(self, data): - if hasattr(base64, 'encodebytes'): - data = base64.encodebytes(data).decode('ascii') - else: - data = base64.encodestring(data).decode('ascii') - return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|') - def represent_bool(self, data): if data: value = 'true' @@ -184,27 +185,17 @@ def represent_float(self, data): # Unfortunately, this is not a valid float representation according # to the definition of the `!!float` tag. We fix this by adding # '.0' before the 'e' symbol. + # TODO (In YAML 1.2 Core, 1e17 would be a valid float though) if '.' not in value and 'e' in value: value = value.replace('e', '.0e', 1) return self.represent_scalar('tag:yaml.org,2002:float', value) - def represent_list(self, data): - #pairs = (len(data) > 0 and isinstance(data, list)) - #if pairs: - # for item in data: - # if not isinstance(item, tuple) or len(item) != 2: - # pairs = False - # break - #if not pairs: - return self.represent_sequence('tag:yaml.org,2002:seq', data) - #value = [] - #for item_key, item_value in data: - # value.append(self.represent_mapping(u'tag:yaml.org,2002:map', - # [(item_key, item_value)])) - #return SequenceNode(u'tag:yaml.org,2002:pairs', value) - - def represent_dict(self, data): - return self.represent_mapping('tag:yaml.org,2002:map', data) + def represent_binary(self, data): + if hasattr(base64, 'encodebytes'): + data = base64.encodebytes(data).decode('ascii') + else: + data = base64.encodestring(data).decode('ascii') + return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|') def represent_set(self, data): value = {} @@ -220,54 +211,91 @@ def represent_datetime(self, data): value = data.isoformat(' ') return self.represent_scalar('tag:yaml.org,2002:timestamp', value) - def represent_yaml_object(self, tag, data, cls, flow_style=None): - if hasattr(data, '__getstate__'): - state = data.__getstate__() - else: - state = data.__dict__.copy() - return self.represent_mapping(tag, state, flow_style=flow_style) - def represent_undefined(self, data): raise RepresenterError("cannot represent an object", data) -SafeRepresenter.add_representer(type(None), - SafeRepresenter.represent_none) - -SafeRepresenter.add_representer(str, - SafeRepresenter.represent_str) - -SafeRepresenter.add_representer(bytes, - SafeRepresenter.represent_binary) - -SafeRepresenter.add_representer(bool, - SafeRepresenter.represent_bool) - -SafeRepresenter.add_representer(int, - SafeRepresenter.represent_int) - -SafeRepresenter.add_representer(float, - SafeRepresenter.represent_float) + @classmethod + def init_representers(cls, name): + for key in _representers[name]: + callback = _representers[name][key] + cls.add_representer(key, callback) -SafeRepresenter.add_representer(list, - SafeRepresenter.represent_list) +class CommonRepresenter(BaseRepresenter): -SafeRepresenter.add_representer(tuple, - SafeRepresenter.represent_list) + def ignore_aliases(self, data): + if data is None: + return True + if isinstance(data, tuple) and data == (): + return True + if isinstance(data, (str, bytes, bool, int, float)): + return True -SafeRepresenter.add_representer(dict, - SafeRepresenter.represent_dict) + def represent_yaml_object(self, tag, data, cls, flow_style=None): + if hasattr(data, '__getstate__'): + state = data.__getstate__() + else: + state = data.__dict__.copy() + return self.represent_mapping(tag, state, flow_style=flow_style) -SafeRepresenter.add_representer(set, - SafeRepresenter.represent_set) +class SafeRepresenter(BaseRepresenter): -SafeRepresenter.add_representer(datetime.date, - SafeRepresenter.represent_date) + def ignore_aliases(self, data): + if data is None: + return True + if isinstance(data, tuple) and data == (): + return True + if isinstance(data, (str, bytes, bool, int, float)): + return True -SafeRepresenter.add_representer(datetime.datetime, - SafeRepresenter.represent_datetime) + def represent_yaml_object(self, tag, data, cls, flow_style=None): + if hasattr(data, '__getstate__'): + state = data.__getstate__() + else: + state = data.__dict__.copy() + return self.represent_mapping(tag, state, flow_style=flow_style) -SafeRepresenter.add_representer(None, - SafeRepresenter.represent_undefined) +_representers = { + 'yaml11': { + bool: BaseRepresenter.represent_bool, + dict: BaseRepresenter.represent_dict, + list: BaseRepresenter.represent_list, + tuple: BaseRepresenter.represent_list, + str: BaseRepresenter.represent_str, + float: BaseRepresenter.represent_float, + int: BaseRepresenter.represent_int, + type(None): BaseRepresenter.represent_none, + bytes: BaseRepresenter.represent_binary, + set: BaseRepresenter.represent_set, + datetime.date: BaseRepresenter.represent_date, + datetime.datetime: BaseRepresenter.represent_datetime, + None: BaseRepresenter.represent_undefined, + }, + 'core': { + bool: BaseRepresenter.represent_bool, + dict: BaseRepresenter.represent_dict, + list: BaseRepresenter.represent_list, + tuple: BaseRepresenter.represent_list, + str: BaseRepresenter.represent_str, + float: BaseRepresenter.represent_float, + int: BaseRepresenter.represent_int, + type(None): BaseRepresenter.represent_none, + None: BaseRepresenter.represent_undefined, + }, + 'json': { + bool: BaseRepresenter.represent_bool, + dict: BaseRepresenter.represent_dict, + list: BaseRepresenter.represent_list, + tuple: BaseRepresenter.represent_list, + str: BaseRepresenter.represent_str, + float: BaseRepresenter.represent_float, + int: BaseRepresenter.represent_int, + type(None): BaseRepresenter.represent_none, + None: BaseRepresenter.represent_undefined, + }, +} + + +SafeRepresenter.init_representers('yaml11') class Representer(SafeRepresenter): diff --git a/lib/yaml/resolver.py b/lib/yaml/resolver.py index 013896d2..11248ca6 100644 --- a/lib/yaml/resolver.py +++ b/lib/yaml/resolver.py @@ -1,5 +1,5 @@ -__all__ = ['BaseResolver', 'Resolver'] +__all__ = ['BaseResolver', 'Resolver' ] from .error import * from .nodes import * @@ -164,64 +164,96 @@ def resolve(self, kind, value, implicit): elif kind is MappingNode: return self.DEFAULT_MAPPING_TAG -class Resolver(BaseResolver): - pass + @classmethod + def init_resolvers(cls, key): + for args in _resolvers[key]: + cls.add_implicit_resolver( + 'tag:yaml.org,2002:' + args[0], + args[1], args[2] + ) -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:bool', - re.compile(r'''^(?:yes|Yes|YES|no|No|NO +_resolvers = { + 'yaml11': [ + ['bool', + re.compile(r'''^(?:yes|Yes|YES|no|No|NO |true|True|TRUE|false|False|FALSE |on|On|ON|off|Off|OFF)$''', re.X), - list('yYnNtTfFoO')) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:float', - re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? + list('yYnNtTfFoO')], + ['float', + re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? |\.[0-9_]+(?:[eE][-+][0-9]+)? |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]* |[-+]?\.(?:inf|Inf|INF) |\.(?:nan|NaN|NAN))$''', re.X), - list('-+0123456789.')) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:int', - re.compile(r'''^(?:[-+]?0b[0-1_]+ + list('-+0123456789.')], + ['int', + re.compile(r'''^(?:[-+]?0b[0-1_]+ |[-+]?0[0-7_]+ |[-+]?(?:0|[1-9][0-9_]*) |[-+]?0x[0-9a-fA-F_]+ |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), - list('-+0123456789')) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:merge', - re.compile(r'^(?:<<)$'), - ['<']) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:null', - re.compile(r'''^(?: ~ + list('-+0123456789')], + ['merge', + re.compile(r'^(?:<<)$'), + ['<']], + ['null', + re.compile(r'''^(?: ~ |null|Null|NULL | )$''', re.X), - ['~', 'n', 'N', '']) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:timestamp', - re.compile(r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] + ['~', 'n', 'N', '']], + ['timestamp', + re.compile(r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]? (?:[Tt]|[ \t]+)[0-9][0-9]? :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)? (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X), - list('0123456789')) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:value', - re.compile(r'^(?:=)$'), - ['=']) - + list('0123456789')], + ['value', + re.compile(r'^(?:=)$'), + ['=']], # The following resolver is only for documentation purposes. It cannot work # because plain scalars cannot start with '!', '&', or '*'. -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:yaml', - re.compile(r'^(?:!|&|\*)$'), - list('!&*')) + ['yaml', + re.compile(r'^(?:!|&|\*)$'), + list('!&*')], + ], + 'core': [ + ['bool', + re.compile(r'''^(?:|true|True|TRUE|false|False|FALSE)$''', re.X), + list('tTfF')], + ['int', + re.compile(r'''^(?: + |0o[0-7]+ + |[-+]?(?:[0-9]+) + |0x[0-9a-fA-F]+ + )$''', re.X), + list('-+0123456789')], + ['float', + re.compile(r'''^(?:[-+]?(?:\.[0-9]+|[0-9]+(\.[0-9]*)?)(?:[eE][-+]?[0-9]+)? + |[-+]?\.(?:inf|Inf|INF) + |\.(?:nan|NaN|NAN))$''', re.X), + list('-+0123456789.')], + ['null', + re.compile(r'''^(?:~||null|Null|NULL)$''', re.X), + ['~', 'n', 'N', '']], + ], + 'json': [ + ['bool', + re.compile(r'''^(?:true|false)$''', re.X), + list('tf')], + ['int', + re.compile(r'''^-?(?:0|[1-9][0-9]*)$''', re.X), + list('-0123456789')], + ['float', + re.compile(r'''^-?(?:0|[1-9][0-9]*)(\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$''', re.X), + list('-0123456789.')], + ['null', + re.compile(r'''^null$''', re.X), + ['n']], + ], +} + +class Resolver(BaseResolver): pass + +Resolver.init_resolvers('yaml11') diff --git a/tests/data/core.schema b/tests/data/core.schema new file mode 100644 index 00000000..f830a277 --- /dev/null +++ b/tests/data/core.schema @@ -0,0 +1,235 @@ +# https://github.com/perlpunk/yaml-test-schema/blob/master/data/schema-core.yaml +--- +'!!bool FALSE': ['bool', 'false()', 'false'] +'!!bool False': ['bool', 'false()', 'false'] +'!!bool TRUE': ['bool', 'true()', 'true'] +'!!bool True': ['bool', 'true()', 'true'] +'!!bool false': ['bool', 'false()', 'false'] +'!!bool true': ['bool', 'true()', 'true'] +'!!float +.INF': ['inf', 'inf()', '.inf'] +'!!float +.Inf': ['inf', 'inf()', '.inf'] +'!!float +.inf': ['inf', 'inf()', '.inf'] +'!!float +0.3e+3': ['float', '300.0', '300.0'] +'!!float +0.3e3': ['float', '300.0', '300.0'] +'!!float -.INF': ['inf', 'inf-neg()', '-.inf'] +'!!float -.Inf': ['inf', 'inf-neg()', '-.inf'] +'!!float -.inf': ['inf', 'inf-neg()', '-.inf'] +'!!float -3.14': ['float', '-3.14', '-3.14'] +'!!float .0': ['float', '0.0', '0.0'] +'!!float .14': ['float', '0.14', '0.14'] +'!!float .3E-1': ['float', '0.03', '0.03'] +'!!float .3e+3': ['float', '300.0', '300.0'] +'!!float .3e3': ['float', '300.0', '300.0'] +'!!float .INF': ['inf', 'inf()', '.inf'] +'!!float .Inf': ['inf', 'inf()', '.inf'] +'!!float .NAN': ['nan', 'nan()', '.nan'] +'!!float .NaN': ['nan', 'nan()', '.nan'] +'!!float .inf': ['inf', 'inf()', '.inf'] +'!!float .nan': ['nan', 'nan()', '.nan'] +'!!float 0.0': ['float', '0.0', '0.0'] +'!!float 0.3e3': ['float', '300.0', '300.0'] +'!!float 001.23': ['float', '1.23', '1.23'] +'!!float 3.': ['float', '3.0', '3.0'] +'!!float 3.14': ['float', '3.14', '3.14'] +'!!float 3.3e+3': ['float', '3300.0', '3300.0'] +'!!int +0': ['int', '0', '0'] +'!!int +23': ['int', '23', '23'] +'!!int -0': ['int', '0', '0'] +'!!int -23': ['int', '-23', '-23'] +'!!int 0': ['int', '0', '0'] +'!!int 0011': ['int', '11', '11'] +'!!int 07': ['int', '7', '7'] +'!!int 0o0': ['int', '0', '0'] +'!!int 0o10': ['int', '8', '8'] +'!!int 0o7': ['int', '7', '7'] +'!!int 0x0': ['int', '0', '0'] +'!!int 0x10': ['int', '16', '16'] +'!!int 0x42': ['int', '66', '66'] +'!!int 0xa': ['int', '10', '10'] +'!!int 23': ['int', '23', '23'] +'!!null #empty': ['null', 'null()', "null"] +'!!null NULL': ['null', 'null()', "null"] +'!!null Null': ['null', 'null()', "null"] +'!!null null': ['null', 'null()', 'null'] +'!!null ~': ['null', 'null()', 'null'] +'!!str #empty': ['str', '', "''"] +'!!str +.INF': ['str', '+.INF', "'+.INF'"] +'!!str +.Inf': ['str', '+.Inf', "'+.Inf'"] +'!!str +.inf': ['str', '+.inf', "'+.inf'"] +'!!str +0': ['str', '+0', "'+0'"] +'!!str +0.3e+3': ['str', '+0.3e+3', "'+0.3e+3'"] +'!!str +0.3e3': ['str', '+0.3e3', "'+0.3e3'"] +'!!str +0100_200': ['str', '+0100_200', "+0100_200"] +'!!str +0b100': ['str', '+0b100', "+0b100"] +'!!str +190:20:30': ['str', '+190:20:30', "+190:20:30"] +'!!str +23': ['str', '+23', "'+23'"] +'!!str -.INF': ['str', '-.INF', "'-.INF'"] +'!!str -.Inf': ['str', '-.Inf', "'-.Inf'"] +'!!str -.inf': ['str', '-.inf', "'-.inf'"] +'!!str -0': ['str', '-0', "'-0'"] +'!!str -0100_200': ['str', '-0100_200', "-0100_200"] +'!!str -0b101': ['str', '-0b101', "-0b101"] +'!!str -0x30': ['str', '-0x30', "-0x30"] +'!!str -190:20:30': ['str', '-190:20:30', "-190:20:30"] +'!!str -23': ['str', '-23', "'-23'"] +'!!str -3.14': ['str', '-3.14', "'-3.14'"] +'!!str .': ['str', '.', '.'] +'!!str .0': ['str', '.0', "'.0'"] +'!!str .14': ['str', '.14', "'.14'"] +'!!str .1_4': ['str', '.1_4', '.1_4'] +'!!str .3E-1': ['str', '.3E-1', "'.3E-1'"] +'!!str .3e+3': ['str', '.3e+3', "'.3e+3'"] +'!!str .3e3': ['str', '.3e3', "'.3e3'"] +'!!str .INF': ['str', '.INF', "'.INF'"] +'!!str .Inf': ['str', '.Inf', "'.Inf'"] +'!!str .NAN': ['str', '.NAN', "'.NAN'"] +'!!str .NaN': ['str', '.NaN', "'.NaN'"] +'!!str ._': ['str', '._', '._'] +'!!str ._14': ['str', '._14', '._14'] +'!!str .inf': ['str', '.inf', "'.inf'"] +'!!str .nan': ['str', '.nan', "'.nan'"] +'!!str 0': ['str', '0', "'0'"] +'!!str 0.0': ['str', '0.0', "'0.0'"] +'!!str 0.3e3': ['str', '0.3e3', "'0.3e3'"] +'!!str 00': ['str', '00', "'00'"] +'!!str 001.23': ['str', '001.23', "'001.23'"] +'!!str 0011': ['str', '0011', "'0011'"] +'!!str 010': ['str', '010', "'010'"] +'!!str 02_0': ['str', '02_0', "02_0"] +'!!str 07': ['str', '07', "'07'"] +'!!str 0b0': ['str', '0b0', "0b0"] +'!!str 0b100_101': ['str', '0b100_101', "0b100_101"] +'!!str 0o0': ['str', '0o0', "'0o0'"] +'!!str 0o10': ['str', '0o10', "'0o10'"] +'!!str 0o7': ['str', '0o7', "'0o7'"] +'!!str 0x0': ['str', '0x0', "'0x0'"] +'!!str 0x2_0': ['str', '0x2_0', "0x2_0"] +'!!str 0xa': ['str', '0xa', "'0xa'"] +'!!str 100_000': ['str', '100_000', "100_000"] +'!!str 190:20:30': ['str', '190:20:30', "190:20:30"] +'!!str 190:20:30.15': ['str', '190:20:30.15', "190:20:30.15"] +'!!str 23': ['str', '23', "'23'"] +'!!str 3.': ['str', '3.', "'3.'"] +'!!str 3.14': ['str', '3.14', "'3.14'"] +'!!str 3.3e+3': ['str', '3.3e+3', "'3.3e+3'"] +'!!str 85.230_15e+03': ['str', '85.230_15e+03', "85.230_15e+03"] +'!!str 85_230.15': ['str', '85_230.15', "85_230.15"] +'!!str FALSE': ['str', 'FALSE', "'FALSE'"] +'!!str False': ['str', 'False', "'False'"] +'!!str N': ['str', 'N', "N"] +'!!str NO': ['str', 'NO', "NO"] +'!!str NULL': ['str', 'NULL', "'NULL'"] +'!!str Null': ['str', 'Null', "'Null'"] +'!!str OFF': ['str', 'OFF', "OFF"] +'!!str ON': ['str', 'ON', "ON"] +'!!str Off': ['str', 'Off', "Off"] +'!!str On': ['str', 'On', "On"] +'!!str TRUE': ['str', 'TRUE', "'TRUE'"] +'!!str True': ['str', 'True', "'True'"] +'!!str Y': ['str', 'Y', "Y"] +'!!str YES': ['str', 'YES', "YES"] +'!!str Yes': ['str', 'Yes', "Yes"] +'!!str _._': ['str', '_._', '_._'] +'!!str false': ['str', 'false', "'false'"] +'!!str n': ['str', 'n', "n"] +'!!str no': ['str', 'no', "no"] +'!!str null': ['str', 'null', "'null'"] +'!!str off': ['str', 'off', "off"] +'!!str on': ['str', 'on', "on"] +'!!str true': ['str', 'true', "'true'"] +'!!str y': ['str', 'y', "y"] +'!!str yes': ['str', 'yes', "yes"] +'!!str ~': ['str', '~', "'~'"] +'#empty': ['null', 'null()', "null"] +'+.INF': ['inf', 'inf()', '.inf'] +'+.Inf': ['inf', 'inf()', '.inf'] +'+.inf': ['inf', 'inf()', '.inf'] +'+0': ['int', '0', '0'] +'+0.3e+3': ['float', '300.0', '300.0'] +'+0.3e3': ['float', '300.0', '300.0'] +'+0100_200': ['str', '+0100_200', '+0100_200'] +'+0b100': ['str', '+0b100', '+0b100'] +'+190:20:30': ['str', '+190:20:30', '+190:20:30'] +'+23': ['int', '23', '23'] +'+3.14': ['float', '3.14', '3.14'] +'-.INF': ['inf', 'inf-neg()', '-.inf'] +'-.Inf': ['inf', 'inf-neg()', '-.inf'] +'-.inf': ['inf', 'inf-neg()', '-.inf'] +'-0': ['int', '0', '0'] +'-0100_200': ['str', '-0100_200', '-0100_200'] +'-0b101': ['str', '-0b101', '-0b101'] +'-0x30': ['str', '-0x30', '-0x30'] +'-190:20:30': ['str', '-190:20:30', '-190:20:30'] +'-23': ['int', '-23', '-23'] +'-3.14': ['float', '-3.14', '-3.14'] +'.': ['str', '.', '.'] +'.0': ['float', '0.0', '0.0'] +'.14': ['float', '0.14', '0.14'] +'.1_4': ['str', '.1_4', '.1_4'] +'.3E-1': ['float', '0.03', '0.03'] +'.3e+3': ['float', '300.0', '300.0'] +'.3e3': ['float', '300.0', '300.0'] +'.INF': ['inf', 'inf()', '.inf'] +'.Inf': ['inf', 'inf()', '.inf'] +'.NAN': ['nan', 'nan()', '.nan'] +'.NaN': ['nan', 'nan()', '.nan'] +'._': ['str', '._', '._'] +'._14': ['str', '._14', '._14'] +'.inf': ['inf', 'inf()', '.inf'] +'.nan': ['nan', 'nan()', '.nan'] +'0': ['int', '0', '0'] +'0.0': ['float', '0.0', '0.0'] +'0.3e3': ['float', '300.0', '300.0'] +'00': ['int', '0', '0'] +'001.23': ['float', '1.23', '1.23'] +'0011': ['int', '11', '11'] +'010': ['int', '10', '10'] +'02_0': ['str', '02_0', '02_0'] +'07': ['int', '7', '7'] +'08': ['int', '8', '8'] +'0b0': ['str', '0b0', '0b0'] +'0b100_101': ['str', '0b100_101', '0b100_101'] +'0o0': ['int', '0', '0'] +'0o10': ['int', '8', '8'] +'0o7': ['int', '7', '7'] +'0x0': ['int', '0', '0'] +'0x10': ['int', '16', '16'] +'0x2_0': ['str', '0x2_0', '0x2_0'] +'0x42': ['int', '66', '66'] +'0xa': ['int', '10', '10'] +'100_000': ['str', '100_000', '100_000'] +'190:20:30': ['str', '190:20:30', '190:20:30'] +'190:20:30.15': ['str', '190:20:30.15', '190:20:30.15'] +'23': ['int', '23', '23'] +'3.': ['float', '3.0', '3.0'] +'3.14': ['float', '3.14', '3.14'] +'3.3e+3': ['float', '3300', '3300.0'] +'3e3': ['float', '3000', '3000.0'] +'85.230_15e+03': ['str', '85.230_15e+03', '85.230_15e+03'] +'85_230.15': ['str', '85_230.15', '85_230.15'] +'FALSE': ['bool', 'false()', 'false'] +'False': ['bool', 'false()', 'false'] +'N': ['str', 'N', "N"] +'NO': ['str', 'NO', "NO"] +'NULL': ['null', 'null()', "null"] +'Null': ['null', 'null()', "null"] +'OFF': ['str', 'OFF', "OFF"] +'ON': ['str', 'ON', "ON"] +'Off': ['str', 'Off', "Off"] +'On': ['str', 'On', "On"] +'TRUE': ['bool', 'true()', 'true'] +'True': ['bool', 'true()', 'true'] +'Y': ['str', 'Y', "Y"] +'YES': ['str', 'YES', "YES"] +'Yes': ['str', 'Yes', "Yes"] +'_._': ['str', '_._', '_._'] +'false': ['bool', 'false()', 'false'] +'n': ['str', 'n', "n"] +'no': ['str', 'no', "no"] +'null': ['null', 'null()', "null"] +'off': ['str', 'off', "off"] +'on': ['str', 'on', "on"] +'true': ['bool', 'true()', 'true'] +'y': ['str', 'y', "y"] +'yes': ['str', 'yes', "yes"] +'~': ['null', 'null()', "null"] diff --git a/tests/data/core.schema-skip b/tests/data/core.schema-skip new file mode 100644 index 00000000..d412d083 --- /dev/null +++ b/tests/data/core.schema-skip @@ -0,0 +1,4 @@ +load: { + } +dump: { + } diff --git a/tests/data/json.schema b/tests/data/json.schema new file mode 100644 index 00000000..676f2909 --- /dev/null +++ b/tests/data/json.schema @@ -0,0 +1,195 @@ +# https://github.com/perlpunk/yaml-test-schema/blob/master/data/schema-json.yaml +--- +'!!bool false': ['bool', 'false()', 'false'] +'!!bool true': ['bool', 'true()', 'true'] +'!!float -3.14': ['float', '-3.14', '-3.14'] +'!!float 0.0': ['float', '0.0', '0.0'] +'!!float 0.3e3': ['float', '300.0', '300.0'] +'!!float 3.': ['float', '3.0', '3.0'] +'!!float 3.14': ['float', '3.14', '3.14'] +'!!int -0': ['int', '0', '0'] +'!!int -23': ['int', '-23', '-23'] +'!!int 0': ['int', '0', '0'] +'!!int 23': ['int', '23', '23'] +'!!null null': ['null', 'null()', 'null'] +'!!str #empty': ['str', '', "''"] +'!!str +.INF': ['str', '+.INF', "+.INF"] +'!!str +.Inf': ['str', '+.Inf', "+.Inf"] +'!!str +.inf': ['str', '+.inf', "+.inf"] +'!!str +0': ['str', '+0', "+0"] +'!!str +0.3e+3': ['str', '+0.3e+3', "+0.3e+3"] +'!!str +0.3e3': ['str', '+0.3e3', "+0.3e3"] +'!!str +0100_200': ['str', '+0100_200', "+0100_200"] +'!!str +0b100': ['str', '+0b100', "+0b100"] +'!!str +190:20:30': ['str', '+190:20:30', "+190:20:30"] +'!!str +23': ['str', '+23', "+23"] +'!!str -.INF': ['str', '-.INF', "-.INF"] +'!!str -.Inf': ['str', '-.Inf', "-.Inf"] +'!!str -.inf': ['str', '-.inf', "-.inf"] +'!!str -0': ['str', '-0', "'-0'"] +'!!str -0100_200': ['str', '-0100_200', "-0100_200"] +'!!str -0b101': ['str', '-0b101', "-0b101"] +'!!str -0x30': ['str', '-0x30', "-0x30"] +'!!str -190:20:30': ['str', '-190:20:30', "-190:20:30"] +'!!str -23': ['str', '-23', "'-23'"] +'!!str -3.14': ['str', '-3.14', "'-3.14'"] +'!!str .': ['str', '.', '.'] +'!!str .0': ['str', '.0', ".0"] +'!!str .14': ['str', '.14', '.14'] +'!!str .1_4': ['str', '.1_4', '.1_4'] +'!!str .3E-1': ['str', '.3E-1', ".3E-1"] +'!!str .3e+3': ['str', '.3e+3', ".3e+3"] +'!!str .3e3': ['str', '.3e3', ".3e3"] +'!!str .INF': ['str', '.INF', ".INF"] +'!!str .Inf': ['str', '.Inf', ".Inf"] +'!!str .NAN': ['str', '.NAN', ".NAN"] +'!!str .NaN': ['str', '.NaN', ".NaN"] +'!!str ._': ['str', '._', '._'] +'!!str ._14': ['str', '._14', '._14'] +'!!str .inf': ['str', '.inf', ".inf"] +'!!str .nan': ['str', '.nan', ".nan"] +'!!str 0': ['str', '0', "'0'"] +'!!str 0.0': ['str', '0.0', "'0.0'"] +'!!str 0.3e3': ['str', '0.3e3', "'0.3e3'"] +'!!str 00': ['str', '00', "00"] +'!!str 001.23': ['str', '001.23', "001.23"] +'!!str 0011': ['str', '0011', "0011"] +'!!str 010': ['str', '010', "010"] +'!!str 02_0': ['str', '02_0', "02_0"] +'!!str 07': ['str', '07', "07"] +'!!str 0b0': ['str', '0b0', "0b0"] +'!!str 0b100_101': ['str', '0b100_101', "0b100_101"] +'!!str 0o0': ['str', '0o0', "0o0"] +'!!str 0o10': ['str', '0o10', "0o10"] +'!!str 0o7': ['str', '0o7', "0o7"] +'!!str 0x0': ['str', '0x0', "0x0"] +'!!str 0x2_0': ['str', '0x2_0', "0x2_0"] +'!!str 0xa': ['str', '0xa', "0xa"] +'!!str 100_000': ['str', '100_000', "100_000"] +'!!str 190:20:30': ['str', '190:20:30', "190:20:30"] +'!!str 190:20:30.15': ['str', '190:20:30.15', "190:20:30.15"] +'!!str 23': ['str', '23', "'23'"] +'!!str 3.': ['str', '3.', "'3.'"] +'!!str 3.14': ['str', '3.14', "'3.14'"] +'!!str 3.3e+3': ['str', '3.3e+3', "'3.3e+3'"] +'!!str 85.230_15e+03': ['str', '85.230_15e+03', "85.230_15e+03"] +'!!str 85_230.15': ['str', '85_230.15', "85_230.15"] +'!!str FALSE': ['str', 'FALSE', "FALSE"] +'!!str False': ['str', 'False', "False"] +'!!str N': ['str', 'N', "N"] +'!!str NO': ['str', 'NO', "NO"] +'!!str NULL': ['str', 'NULL', "NULL"] +'!!str Null': ['str', 'Null', "Null"] +'!!str OFF': ['str', 'OFF', "OFF"] +'!!str ON': ['str', 'ON', "ON"] +'!!str Off': ['str', 'Off', "Off"] +'!!str On': ['str', 'On', "On"] +'!!str TRUE': ['str', 'TRUE', 'TRUE'] +'!!str True': ['str', 'True', 'True'] +'!!str Y': ['str', 'Y', "Y"] +'!!str YES': ['str', 'YES', "YES"] +'!!str Yes': ['str', 'Yes', "Yes"] +'!!str _._': ['str', '_._', '_._'] +'!!str false': ['str', 'false', "'false'"] +'!!str n': ['str', 'n', "n"] +'!!str no': ['str', 'no', "no"] +'!!str null': ['str', 'null', "'null'"] +'!!str off': ['str', 'off', "off"] +'!!str on': ['str', 'on', "on"] +'!!str true': ['str', 'true', "'true'"] +'!!str y': ['str', 'y', "y"] +'!!str yes': ['str', 'yes', "yes"] +'!!str ~': ['str', '~', '~'] +'#empty': ['str', '', "''"] +'+.INF': ['str', '+.INF', '+.INF'] +'+.Inf': ['str', '+.Inf', '+.Inf'] +'+.inf': ['str', '+.inf', '+.inf'] +'+0': ['str', '+0', '+0'] +'+0.3e+3': ['str', '+0.3e+3', '+0.3e+3'] +'+0.3e3': ['str', '+0.3e3', '+0.3e3'] +'+0100_200': ['str', '+0100_200', '+0100_200'] +'+0b100': ['str', '+0b100', '+0b100'] +'+190:20:30': ['str', '+190:20:30', '+190:20:30'] +'+23': ['str', '+23', '+23'] +'+3.14': ['str', '+3.14', '+3.14'] +'-.INF': ['str', '-.INF', '-.INF'] +'-.Inf': ['str', '-.Inf', '-.Inf'] +'-.inf': ['str', '-.inf', '-.inf'] +'-0': ['int', '0', '0'] +'-0100_200': ['str', '-0100_200', '-0100_200'] +'-0b101': ['str', '-0b101', '-0b101'] +'-0x30': ['str', '-0x30', '-0x30'] +'-190:20:30': ['str', '-190:20:30', '-190:20:30'] +'-23': ['int', '-23', '-23'] +'-3.14': ['float', '-3.14', '-3.14'] +'.': ['str', '.', '.'] +'.0': ['str', '.0', '.0'] +'.14': ['str', '.14', '.14'] +'.1_4': ['str', '.1_4', '.1_4'] +'.3E-1': ['str', '.3E-1', '.3E-1'] +'.3e+3': ['str', '.3e+3', '.3e+3'] +'.3e3': ['str', '.3e3', '.3e3'] +'.INF': ['str', '.INF', '.INF'] +'.Inf': ['str', '.Inf', '.Inf'] +'.NAN': ['str', '.NAN', '.NAN'] +'.NaN': ['str', '.NaN', '.NaN'] +'._': ['str', '._', '._'] +'._14': ['str', '._14', '._14'] +'.inf': ['str', '.inf', '.inf'] +'.nan': ['str', '.nan', '.nan'] +'0': ['int', '0', '0'] +'0.0': ['float', '0.0', '0.0'] +'0.3e3': ['float', '300.0', '300.0'] +'00': ['str', '00', '00'] +'001.23': ['str', '001.23', '001.23'] +'0011': ['str', '0011', '0011'] +'010': ['str', '010', '010'] +'02_0': ['str', '02_0', '02_0'] +'07': ['str', '07', '07'] +'08': ['str', '08', '08'] +'0b0': ['str', '0b0', '0b0'] +'0b100_101': ['str', '0b100_101', '0b100_101'] +'0o0': ['str', '0o0', '0o0'] +'0o10': ['str', '0o10', '0o10'] +'0o7': ['str', '0o7', '0o7'] +'0x0': ['str', '0x0', '0x0'] +'0x10': ['str', '0x10', '0x10'] +'0x2_0': ['str', '0x2_0', '0x2_0'] +'0x42': ['str', '0x42', '0x42'] +'0xa': ['str', '0xa', '0xa'] +'100_000': ['str', '100_000', '100_000'] +'190:20:30': ['str', '190:20:30', '190:20:30'] +'190:20:30.15': ['str', '190:20:30.15', '190:20:30.15'] +'23': ['int', '23', '23'] +'3.': ['float', '3.0', '3.0'] +'3.14': ['float', '3.14', '3.14'] +'3.3e+3': ['float', '3300', '3300.0'] +'3e3': ['float', '3000', '3000.0'] +'85.230_15e+03': ['str', '85.230_15e+03', '85.230_15e+03'] +'85_230.15': ['str', '85_230.15', '85_230.15'] +'FALSE': ['str', 'FALSE', 'FALSE'] +'False': ['str', 'False', 'False'] +'N': ['str', 'N', "N"] +'NO': ['str', 'NO', "NO"] +'NULL': ['str', 'NULL', 'NULL'] +'Null': ['str', 'Null', 'Null'] +'OFF': ['str', 'OFF', "OFF"] +'ON': ['str', 'ON', "ON"] +'Off': ['str', 'Off', "Off"] +'On': ['str', 'On', "On"] +'TRUE': ['str', 'TRUE', 'TRUE'] +'True': ['str', 'True', 'True'] +'Y': ['str', 'Y', "Y"] +'YES': ['str', 'YES', "YES"] +'Yes': ['str', 'Yes', "Yes"] +'_._': ['str', '_._', '_._'] +'false': ['bool', 'false()', 'false'] +'n': ['str', 'n', "n"] +'no': ['str', 'no', "no"] +'null': ['null', 'null()', "null"] +'off': ['str', 'off', "off"] +'on': ['str', 'on', "on"] +'true': ['bool', 'true()', 'true'] +'y': ['str', 'y', "y"] +'yes': ['str', 'yes', "yes"] +'~': ['str', '~', '~'] diff --git a/tests/data/json.schema-skip b/tests/data/json.schema-skip new file mode 100644 index 00000000..d2fe12e4 --- /dev/null +++ b/tests/data/json.schema-skip @@ -0,0 +1,6 @@ +load: { + } +dump: { + '#empty': 1, + '!!str #empty': 1, + } diff --git a/tests/lib/test_schema.py b/tests/lib/test_schema.py index f3370ec1..7bc84b7f 100644 --- a/tests/lib/test_schema.py +++ b/tests/lib/test_schema.py @@ -1,4 +1,5 @@ import yaml +import os import sys import pprint import math @@ -48,6 +49,17 @@ def _fail(input, test): print("Input: >>" + input + "<<") print(test) +class MyCoreLoader(yaml.BaseLoader): pass +class MyJSONLoader(yaml.BaseLoader): pass +class MyCoreDumper(yaml.CommonDumper): pass +class MyJSONDumper(yaml.CommonDumper): pass + +MyCoreLoader.init_tags('core') +MyJSONLoader.init_tags('json') + +MyCoreDumper.init_tags('core') +MyJSONDumper.init_tags('json') + # The tests/data/yaml11.schema file is copied from # https://github.com/perlpunk/yaml-test-schema/blob/master/data/schema-yaml11.yaml def test_implicit_resolver(data_filename, skip_filename, verbose=False): @@ -59,6 +71,19 @@ def test_implicit_resolver(data_filename, skip_filename, verbose=False): 'nan': [float, check_float], 'bool': [bool, check_bool], } + loaders = { + 'yaml11': yaml.SafeLoader, + 'core': MyCoreLoader, + 'json': MyJSONLoader, + } + dumpers = { + 'yaml11': yaml.SafeDumper, + 'core': MyCoreDumper, + 'json': MyJSONDumper, + } + loadername = os.path.splitext(os.path.basename(data_filename))[0] + print('==================') + print(loadername) with open(skip_filename, 'rb') as file: skipdata = yaml.load(file, Loader=yaml.SafeLoader) skip_load = skipdata['load'] @@ -84,7 +109,8 @@ def test_implicit_resolver(data_filename, skip_filename, verbose=False): # Test loading try: - loaded = yaml.safe_load(input) + doc_input = """---\n""" + input + loaded = yaml.load(doc_input, Loader=loaders[loadername]) except: print("Error:", sys.exc_info()[0], '(', sys.exc_info()[1], ')') fail+=1 @@ -106,22 +132,27 @@ def test_implicit_resolver(data_filename, skip_filename, verbose=False): else: t = types[exp_type][0] code = types[exp_type][1] + if isinstance(loaded, t): if code(loaded, data): pass else: fail+=1 + print("Expected data: >>" + str(data) + "<< Got: >>" + str(loaded) + "<<") _fail(input, test) else: fail+=1 + print("Expected type: >>" + exp_type + "<< Got: >>" + str(loaded) + "<<") _fail(input, test) # Skip known dumper bugs if input in skip_dump: continue - dump = yaml.safe_dump(loaded, explicit_end=False) + dump = yaml.dump(loaded, explicit_end=False, Dumper=dumpers[loadername]) # strip trailing newlines and footers + if (dump == '...\n'): + dump = '' if dump.endswith('\n...\n'): dump = dump[:-5] if dump.endswith('\n'): @@ -130,6 +161,11 @@ def test_implicit_resolver(data_filename, skip_filename, verbose=False): pass else: print("Compare: >>" + dump + "<< >>" + exp_dump + "<<") + print(skip_dump) + print(input) + print(test) + print(loaded) + print(type(loaded)) fail+=1 _fail(input, test)