From 52c3cb12ba4e78d3711fa45a9617d1932f790efc Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 18 Nov 2021 16:02:59 +0100 Subject: [PATCH] osbuild-mpp: Support loading yaml as well as json files yaml files are essentially compatible with json files, although they have some advantages, like allowing comments and being easier for humans to read/write. This changes the reading of the file to use a yaml parser instead of a json parser, but still produces json at the end. I tried manually converting a json file to yaml and running osbuild-mpp, and it produced an identical file. By default the yaml parser doesn't respect order so i had to tweak the loader a bit to use OrderedDict. --- tools/osbuild-mpp | 49 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/tools/osbuild-mpp b/tools/osbuild-mpp index 966ff8ee..a822f4a4 100755 --- a/tools/osbuild-mpp +++ b/tools/osbuild-mpp @@ -242,6 +242,7 @@ import collections import contextlib import hashlib import json +import yaml import os import pathlib import string @@ -257,6 +258,33 @@ import hawkey from osbuild.util.rhsm import Subscriptions +class YamlOrderedLoader(yaml.Loader): + def construct_mapping(self, node, deep=False): + if not isinstance(node, yaml.MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + mapping = collections.OrderedDict() + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + if not isinstance(key, collections.abc.Hashable): + raise ConstructorError("while constructing a mapping", node.start_mark, + "found unhashable key", key_node.start_mark) + value = self.construct_object(value_node, deep=deep) + mapping[key] = value + return mapping + def construct_yaml_map(self, node): + data = collections.OrderedDict() + yield data + value = self.construct_mapping(node) + data.update(value) +yaml.add_constructor('tag:yaml.org,2002:map', YamlOrderedLoader.construct_yaml_map) + +def yaml_load_ordered(source): + return yaml.load(source, YamlOrderedLoader) + +def json_load_ordered(source): + return json.load(source, object_pairs_hook=collections.OrderedDict) def element_enter(element, key, default): if key not in element: @@ -603,11 +631,22 @@ class ManifestFile: @staticmethod def load_from_fd(f, path, overrides, default_vars): # We use OrderedDict to preserve key order (for python < 3.6) - try: - data = json.load(f, object_pairs_hook=collections.OrderedDict) - except json.decoder.JSONDecodeError as err: - print (f"Invalid json in \"{path}\": {err.msg} at line {err.lineno} (col {err.colno})") - sys.exit(1) + if path.endswith(".yml") or path.endswith(".yaml"): + try: + data = yaml_load_ordered(f) + except yaml.YAMLError as err: + pos = "" + if hasattr(err, 'problem_mark'): + mark = err.problem_mark + pos = f" at line {mark.line+1} (col {mark.column+1})" + print(f"Invalid yaml in \"{path}\": {err.problem}{pos}") + sys.exit(1) + else: + try: + data = json_load_ordered(f) + except json.decoder.JSONDecodeError as err: + print(f"Invalid json in \"{path}\": {err.msg} at line {err.lineno} (col {err.colno})") + sys.exit(1) version = int(data.get("version", "1")) if version == 1: