summaryrefslogtreecommitdiff
path: root/morphlib
diff options
context:
space:
mode:
authorLars Wirzenius <lars.wirzenius@codethink.co.uk>2013-08-07 16:55:42 +0000
committerLars Wirzenius <lars.wirzenius@codethink.co.uk>2013-08-14 16:32:29 +0000
commitd3c10c7fe1b52a970984ed6b72fc8178eebac7f3 (patch)
tree7c59b731884db00683efd647b33b012ee5a47418 /morphlib
parentbac6fbfa164af018907796b9b1846a6066427daa (diff)
downloadmorph-d3c10c7fe1b52a970984ed6b72fc8178eebac7f3.tar.gz
Add a MorphologySet class, for changing many morphologies
Various parts of Morph need to change a set of morphologies at once, particularly for petrification and unpetrification. This is easiest done by loading all the morphologies into memory at once, and changing them there, then saving again.
Diffstat (limited to 'morphlib')
-rw-r--r--morphlib/__init__.py1
-rw-r--r--morphlib/morphset.py158
-rw-r--r--morphlib/morphset_tests.py180
3 files changed, 339 insertions, 0 deletions
diff --git a/morphlib/__init__.py b/morphlib/__init__.py
index b9b9924b..bcdd733b 100644
--- a/morphlib/__init__.py
+++ b/morphlib/__init__.py
@@ -67,6 +67,7 @@ import morph2
import morphologyfactory
import morph3
import morphloader
+import morphset
import remoteartifactcache
import remoterepocache
import repoaliasresolver
diff --git a/morphlib/morphset.py b/morphlib/morphset.py
new file mode 100644
index 00000000..98a4b8f9
--- /dev/null
+++ b/morphlib/morphset.py
@@ -0,0 +1,158 @@
+# Copyright (C) 2013 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# =*= License: GPL-2 =*=
+
+
+import morphlib
+
+
+class StratumNotInSystemError(morphlib.Error):
+
+ def __init__(self, system_name, stratum_name):
+ self.msg = (
+ 'System %s does not contain %s' % (system_name, stratum_name))
+
+
+class StratumNotInSetError(morphlib.Error):
+
+ def __init__(self, stratum_name):
+ self.msg = 'Stratum %s is not in MorphologySet' % stratum_name
+
+
+class ChunkNotInStratumError(morphlib.Error):
+
+ def __init__(self, stratum_name, chunk_name):
+ self.msg = (
+ 'Stratum %s does not contain %s' % (stratum_name, chunk_name))
+
+
+class MorphologySet(object):
+
+ '''Store and manipulate a set of Morphology objects.'''
+
+ def __init__(self):
+ self.morphologies = []
+
+ def add_morphology(self, morphology):
+ '''Add a morphology object to the set, unless it's there already.'''
+
+ triplet = (
+ morphology.repo_url,
+ morphology.ref,
+ morphology.filename
+ )
+ for existing in self.morphologies:
+ existing_triplet = (
+ existing.repo_url,
+ existing.ref,
+ existing.filename
+ )
+ if existing_triplet == triplet:
+ return
+
+ self.morphologies.append(morphology)
+
+ def has(self, repo_url, ref, filename):
+ '''Does the set have a morphology for the given triplet?'''
+ return self._get_morphology(repo_url, ref, filename) is not None
+
+ def _get_morphology(self, repo_url, ref, filename):
+ for m in self.morphologies:
+ if (m.repo_url == repo_url and
+ m.ref == ref and
+ m.filename == filename):
+ return m
+ return None
+
+ def _find_spec(self, specs, wanted_name):
+ for spec in specs:
+ name = spec.get('morph', spec.get('name'))
+ if name == wanted_name:
+ return spec['repo'], spec['ref'], name
+ return None, None, None
+
+ def get_stratum_in_system(self, system_morph, stratum_name):
+ '''Return morphology for a stratum that is in a system.
+
+ If the stratum is not in the system, raise StratumNotInSystemError.
+ If the stratum morphology has not been added to the set,
+ raise StratumNotInSetError.
+
+ '''
+
+ repo_url, ref, morph = self._find_spec(
+ system_morph['strata'], stratum_name)
+ if repo_url is None:
+ raise StratumNotInSystemError(system_morph['name'], stratum_name)
+ m = self._get_morphology(repo_url, ref, '%s.morph' % morph)
+ if m is None:
+ raise StratumNotInSetError(stratum_name)
+ return m
+
+ def get_chunk_triplet(self, stratum_morph, chunk_name):
+ '''Return the repo url, ref, morph name triplet for a chunk.
+
+ Given a stratum morphology, find the triplet used to refer to
+ a given chunk. Note that because of how the chunk may be
+ referred to using either name or morph fields in the morphology,
+ the morph field (or its computed value) is always returned.
+ Note also that the morph field, not the filename, is returned.
+
+ Raise ChunkNotInStratumError if the chunk is not found in the
+ stratum.
+
+ '''
+
+ repo_url, ref, morph = self._find_spec(
+ stratum_morph['chunks'], chunk_name)
+ if repo_url is None:
+ raise ChunkNotInStratumError(stratum_morph['name'], chunk_name)
+ return repo_url, ref, morph
+
+ def change_ref(self, repo_url, orig_ref, morph_filename, new_ref):
+ '''Change a triplet's ref to a new one in all morphologies in a ref.
+
+ Change orig_ref to new_ref in any morphology that references the
+ original triplet. This includes stratum build-dependencies.
+
+ '''
+
+ def wanted_spec(spec):
+ return (spec['repo'] == repo_url and
+ spec['ref'] == orig_ref and
+ spec['morph'] + '.morph' == morph_filename)
+
+ def change_specs(specs):
+ for spec in specs:
+ if wanted_spec(spec):
+ spec['ref'] = new_ref
+ m.dirty = True
+
+ def change(m):
+ if m['kind'] == 'system':
+ change_specs(m['strata'])
+ elif m['kind'] == 'stratum':
+ change_specs(m['chunks'])
+ change_specs(m['build-depends'])
+
+ for m in self.morphologies:
+ change(m)
+
+ m = self._get_morphology(repo_url, orig_ref, morph_filename)
+ if m and m.ref != new_ref:
+ m.ref = new_ref
+ m.dirty = True
+
diff --git a/morphlib/morphset_tests.py b/morphlib/morphset_tests.py
new file mode 100644
index 00000000..7dbc861a
--- /dev/null
+++ b/morphlib/morphset_tests.py
@@ -0,0 +1,180 @@
+# Copyright (C) 2013 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# =*= License: GPL-2 =*=
+
+
+import unittest
+
+import morphlib
+
+
+class MorphologySetTests(unittest.TestCase):
+
+ def setUp(self):
+ self.morphs = morphlib.morphset.MorphologySet()
+
+ self.system = morphlib.morph3.Morphology({
+ 'kind': 'system',
+ 'name': 'foo-system',
+ 'strata': [
+ {
+ 'repo': 'test:morphs',
+ 'ref': 'master',
+ 'morph': 'foo-stratum',
+ },
+ ],
+ })
+ self.system.repo_url = 'test:morphs'
+ self.system.ref = 'master'
+ self.system.filename = 'foo-system.morph'
+
+ self.stratum = morphlib.morph3.Morphology({
+ 'kind': 'stratum',
+ 'name': 'foo-stratum',
+ 'chunks': [
+ {
+ 'repo': 'test:foo-chunk',
+ 'ref': 'master',
+ 'morph': 'foo-chunk',
+ },
+ ],
+ 'build-depends': [],
+ })
+ self.stratum.repo_url = 'test:morphs'
+ self.stratum.ref = 'master'
+ self.stratum.filename = 'foo-stratum.morph'
+
+ def test_is_empty_initially(self):
+ self.assertEqual(self.morphs.morphologies, [])
+ self.assertFalse(
+ self.morphs.has(
+ self.system.repo_url, self.system.ref, self.system.filename))
+
+ def test_adds_morphology(self):
+ self.morphs.add_morphology(self.system)
+ self.assertEqual(self.morphs.morphologies, [self.system])
+ self.assertTrue(
+ self.morphs.has(
+ self.system.repo_url, self.system.ref, self.system.filename))
+
+ self.morphs.add_morphology(self.stratum)
+ self.assertEqual(
+ self.morphs.morphologies,
+ [self.system, self.stratum])
+
+ def test_does_not_add_morphology_twice(self):
+ self.morphs.add_morphology(self.system)
+ self.morphs.add_morphology(self.system)
+ self.assertEqual(self.morphs.morphologies, [self.system])
+
+ def test_get_stratum_in_system(self):
+ self.morphs.add_morphology(self.system)
+ self.morphs.add_morphology(self.stratum)
+ self.assertEqual(
+ self.morphs.get_stratum_in_system(
+ self.system, self.stratum['name']),
+ self.stratum)
+
+ def test_raises_stratum_not_in_system_error(self):
+ self.morphs.add_morphology(self.system)
+ self.morphs.add_morphology(self.stratum)
+ self.assertRaises(
+ morphlib.morphset.StratumNotInSystemError,
+ self.morphs.get_stratum_in_system, self.system, 'unknown-stratum')
+
+ def test_raises_stratum_not_in_set_error(self):
+ self.morphs.add_morphology(self.system)
+ self.assertRaises(
+ morphlib.morphset.StratumNotInSetError,
+ self.morphs.get_stratum_in_system, self.system, 'foo-stratum')
+
+ def test_get_chunk_triplet(self):
+ self.morphs.add_morphology(self.system)
+ self.morphs.add_morphology(self.stratum)
+ self.assertEqual(
+ self.morphs.get_chunk_triplet(self.stratum, 'foo-chunk'),
+ ('test:foo-chunk', 'master', 'foo-chunk'))
+
+ def test_raises_chunk_not_in_stratum_error(self):
+ self.assertRaises(
+ morphlib.morphset.ChunkNotInStratumError,
+ self.morphs.get_chunk_triplet, self.stratum, 'wrong')
+
+ def test_changes_stratum_ref(self):
+ self.morphs.add_morphology(self.system)
+ self.morphs.add_morphology(self.stratum)
+ self.morphs.change_ref(
+ self.stratum.repo_url,
+ self.stratum.ref,
+ self.stratum.filename,
+ 'new-ref')
+ self.assertEqual(self.stratum.ref, 'new-ref')
+ self.assertEqual(
+ self.system['strata'][0],
+ {
+ 'repo': 'test:morphs',
+ 'ref': 'new-ref',
+ 'morph': 'foo-stratum'
+ })
+
+ def test_changes_stratum_ref_in_build_depends(self):
+ other_stratum = morphlib.morph3.Morphology({
+ 'name': 'other-stratum',
+ 'kind': 'stratum',
+ 'chunks': [],
+ 'build-depends': [
+ {
+ 'repo': self.stratum.repo_url,
+ 'ref': self.stratum.ref,
+ 'morph': self.stratum['name'],
+ },
+ ]
+ })
+
+ self.morphs.add_morphology(self.system)
+ self.morphs.add_morphology(self.stratum)
+ self.morphs.add_morphology(other_stratum)
+ self.morphs.change_ref(
+ self.stratum.repo_url,
+ self.stratum.ref,
+ self.stratum.filename,
+ 'new-ref')
+ self.assertEqual(
+ other_stratum['build-depends'][0],
+ {
+ 'repo': 'test:morphs',
+ 'ref': 'new-ref',
+ 'morph': 'foo-stratum'
+ })
+
+ def test_changes_chunk_ref(self):
+ self.morphs.add_morphology(self.system)
+ self.morphs.add_morphology(self.stratum)
+ self.morphs.change_ref(
+ 'test:foo-chunk',
+ 'master',
+ 'foo-chunk.morph',
+ 'new-ref')
+ self.assertEqual(
+ self.stratum['chunks'],
+ [
+ {
+ 'repo': 'test:foo-chunk',
+ 'ref': 'new-ref',
+ 'morph': 'foo-chunk',
+ }
+ ])
+