changeset 38981:aae454c4f45a

refactor transitive closure into module table
author Dmitry Selyutin <ghostmansd@gmail.com>
date Tue, 19 Sep 2017 22:40:10 +0300
parents e2d5a91aea8a
children 987c6d853447
files pygnulib/filesystem.py pygnulib/module.py
diffstat 2 files changed, 104 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/pygnulib/filesystem.py	Tue Sep 19 22:38:03 2017 +0300
+++ b/pygnulib/filesystem.py	Tue Sep 19 22:40:10 2017 +0300
@@ -8,7 +8,7 @@
 
 
 from .error import type_assert as _type_assert_
-from .error import GnulibModuleNotFoundError as _GnulibModuleNotFoundError_
+from .error import UnknownModuleError as _UnknownModuleError_
 from .config import Base as _BaseConfig_
 from .module import Base as _BaseModule_
 from .module import File as _FileModule_
@@ -129,7 +129,7 @@
         try:
             return _FileModule_(path, name=name) if full else _BaseModule_(name)
         except FileNotFoundError:
-            raise _GnulibModuleNotFoundError_(name)
+            raise _UnknownModuleError_(name)
 
 
     def modules(self, full=True):
@@ -149,67 +149,3 @@
                 path = _os_.path.join(root, name)
                 name = path[len(prefix) + 1:]
                 yield self.module(name, full)
-
-
-    def transitive_closure(self, config, modules, tests):
-        """
-        GnulibGit.transitive_closure(config, modules, tests) ->
-            set((
-                (module0, demander0, condition0),
-                (moduleN, demanderN, conditionN),
-            ))
-
-        config is a gnulib configuration (e.g. pygnulib.Config.Base instance).
-        modules is an iterable, yielding a module (either name or instance).
-        tests designates whether to consider the corresponding test module and its dependencies.
-
-        The returned value is a set, containing a sequence of tuples (module, demander, condition).
-        If condition is None, but demander is not, there is no special condition for this module.
-        If demander is None, the module is provided unconditionally (condition is always None).
-        The correct iteration over the resulting table is shown below:
-
-        for (module, demander, condition) in gnulib.transitive_closure(modules):
-            if demander is not None:
-                if condition is not None:
-                    fmt = "{0} is added as dependency from {1} under '{2}' condition"
-                    args = (module.name, demander.name, condition)
-                else:
-                    fmt = "{0} is added as dependency from {1} without specific condition"
-                    args = (module.name, demander.name)
-            else:
-                fmt = "{0} is added unconditionally"
-                args = (module.name,)
-            print(fmt.format(args))
-        """
-        demanders = set()
-        (old, new) = (set(), set())
-        for module in modules:
-            if isinstance(module, str):
-                module = self.module(module)
-            new.add((module, None, None))
-        while old != new:
-            old.update(new)
-            for (demander, _, _) in old:
-                if demander in demanders:
-                    continue
-                if tests:
-                    try:
-                        name = "{0}-tests".format(demander.name)
-                        new.add((self.module(name), None, None))
-                    except _GnulibModuleNotFoundError_:
-                        pass # ignore non-existent tests
-                for (dependency, condition) in demander.dependencies:
-                    module = self.module(dependency)
-                    exclude = (
-                        (not config.obsolete and module.obsolete),
-                        (not config.cxx_tests and module.cxx_test),
-                        (not config.longrunning_tests and module.longrunning_test),
-                        (not config.privileged_tests and module.privileged_test),
-                        (not config.unportable_tests and module.unportable_test),
-                    )
-                    if any(exclude):
-                        continue
-                    condition = condition if condition.strip() else None
-                    new.add((module, demander, condition))
-                demanders.add(demander)
-        return new
--- a/pygnulib/module.py	Tue Sep 19 22:38:03 2017 +0300
+++ b/pygnulib/module.py	Tue Sep 19 22:40:10 2017 +0300
@@ -12,6 +12,8 @@
 
 
 from .error import type_assert as _type_assert_
