summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorSam Thursfield <sam.thursfield@codethink.co.uk>2013-01-15 15:54:30 +0000
committerSam Thursfield <sam.thursfield@codethink.co.uk>2013-02-18 15:45:09 +0000
commit6f3cbfeed92ef3fabb91622eb58c6ffe5c63289b (patch)
tree2db3eadbc395eb3ae532ae8c89edc0b225bf30df /scripts
parent4ab560d0b8d8243b941a343f4984112112cacbbd (diff)
downloadmorph-6f3cbfeed92ef3fabb91622eb58c6ffe5c63289b.tar.gz
Add scripts/edit-morph.py
This allows programmatic edits of certain kinds to morphologies, to minimise the need for manual editing.
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/edit-morph163
1 files changed, 163 insertions, 0 deletions
diff --git a/scripts/edit-morph b/scripts/edit-morph
new file mode 100755
index 00000000..116b9681
--- /dev/null
+++ b/scripts/edit-morph
@@ -0,0 +1,163 @@
+#!/usr/bin/env python
+# 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.
+
+
+import cliapp
+import os
+import re
+
+import morphlib
+
+class EditMorph(cliapp.Application):
+ '''Tools for performing set operations on large morphologies'''
+
+ def add_settings(self):
+ self.settings.boolean(['no-git-update'],
+ 'do not update the cached git repositories '
+ 'automatically')
+
+ def load_morphology(self, file_name, expected_kind = None):
+ with open(file_name) as f:
+ text = f.read()
+ try:
+ morphology = morphlib.morph2.Morphology(text)
+ except ValueError as e:
+ raise morphlib.Error("Error parsing %s: %s" %
+ (file_name, str(e)))
+
+ if expected_kind is not None and morphology['kind'] != expected_kind:
+ raise morphlib.Error("Expected: a %s morphology" % expected_kind)
+
+ return morphology
+
+
+ def cmd_remove_chunk(self, args):
+ '''Removes from STRATUM all reference of CHUNK'''
+
+ if len(args) != 2:
+ raise cliapp.AppException("remove-chunk expects a morphology file "
+ "name and a chunk name")
+
+ file_name = args[0]
+ chunk_name = args[1]
+ morphology = self.load_morphology(file_name, expected_kind = 'stratum')
+
+ component_count = 0
+ build_depends_count = 0
+ new_chunks = morphology['chunks']
+ for info in morphology['chunks']:
+ if info['name'] == chunk_name:
+ new_chunks.remove(info)
+ component_count += 1
+ elif chunk_name in info['build-depends']:
+ info['build-depends'].remove(chunk_name)
+ build_depends_count += 1
+ morphology._dict['chunks'] = new_chunks
+
+ with morphlib.savefile.SaveFile(file_name, 'w') as f:
+ morphology.write_to_file(f)
+
+ print "Removed: %i chunk(s) and %i build depend(s)." % \
+ (component_count, build_depends_count)
+
+ def cmd_sort(self, args):
+ """Sort STRATUM"""
+
+ if len(args) != 1:
+ raise cliapp.AppException("sort expects a morphology file name")
+
+ file_name = args[0]
+ morphology = self.load_morphology(file_name, expected_kind='stratum')
+
+ for chunk in morphology['chunks']:
+ chunk['build-depends'].sort()
+
+ morphology._dict['chunks'] = self.sort_chunks(morphology['chunks'])
+
+ with morphlib.savefile.SaveFile(file_name, 'w') as f:
+ morphology.write_to_file(f)
+
+ def sort_chunks(self, chunks_list):
+ """Sort stratum chunks
+
+ The order is something like alphabetical reverse dependency order.
+ Chunks on which nothing depends are sorted at the bottom.
+
+ The algorithm used is a simple-minded recursive sort.
+ """
+
+ chunk_dict = {}
+ for chunk in chunks_list:
+ chunk_dict[chunk['name']] = chunk
+
+ reverse_deps_dict = {}
+ for chunk_name in chunk_dict.keys():
+ chunk = chunk_dict[chunk_name]
+ for dep in chunk['build-depends']:
+ if dep not in reverse_deps_dict:
+ reverse_deps_dict[dep] = [chunk_name]
+ else:
+ reverse_deps_dict[dep].append(chunk_name)
+
+ sort_order = list(chunk_dict.keys())
+ sort_order.sort(key=unicode.lower)
+
+ result = []
+ satisfied_list = []
+ repeat_count = 0
+ while len(sort_order) > 0:
+ postponed_list = []
+
+ # Move any chunk into the result order that has all its
+ # dependencies satisfied in the result already.
+ for chunk_name in sort_order:
+ deps_satisfied = True
+
+ chunk = chunk_dict[chunk_name]
+ for dep in chunk['build-depends']:
+ if dep not in satisfied_list:
+ deps_satisfied = False
+ if dep not in sort_order:
+ raise cliapp.AppException(
+ 'Invalid build-dependency for %s: %s'
+ % (chunk['name'], dep))
+ break
+
+ if deps_satisfied:
+ result.append(chunk)
+ satisfied_list.append(chunk_name)
+ else:
+ postponed_list.append(chunk_name)
+
+ if len(postponed_list) == len(sort_order):
+ # This is not the smartest algorithm possible (but it works!)
+ repeat_count += 1
+ if repeat_count > 10:
+ raise cliapp.AppException('Stuck in loop while sorting')
+
+ assert(len(postponed_list) + len(result) == len(chunk_dict.keys()))
+ sort_order = postponed_list
+
+ # Move chunks which are not build-depends of other chunks to the end.
+ targets = [c for c in chunk_dict.keys() if c not in reverse_deps_dict]
+ targets.sort(key=unicode.lower)
+ for chunk_name in targets:
+ result.remove(chunk_dict[chunk_name])
+ result.append(chunk_dict[chunk_name])
+
+ return result
+
+EditMorph().run()