summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--morphlib/morphloader.py58
-rw-r--r--morphlib/morphloader_tests.py40
2 files changed, 93 insertions, 5 deletions
diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py
index e4367fa1..cd005fae 100644
--- a/morphlib/morphloader.py
+++ b/morphlib/morphloader.py
@@ -205,6 +205,61 @@ class MultipleValidationErrors(morphlib.Error):
self.msg += ('\t' + str(error))
+class OrderedDumper(yaml.SafeDumper):
+ keyorder = (
+ 'name',
+ 'kind',
+ 'description',
+ 'arch',
+ 'strata',
+ 'configuration-extensions',
+ 'morph',
+ 'repo',
+ 'ref',
+ 'unpetrify-ref',
+ 'build-depends',
+ 'build-mode',
+ 'artifacts',
+ 'max-jobs',
+ 'products',
+ 'chunks',
+ 'build-system',
+ 'pre-configure-commands',
+ 'configure-commands',
+ 'post-configure-commands',
+ 'pre-build-commands',
+ 'build-commands',
+ 'post-build-commands',
+ 'pre-install-commands',
+ 'install-commands',
+ 'post-install-commands',
+ 'artifact',
+ 'include',
+ 'systems',
+ 'deploy',
+ 'type',
+ 'location',
+ )
+
+ @classmethod
+ def _iter_in_global_order(cls, mapping):
+ for key in cls.keyorder:
+ if key in mapping:
+ yield key, mapping[key]
+ for key in sorted(mapping.iterkeys()):
+ if key not in cls.keyorder:
+ yield key, mapping[key]
+
+ @classmethod
+ def _represent_dict(cls, dumper, mapping):
+ return dumper.represent_mapping('tag:yaml.org,2002:map',
+ cls._iter_in_global_order(mapping))
+
+ def __init__(self, *args, **kwargs):
+ yaml.SafeDumper.__init__(self, *args, **kwargs)
+ self.add_representer(dict, self._represent_dict)
+
+
class MorphologyLoader(object):
'''Load morphologies from disk, or save them back to disk.'''
@@ -324,7 +379,8 @@ class MorphologyLoader(object):
def save_to_string(self, morphology):
'''Return normalised textual form of morphology.'''
- return yaml.safe_dump(morphology.data, default_flow_style=False)
+ return yaml.dump(morphology.data, Dumper=OrderedDumper,
+ default_flow_style=False)
def save_to_file(self, filename, morphology):
'''Save a morphology object to a named file.'''
diff --git a/morphlib/morphloader_tests.py b/morphlib/morphloader_tests.py
index b8738804..a050e10b 100644
--- a/morphlib/morphloader_tests.py
+++ b/morphlib/morphloader_tests.py
@@ -505,9 +505,9 @@ build-system: dummy
# The following verifies that the YAML is written in a normalised
# fashion.
self.assertEqual(text, '''\
-build-system: dummy
-kind: chunk
name: foo
+kind: chunk
+build-system: dummy
''')
def test_saves_to_file(self):
@@ -524,9 +524,9 @@ name: foo
# The following verifies that the YAML is written in a normalised
# fashion.
self.assertEqual(text, '''\
-build-system: dummy
-kind: chunk
name: foo
+kind: chunk
+build-system: dummy
''')
def test_validate_does_not_set_defaults(self):
@@ -862,3 +862,35 @@ name: foo
self.assertEqual(warning.morphology_name, 'foo')
self.assertEqual(warning.stratum_name, 'bar')
self.assertEqual(warning.field, obsolete_field)
+
+ def test_unordered_asciibetically_after_ordered(self):
+ # We only get morphologies with arbitrary keys in clusters
+ m = morphlib.morph3.Morphology(
+ name='foo',
+ kind='cluster',
+ systems=[
+ {
+ 'morph': 'system-name',
+ 'repo': 'test:morphs',
+ 'ref': 'master',
+ 'deploy': {
+ 'deployment-foo': {
+ 'type': 'tarball',
+ 'location': '/tmp/path.tar',
+ 'HOSTNAME': 'aasdf',
+ }
+ }
+ }
+ ]
+ )
+ s = self.loader.save_to_string(m)
+ # root field order
+ self.assertLess(s.find('name'), s.find('kind'))
+ self.assertLess(s.find('kind'), s.find('systems'))
+ # systems field order
+ self.assertLess(s.find('morph'), s.find('repo'))
+ self.assertLess(s.find('repo'), s.find('ref'))
+ self.assertLess(s.find('ref'), s.find('deploy'))
+ # deployment keys field order
+ self.assertLess(s.find('type'), s.find('location'))
+ self.assertLess(s.find('location'), s.find('HOSTNAME'))