+from .error import UnknownModuleError as _UnknownModuleError_
+from .config import Option as _ConfigOption_
 
 
 
@@ -342,12 +344,16 @@
 
 
     def __lt__(self, value):
+        if not isinstance(value, Base):
+            return True
         return self.name < value.name
 
     def __le__(self, value):
         return self.__lt__(value) or self.__eq__(value)
 
     def __eq__(self, value):
+        if not isinstance(value, Base):
+            return False
         return self.name == value.name
 
     def __ne__(self, value):
@@ -432,3 +438,99 @@
 
     def __exit__(self, exctype, excval, exctrace):
         self.close()
+
+
+
+class Table:
+    """module transitive closure result"""
+    def __init__(self, lookup, modules, options):
+        """
+        Perform a transitive closure, generating a set of module dependencies.
+        Each iteration over the table yields a tuple of (module, demander, condition).
+
+        If condition is None, but demander is not, there is no special condition for this module.
+        If demander is None, the module is provided unconditionally (condition is always None).
+
+        lookup must be a callable which obtains a pygnulib module by its name.
+        modules is an iterable, yielding a module (either name or instance).
+        options may be any combination of gnulib configuration options.
+        """
+        if not callable(lookup):
+            raise TypeError("lookup must be a callable")
+        _type_assert_("options", options, _ConfigOption_)
+
+        obsolete = bool(options & _ConfigOption_.Obsolete)
+        tests = bool(options & _ConfigOption_.Tests)
+        cxx_tests = bool(options & _ConfigOption_.CXX)
+        longrunning_tests = bool(options & _ConfigOption_.Longrunning)
+        privileged_tests = bool(options & _ConfigOption_.Privileged)
+        unportable_tests = bool(options & _ConfigOption_.Unportable)
+        modules = set(lookup(module) for module in modules)
+
+        def _transitive_closure_(tests):
+            queue = set()
+            previous = set()
+            current = set()
+            for module in modules:
+                current.add((module, None, None))
+            while previous != current:
+                previous.update(current)
+                for (demander, _, _) in previous:
+                    if demander in queue:
+                        continue
+                    if tests and not demander.name.endswith("-tests"):
+                        try:
+                            name = "{0}-tests".format(demander.name)
+                            current.add((lookup(name), None, None))
+                        except _UnknownModuleError_:
+                            pass # ignore non-existent tests
+                    for (dependency, condition) in demander.dependencies:
+                        module = lookup(dependency)
+                        exclude = (
+                            (not obsolete and module.obsolete),
+                            (not cxx_tests and module.cxx_test),
+                            (not longrunning_tests and module.longrunning_test),
+                            (not privileged_tests and module.privileged_test),
+                            (not unportable_tests and module.unportable_test),
+                        )
+                        if not any(exclude):
+                            condition = condition if condition.strip() else None
+                            current.add((module, demander, condition))
+                    queue.add(demander)
+            return current
+
+        self.__with_tests = tests
+        self.__base = _transitive_closure_(False)
+        self.__full = _transitive_closure_(True)
+
+
+    def __iter__(self):
+        return iter(self.__full)
+
+
+    @property
+    def main(self):
+        """an iterator over main module set"""
+        return iter(set(module for (module, _, _) in self.__base))
+
+
+    @property
+    def tests(self):
+        """an iterator over tests-related module set"""
+        final = self.final
+        if not self.__with_tests:
+            final = (module for module in final if module.applicability != "all")
+        main = (module for module in self.main if module.applicability != "all")
+        return iter(set(final) ^ set(main))
+
+
+    @property
+    def final(self):
+        """an iterator over final module set"""
+        return self.all if self.__with_tests else self.main
+
+
+    @property
+    def all(self):
+        """an iterator over all modules"""
+        return iter(set(module for (module, _, _) in self.__full))