]> Savannah Git Hosting - gnulib.git/commitdiff
simplify properties; improve performance
authorDmitry Selyutin <ghostmansd@gmail.com>
Sun, 4 Feb 2018 10:13:13 +0000 (13:13 +0300)
committerDmitry Selyutin <ghostmansd@gmail.com>
Sun, 4 Feb 2018 10:13:13 +0000 (13:13 +0300)
pygnulib.py
pygnulib/config.py
pygnulib/error.py
pygnulib/generator.py
pygnulib/misc.py [new file with mode: 0644]
pygnulib/module.py
pygnulib/parser.py
pygnulib/tools.py
pygnulib/vfs.py

index defa62650a267561210b93336314c91db4756d2d..1636a6a4d1c1845b77f99eba7aa24c817db95747 100755 (executable)
@@ -116,15 +116,7 @@ def extract_hook(program, gnulib, mode, namespace, *args, **kwargs):
 def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args, **kwargs):
     (_, _) = (args, kwargs)
     config = BaseConfig(**namespace)
-    try:
-        cache = CachedConfig(root=config.root)
-    except FileNotFoundError:
-        cache = BaseConfig(**config)
-        cache.files = set()
-        cache.ac_version = "2.59"
-    for key in {"ac_version", "files"}:
-        if key not in namespace:
-            config[key] = cache[key]
+    cache = CachedConfig(root=config.root, gnulib_comp=False, gnulib_cache=False)
     database = Database(gnulib.module, config)
 
     # Print some information about modules.
@@ -155,7 +147,7 @@ def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args,
 
     # Determine license incompatibilities, if any.
     incompatibilities = set()
-    if config.licenses & {"LGPLv2", "LGPLv2+", "LGPLv3", "LGPLv3+"}:
+    if set(config.licenses) & {"LGPLv2", "LGPLv2+", "LGPLv3", "LGPLv3+"}:
         acceptable = IGNORED_LICENSES | config.licenses
         for (name, licenses) in ((module.name, module.licenses) for module in main):
             if not (acceptable & licenses):
@@ -176,7 +168,13 @@ def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args,
                 print("\n".join("  " + line for line in notice.splitlines()), file=sys.stdout)
 
     # Determine the final file list.
-    files = (set(database.main_files) | set(database.test_files))
+    test_files = set()
+    main_files = set(database.main_files)
+    for file in database.test_files:
+        if file.startswith("lib/"):
+            file = "tests=lib/{}".format(file[len("lib/"):])
+        test_files.add(file)
+    files = (main_files | test_files)
     if verbosity >= 0:
         print("File list:", file=sys.stdout)
         for file in sorted(files):
@@ -188,24 +186,6 @@ def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args,
             else:
                 print("  ", file, file=sys.stdout, sep="")
 
-    table = {}
-    overrides = []
-    project = BaseVFS(config.root, **table)
-    overrides = {BaseVFS(override, **table) for override in config.overrides}
-    for (name, key) in SUBSTITUTION.items():
-        project[name] = config[key] if key else ""
-    for override in overrides:
-        for (name, key) in SUBSTITUTION.items():
-            override[name] = config[key] if key else ""
-
-    table = {}
-    old_files = set(cache.files)
-    if "m4/gnulib-tool.m4" in project:
-        old_files |= set(["m4/gnulib-tool.m4"])
-    for (tbl_key, cfg_key) in SUBSTITUTION.items():
-        table[tbl_key] = cache[cfg_key] if cfg_key else ""
-    new_files = frozenset(files | set(["m4/gnulib-tool.m4"]))
-
     dry_run = options["dry_run"]
     gnulib_copymode = config.copymode
     local_copymode = config.local_copymode
@@ -249,11 +229,29 @@ def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args,
             transfer_file(local, src_vfs, src_name, dst_vfs, dst_name)
         print(fmt.format(file=dst_name), file=sys.stdout)
 
+    # Adjust the VFS mappings.
+    overrides = []
+    project = BaseVFS(config.root)
+    overrides = {BaseVFS(override) for override in config.overrides}
+    for (alias, key) in SUBSTITUTION.items():
+        path = config[key] if key else ""
+        project[alias] = path
+        for override in overrides:
+            override[alias] = path
+    gnulib["tests=lib"] = "lib"
+
+    # Determine all relevant file lists.
+    old_files = set(cache.files)
+    if "m4/gnulib-tool.m4" in project:
+        old_files |= set(["m4/gnulib-tool.m4"])
+    new_files = (files | set(["m4/gnulib-tool.m4"]))
+
     # First the files that are in old_files, but not in new_files.
     # Then the files that are in new_files, but not in old_files.
     # Then the files that are in new_files and in old_files.
-    removed_files = {file for file in old_files if file not in new_files}
-    added_files = {file for file in new_files if file not in old_files}
+    origin = lambda file: ("lib/" + file[len("tests=lib/"):]) if file.startswith("tests=lib/") else file
+    removed_files = set(map(origin, old_files)).difference(map(origin, new_files))
+    added_files = new_files.difference(old_files)
     kept_files = (old_files & new_files)
     for file in sorted(removed_files):
         remove_file(project, file)
@@ -299,8 +297,15 @@ def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args,
     # Generate the contents of library makefile.
     path = os.path.join(config.source_base, config.makefile_name)
     with tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False) as tmp:
-        modules = database.main_modules
-        for line in lib_makefile(path, config, explicit, database, modules, mkedits, False):
+        arguments = {
+            "path": path,
+            "config": config,
+            "explicit": explicit,
+            "modules": database.main_modules,
+            "mkedits": mkedits,
+            "testing": False,
+        }
+        for line in lib_makefile(**arguments):
             print(line, file=tmp)
     (src, dst) = (tmp.name, path)
     present = vfs_exists(project, dst)
@@ -346,7 +351,7 @@ def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args,
         action = update_file if present else add_file
         action(False, None, src, project, dst, present)
         os.unlink(tmp.name)
-        # Fetch PO files.
+
         po_root = os.path.join(project.absolute, project["po"])
         fmt = ("{} gnulib PO files from " + TP_URL)
         print(fmt.format("Fetching", "Fetch")[dry_run], file=sys.stdout)
@@ -382,7 +387,7 @@ def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args,
 
     # Create m4/gnulib-cache.m4.
     with tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False) as tmp:
-        for line in gnulib_cache(config):
+        for line in gnulib_cache(config, explicit):
             print(line, file=tmp)
     (src, dst) = (tmp.name, "m4/gnulib-cache.m4")
     present = vfs_exists(project, dst)
@@ -394,7 +399,13 @@ def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args,
 
     # Create m4/gnulib-comp.m4.
     with tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False) as tmp:
-        for line in gnulib_comp(config, explicit, database, True):
+        arguments = {
+            "config": config,
+            "explicit": explicit,
+            "database": database,
+            "subdirs": True,
+        }
+        for line in gnulib_comp(**arguments):
             print(line, file=tmp)
     (src, dst) = (tmp.name, "m4/gnulib-comp.m4")
     present = vfs_exists(project, dst)
@@ -408,8 +419,16 @@ def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args,
     if config.tests:
         path = os.path.join(config.tests_base, config.makefile_name)
         with tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False) as tmp:
-            modules = database.test_modules
-            for line in tests_makefile(path, config, explicit, database, modules, mkedits, False):
+            arguments = {
+                "path": path,
+                "config": config,
+                "explicit": explicit,
+                "modules": database.test_modules,
+                "mkedits": mkedits,
+                "testing": False,
+                "libtests": database.libtests,
+            }
+            for line in tests_makefile(**arguments):
                 print(line, file=tmp)
         (src, dst) = (tmp.name, path)
         present = vfs_exists(project, dst)
@@ -418,7 +437,6 @@ def import_hook(script, gnulib, namespace, explicit, verbosity, options, *args,
         action = update_file if present else add_file
         action(False, None, src, project, dst, present)
         os.unlink(tmp.name)
-
     return os.EX_OK
 
 
@@ -464,7 +482,6 @@ HOOKS = {
 
 def main(script, gnulib, program, arguments, environ):
     gnulib = GnulibGitVFS(gnulib)
-    gnulib["tests=lib"] = "lib"
     parser = CommandLineParser(program)
     try:
         (namespace, mode, verbosity, options) = parser.parse(arguments)
@@ -485,9 +502,9 @@ def main(script, gnulib, program, arguments, environ):
         "verbosity": verbosity,
         "options": options,
     }
-    for (action, callback) in HOOKS.items():
+    for (action, hook) in HOOKS.items():
         if mode.startswith(action):
-            return callback(**kwargs)
+            return hook(**kwargs)
     return os.EX_SOFTWARE
 
 
@@ -501,13 +518,12 @@ if __name__ == "__main__":
     environ = dict(os.environ)
     try:
         result = main(script, gnulib, program, arguments, environ)
-    except BaseException as error:
+    except StopIteration as error:
         with codecs.open(log, "wb", "UTF-8") as stream:
             program = repr(program) if " " in program else program
             arguments = " ".join(repr(arg) if " " in arg else arg for arg in arguments)
             print(traceback.format_exc(), file=stream)
             print("COMMAND:", program, arguments, file=stream)
-            print("VERSION:", gnulib, file=stream)
         typeid = type(error)
         module = typeid.__module__
         name = typeid.__name__
index 71c04cb32260ba5f08f49baf3a8db85bde7879dc..ab7586c3369a22f4b1d76dc607b7496fe6310143 100644 (file)
@@ -7,14 +7,15 @@
 import codecs as _codecs
 import os as _os
 import re as _re
-
-from collections import OrderedDict as _OrderedDict
 from distutils import version as _version
 
-
-from .error import type_assert as _type_assert
 from .error import AutoconfVersionError as _AutoconfVersionError
 from .error import M4BaseMismatchError as _M4BaseMismatchError
+from .misc import Property as _Property
+from .misc import PathProperty as _PathProperty
+from .misc import BitwiseProperty as _BitwiseProperty
+from .misc import StringListProperty as _StringListProperty
+from .misc import PathListProperty as _PathListProperty
 
 
 
@@ -22,9 +23,6 @@ def _compile(regex):
     return _re.compile(regex, _re.S | _re.M)
 
 
-_ITERABLES = frozenset((list, tuple, set, frozenset, type({}.keys()), type({}.values())))
-
-
 
 LGPLv2_LICENSE = frozenset({"LGPLv2", "LGPLv2+"})
 LGPLv3_LICENSE = frozenset({"LGPLv2+", "LGPLv3", "LGPLv3+", "LGPL"})
@@ -41,630 +39,418 @@ OTHER_LICENSES = frozenset({
 
 
 
-KEYS = frozenset({
-    "root",
-    "overrides",
-    "source_base",
-    "m4_base",
-    "po_base",
-    "doc_base",
-    "tests_base",
-    "auxdir",
-    "libname",
-    "makefile_name",
-    "macro_prefix",
-    "po_domain",
-    "witness_c_macro",
-    "licenses",
-    "ac_version",
-    "ac_file",
-    "modules",
-    "avoids",
-    "files",
-    "copymode",
-    "local_copymode",
-    "tests",
-    "obsolete",
-    "cxx_tests",
-    "longrunning_tests",
-    "privileged_tests",
-    "unportable_tests",
-    "libtool",
-    "conditionals",
-    "copyrights",
-    "gnumake",
-    "single_configure",
-    "vc_files",
-    "all_tests",
-})
-
-
-
 class BaseConfig:
     """gnulib generic configuration"""
-    _TABLE = {
+    __slots__ = ("__options", "__flags", "__active")
+
+
+    __PROPERTIES = {
         "root"              : ".",
-        "overrides"         : set(),
+        "overrides"         : tuple(),
         "source_base"       : "lib",
         "m4_base"           : "m4",
-        "po_base"           : "po",
+        "po_base"           : "",
         "doc_base"          : "doc",
         "tests_base"        : "tests",
-        "auxdir"            : "",
+        "auxdir"            : "build-aux",
         "libname"           : "libgnu",
         "makefile_name"     : "Makefile.am",
         "macro_prefix"      : "gl",
         "po_domain"         : "",
         "witness_c_macro"   : "",
-        "licenses"          : set(),
+        "licenses"          : tuple(),
         "ac_version"        : "2.59",
         "ac_file"           : "configure.ac",
-        "modules"           : set(),
-        "avoids"            : set(),
-        "files"             : set(),
+        "modules"           : tuple(),
+        "avoids"            : tuple(),
+        "files"             : tuple(),
         "copymode"          : None,
         "local_copymode"    : None,
         "tests"             : False,
-        "obsolete"          : False,
         "cxx_tests"         : False,
         "longrunning_tests" : False,
         "privileged_tests"  : False,
         "unportable_tests"  : False,
+        "all_tests"         : False,
+        "obsolete"          : False,
         "libtool"           : False,
-        "conditionals"      : True,
+        "conditionals"      : False,
         "copyrights"        : False,
         "gnumake"           : False,
         "single_configure"  : False,
         "vc_files"          : False,
     }
+    __OPTIONS = {
+        "root",
+        "overrides",
+        "source_base",
+        "m4_base",
+        "po_base",
+        "doc_base",
+        "tests_base",
+        "auxdir",
+        "libname",
+        "makefile_name",
+        "macro_prefix",
+        "po_domain",
+        "witness_c_macro",
+        "licenses",
+        "ac_version",
+        "ac_file",
+        "modules",
+        "avoids",
+        "files",
+        "copymode",
+        "local_copymode",
+    }
+    __FLAGS = {
+        "tests",
+        "cxx_tests",
+        "longrunning_tests",
+        "privileged_tests",
+        "unportable_tests",
+        "all_tests",
+        "obsolete",
+        "libtool",
+        "conditionals",
+        "copyrights",
+        "gnumake",
+        "single_configure",
+        "vc_files",
+    }
 
 
-    class _Option:
-        """gnulib configuration options"""
-        Tests = (1 << 0)
-        Obsolete = (1 << 1)
-        CXX = (1 << 2)
-        Longrunning = (1 << 3)
-        Privileged = (1 << 4)
-        Unportable = (1 << 5)
-        Libtool = (1 << 6)
-        Conditionals = (1 << 7)
-        Copyrights = (1 << 8)
-        GNUMake = (1 << 9)
-        SingeConfigure = (1 << 10)
-        AllTests = (Obsolete | Tests | CXX | Longrunning | Privileged | Unportable)
-
-
-    def __init__(self, **kwargs):
-        self.__options = 0
-        self.__table = {}
-        for (key, value) in BaseConfig._TABLE.items():
-            self[key] = kwargs.get(key, value)
+    def __init__(self, root, **kwargs):
+        if not isinstance(root, str):
+            raise TypeError("root: str expected")
+        if not root:
+            raise ValueError("root: empty path")
+        root = _os.path.normpath(root)
 
+        self.__active = {}
+        self.__flags = 0
+        self.__options = {}
+        for key in BaseConfig.__OPTIONS:
+            value = BaseConfig.__PROPERTIES[key]
+            self.__set_option_pure(key, value)
+        for key in BaseConfig.__FLAGS:
+            state = BaseConfig.__PROPERTIES[key]
+            mask = getattr(self.__class__, key).mask
+            self.__set_flags_pure(mask, state)
+        self.__set_option("root", root)
 
-    def __repr__(self):
-        module = self.__class__.__module__
-        name = self.__class__.__name__
-        return "{}.{}{}".format(module, name, repr(self.__table))
+        for (key, value) in kwargs.items():
+            setattr(self, key, value)
 
 
     def __enter__(self):
         return self
 
 
-    def __exit__(self, exctype, excval, exctrace):
-        pass
-
-
-    @property
-    def root(self):
-        """target directory"""
-        return self.__table["root"]
-
-    @root.setter
-    def root(self, value):
-        _type_assert("root", value, str)
-        if not value:
-            raise ValueError("non-empty path not allowed")
-        self.__table["root"] = _os.path.normpath(value)
-
-
-    @property
-    def overrides(self):
-        """local override directories"""
-        return self.__table["overrides"]
-
-    @overrides.setter
-    def overrides(self, value):
-        _type_assert("overrides", value, _ITERABLES)
-        result = list()
-        for item in value:
-            _type_assert("override", item, str)
-            result.append(_os.path.normpath(item))
-        result = _OrderedDict.fromkeys(result)
-        self.__table["overrides"] = tuple(result)
-
-
-    @property
-    def source_base(self):
-        """directory relative to ROOT where source code is placed; defaults to 'lib'"""
-        return self.__table["source_base"]
-
-    @source_base.setter
-    def source_base(self, value):
-        _type_assert("source_base", value, str)
-        value = _os.path.normpath(value)
-        if _os.path.isabs(value):
-            raise ValueError("source_base cannot be an absolute path")
-        self.__table["source_base"] = _os.path.normpath(value) if value else "lib"
-
-
-    @property
-    def m4_base(self):
-        """directory relative to ROOT where *.m4 macros are placed; defaults to 'm4'"""
-        return self.__table["m4_base"]
-
-    @m4_base.setter
-    def m4_base(self, value):
-        _type_assert("m4_base", value, str)
-        value = _os.path.normpath(value)
-        if _os.path.isabs(value):
-            raise ValueError("m4_base cannot be an absolute path")
-        self.__table["m4_base"] = _os.path.normpath(value) if value else "m4"
-
-
-    @property
-    def po_base(self):
-        """directory relative to ROOT where *.po files are placed; defaults to 'po'"""
-        return self.__table["po_base"]
-
-    @po_base.setter
-    def po_base(self, value):
-        _type_assert("po_base", value, str)
-        value = _os.path.normpath(value)
-        if _os.path.isabs(value):
-            raise ValueError("po_base cannot be an absolute path")
-        self.__table["po_base"] = _os.path.normpath(value) if value else "po"
-
-
-    @property
-    def doc_base(self):
-        """directory relative to ROOT where doc files are placed; defaults to 'doc'"""
-        return self.__table["doc_base"]
-
-    @doc_base.setter
-    def doc_base(self, value):
-        _type_assert("doc_base", value, str)
-        value = _os.path.normpath(value)
-        if _os.path.isabs(value):
-            raise ValueError("doc_base cannot be an absolute path")
-        self.__table["doc_base"] = _os.path.normpath(value) if value else "doc"
-
-
-    @property
-    def tests_base(self):
-        """directory relative to ROOT where unit tests are placed; defaults to 'tests'"""
-        return self.__table["tests_base"]
-
-    @tests_base.setter
-    def tests_base(self, value):
-        _type_assert("tests_base", value, str)
-        value = _os.path.normpath(value)
-        if _os.path.isabs(value):
-            raise ValueError("tests_base cannot be an absolute path")
-        self.__table["tests_base"] = _os.path.normpath(value) if value else "tests"
-
-
-    @property
-    def auxdir(self):
-        """directory relative to ROOT where auxiliary build tools are placed"""
-        return self.__table["auxdir"]
-
-    @auxdir.setter
-    def auxdir(self, value):
-        _type_assert("auxdir", value, str)
-        value = _os.path.normpath(value)
-        if _os.path.isabs(value):
-            raise ValueError("auxdir cannot be an absolute path")
-        self.__table["auxdir"] = _os.path.normpath(value) if value else "build-aux"
-
-
-    @property
-    def libname(self):
-        """library name; defaults to 'libgnu'"""
-        return self.__table["libname"]
-
-    @libname.setter
-    def libname(self, value):
-        _type_assert("libname", value, str)
-        self.__table["libname"] = value if value else "libgnu"
-
-
-    @property
-    def makefile_name(self):
-        """name of makefile in automake syntax in the source-base and tests-base directories"""
-        return self.__table["makefile_name"]
-
-    @makefile_name.setter
-    def makefile_name(self, value):
-        _type_assert("makefile_name", value, str)
-        value = _os.path.normpath(value)
-        if _os.path.isabs(value):
-            raise ValueError("makefile_name cannot be an absolute path")
-        self.__table["makefile_name"] = value
-
-
-    @property
-    def macro_prefix(self):
-        """
-        the prefix of the macros 'gl_EARLY' and 'gl_INIT' (default is 'gl');
-        the change of this parameter also affects include_guard_prefix parameter
-        """
-        return self.__table["macro_prefix"]
-
-    @macro_prefix.setter
-    def macro_prefix(self, value):
-        _type_assert("macro_prefix", value, str)
-        self.__table["macro_prefix"] = value
-
-
-    @property
-    def po_domain(self):
-        """the prefix of the i18n domain"""
-        return self.__table["po_domain"]
-
-    @po_domain.setter
-    def po_domain(self, value):
-        _type_assert("po_domain", value, str)
-        self.__table["po_domain"] = value
-
-
-    @property
-    def witness_c_macro(self):
-        """the C macro that is defined when the sources are compiled or used"""
-        return self.__table["witness_c_macro"]
-
-    @witness_c_macro.setter
-    def witness_c_macro(self, value):
-        _type_assert("witness_c_macro", value, str)
-        self.__table["witness_c_macro"] = value
-
-
-    @property
-    def licenses(self):
-        """abort if modules aren't available under the LGPL; also modify license template"""
-        return self.__table["licenses"]
-
-    @licenses.setter
-    def licenses(self, value):
-        _type_assert("licenses", value, _ITERABLES)
-        result = set()
-        for item in value:
-            _type_assert("license", item, str)
-            result.add(item)
-        self.__table["licenses"] = frozenset(result)
-
-
-    @property
-    def ac_version(self):
-        """minimal supported autoconf version"""
-        return self.__table["ac_version"]
-
-    @ac_version.setter
-    def ac_version(self, value):
-        _type_assert("ac_version", value, str)
-        if _version.LooseVersion(value) < _version.LooseVersion("2.59"):
-            raise _AutoconfVersionError("2.59")
-        self.__table["ac_version"] = value
-
+    def __repr__(self):
+        module = self.__class__.__module__
+        name = self.__class__.__name__
+        table = ", ".join(f"{key}={value}" for (key, value) in self.items())
+        return f"{module}.{name}{{{table}}}"
 
-    @property
-    def ac_file(self):
-        """autoconf file (usually configure.ac or configure.in)"""
-        return self.__table["ac_file"]
 
-    @ac_file.setter
-    def ac_file(self, value):
-        _type_assert("ac_file", value, str)
-        self.__table["ac_file"] = value
+    def __getitem__(self, key):
+        if key not in BaseConfig.__PROPERTIES:
+            key = key.replace("-", "_")
+            if key not in BaseConfig.__PROPERTIES:
+                raise KeyError("unsupported option: {0}".format(key))
+        return getattr(self, key)
 
 
-    @property
-    def modules(self):
-        """list of modules"""
-        return self.__table["modules"]
+    def __setitem__(self, key, value):
+        if key not in BaseConfig.__PROPERTIES:
+            key = key.replace("_", "-")
+            if key not in BaseConfig.__PROPERTIES:
+                raise KeyError("unsupported option: {0}".format(key))
+        return setattr(self, key, value)
 
-    @modules.setter
-    def modules(self, value):
-        _type_assert("modules", value, _ITERABLES)
-        result = set()
-        for item in value:
-            _type_assert("module", item, str)
-            result.add(item)
-        self.__table["modules"] = frozenset(result)
 
+    def __get_option(self, key):
+        return self.__options[key]
 
-    @property
-    def avoids(self):
-        """list of modules to avoid"""
-        return self.__table["avoids"]
+    def __set_option_pure(self, key, value):
+        self.__options[key] = value
 
-    @avoids.setter
-    def avoids(self, value):
-        _type_assert("avoids", value, _ITERABLES)
-        result = set()
-        for item in value:
-            _type_assert("avoid", item, str)
-            result.add(item)
-        self.__table["avoids"] = frozenset(result)
+    def __set_option(self, key, value):
+        self.__active[key] = True
+        return self.__set_option_pure(key, value)
 
 
-    @property
-    def files(self):
-        """list of files to be processed"""
-        return self.__table["files"]
+    def __get_flags(self, mask):
+        return self.__flags & mask
 
-    @files.setter
-    def files(self, value):
-        _type_assert("files", value, _ITERABLES)
-        result = set()
-        for item in value:
-            _type_assert("file", item, str)
-            result.add(item)
-        self.__table["files"] = frozenset(result)
+    def __set_flags_pure(self, mask, state):
+        if state:
+            self.__flags |= mask
+        else:
+            self.__flags &= ~mask
+
+    def __set_flags(self, mask, state):
+        key = {getattr(BaseConfig, key).mask:key for key in BaseConfig.__FLAGS}[mask]
+        self.__active[key] = True
+        return self.__set_flags_pure(mask, state)
+
+
+    root = _PathProperty(
+        fget=lambda self: self.__get_option("root"),
+        fset=lambda self, path: self.__set_option("root", path),
+        doc="target directory (root project directory)",
+    )
+    overrides = _PathListProperty(
+        sorted=False,
+        unique=True,
+        fget=lambda self: self.__get_option("overrides"),
+        fset=lambda self, paths: self.__set_option("overrides", paths),
+        doc="local override directories",
+    )
+    source_base = _PathProperty(
+        fget=lambda self: self.__get_option("source_base"),
+        fset=lambda self, path: self.__set_option("source_base", path),
+        doc="directory relative to ROOT where source code is placed; defaults to 'lib'",
+    )
+    m4_base = _PathProperty(
+        fget=lambda self: self.__get_option("m4_base"),
+        fset=lambda self, path: self.__set_option("m4_base", path),
+        doc="directory relative to ROOT where *.m4 macros are placed; defaults to 'm4'",
+    )
+    po_base = _Property(
+        fget=lambda self: self.__get_option("po_base"),
+        fset=lambda self, path: self.__set_option("po_base", path),
+        check=lambda value: isinstance(value, str),
+        doc="directory relative to ROOT where *.po files are placed; defaults to 'po'",
+    )
+    doc_base = _PathProperty(
+        fget=lambda self: self.__get_option("doc_base"),
+        fset=lambda self, path: self.__set_option("doc_base", path),
+        doc="directory relative to ROOT where doc files are placed; defaults to 'doc'",
+    )
+    tests_base = _PathProperty(
+        fget=lambda self: self.__get_option("tests_base"),
+        fset=lambda self, path: self.__set_option("tests_base", path),
+        doc="directory relative to ROOT where unit tests are placed; defaults to 'tests'",
+    )
+    auxdir = _PathProperty(
+        fget=lambda self: self.__get_option("auxdir"),
+        fset=lambda self, path: self.__set_option("auxdir", path),
+        doc="directory relative to ROOT where auxiliary build tools are placed; defaults to 'build-aux'",
+    )
+    libname = _Property(
+        fget=lambda self: self.__get_option("libname"),
+        fset=lambda self, name: self.__set_option("libname", name),
+        check=lambda value: isinstance(value, str) and value,
+        doc="library name; defaults to 'libgnu'",
+    )
+    makefile_name = _Property(
+        fget=lambda self: self.__get_option("makefile_name"),
+        fset=lambda self, name: self.__set_option("makefile_name", name),
+        check=lambda value: isinstance(value, str) and value,
+        doc="name of makefile in automake syntax in the source-base and tests-base directories",
+    )
+    macro_prefix = _Property(
+        fget=lambda self: self.__get_option("macro_prefix"),
+        fset=lambda self, name: self.__set_option("macro_prefix", name),
+        check=lambda value: isinstance(value, str) and value,
+        doc="the prefix of the macros 'gl_EARLY' and 'gl_INIT'; defaults to 'gl'",
+    )
+    po_domain = _Property(
+        fget=lambda self: self.__get_option("po_domain"),
+        fset=lambda self, name: self.__set_option("po_domain", name),
+        check=lambda value: value is None or isinstance(value, str),
+        doc="the prefix of the i18n domain",
+    )
+    witness_c_macro = _Property(
+        fget=lambda self: self.__get_option("witness_c_macro"),
+        fset=lambda self, name: self.__set_option("witness_c_macro", name),
+        check=lambda value: isinstance(value, str) and (value == "") or not value[0].isdigit(),
+        doc="the C macro that is defined when the sources are compiled or used",
+    )
+    modules = _StringListProperty(
+        sorted=True,
+        unique=True,
+        fget=lambda self: self.__get_option("modules"),
+        fset=lambda self, name: self.__set_option("modules", name),
+        doc="sorted set of the desired modules",
+    )
+    avoids = _StringListProperty(
+        sorted=True,
+        unique=True,
+        fget=lambda self: self.__get_option("avoids"),
+        fset=lambda self, name: self.__set_option("avoids", name),
+        doc="sorted set of the modules that must be avoided",
+    )
+    files = _PathListProperty(
+        sorted=True,
+        unique=True,
+        fget=lambda self: self.__get_option("files"),
+        fset=lambda self, name: self.__set_option("files", name),
+        doc="a list of files to be processed",
+    )
+    copymode = _Property(
+        fget=lambda self: self.__get_option("copymode"),
+        fset=lambda self, mode: self.__set_option("copymode", mode),
+        check=lambda value: value in {None, "hardlink", "symlink"},
+        doc="file copy mode ('symlink', 'hardlink' or None)",
+    )
+    local_copymode = _Property(
+        fget=lambda self: self.__get_option("copymode"),
+        fset=lambda self, mode: self.__set_option("copymode", mode),
+        check=lambda value: value in {None, "hardlink", "symlink"},
+        doc="file copy mode for local directory ('symlink', 'hardlink' or None)",
+    )
+    ac_file = _PathProperty(
+        fget=lambda self: self.__get_option("ac_file"),
+        fset=lambda self, path: self.__set_option("ac_file", path),
+        doc="autoconf file (usually configure.ac or configure.in)",
+    )
+    ac_version = _Property(
+        fget=lambda self: self.__get_option("ac_version"),
+        fset=lambda self, version: self.__set_option("ac_version", str(_version.LooseVersion(version))),
+        doc="minimal supported autoconf version",
+    )
+    licenses = _StringListProperty(
+        sorted=True,
+        unique=True,
+        fget=lambda self: self.__get_option("licenses"),
+        fset=lambda self, name: self.__set_option("licenses", name),
+        doc="acceptable licenses for modules",
+    )
+    tests = _BitwiseProperty(
+        mask=(1 << 0),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="include unit tests for the included modules?",
+    )
+    obsolete = _BitwiseProperty(
+        mask=(1 << 1),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="include obsolete modules when they occur among the modules?",
+    )
+    cxx_tests = _BitwiseProperty(
+        mask=(1 << 2),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="include even unit tests for C++ interoperability?",
+    )
+    longrunning_tests = _BitwiseProperty(
+        mask=(1 << 3),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="include even unit tests for C++ interoperability?",
+    )
+    privileged_tests = _BitwiseProperty(
+        mask=(1 << 4),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="include even unit tests that require root privileges?",
+    )
+    unportable_tests = _BitwiseProperty(
+        mask=(1 << 5),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="include even unit tests that fail on some platforms?",
+    )
+    mask = _BitwiseProperty(
+        mask=(obsolete.mask | cxx_tests.mask | longrunning_tests.mask | privileged_tests.mask | unportable_tests.mask),
+        fget=lambda self, mask: self.__get_flags(mask),
+        doc="configuration acceptibility mask",
+    )
+    all_tests = _BitwiseProperty(
+        mask=(tests.mask | cxx_tests.mask | longrunning_tests.mask | privileged_tests.mask | unportable_tests.mask),
+        fget=lambda self, mask: bool(self.__get_flags(mask) == mask),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="include all kinds of unit tests?",
+    )
+    libtool = _BitwiseProperty(
+        mask=(1 << 6),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="use libtool rules?",
+    )
+    conditionals = _BitwiseProperty(
+        mask=(1 << 7),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="support conditional dependencies (may save configure time and object code)?",
+    )
+    copyrights = _BitwiseProperty(
+        mask=(1 << 8),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="update the license copyright text?",
+    )
+    gnumake = _BitwiseProperty(
+        mask=(1 << 9),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="output for GNU Make instead of for the default Automake?",
+    )
+    single_configure = _BitwiseProperty(
+        mask=(1 << 10),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="generate a single configure file?",
+    )
+    vc_files = _BitwiseProperty(
+        mask=(1 << 11),
+        fget=lambda self, mask: bool(self.__get_flags(mask)),
+        fset=lambda self, mask, state: self.__set_flags(mask, state),
+        doc="update version control related files?",
+    )
 
 
     @property
     def include_guard_prefix(self):
         """include guard prefix"""
-        prefix = self.__table["macro_prefix"].upper()
-        default = BaseConfig._TABLE["macro_prefix"].upper()
-        return "GL" if prefix == default else "GL_{0}".format(prefix)
-
-
-    @property
-    def copymode(self):
-        """file copy mode ('symlink', 'hardlink' or None)"""
-        return self.__table["copymode"]
-
-    @copymode.setter
-    def copymode(self, value):
-        if value not in frozenset({None, "symlink", "hardlink"}):
-            raise ValueError("copymode: None, 'symlink' or 'hardlink'")
-        self.__table["copymode"] = value
-
-
-    @property
-    def local_copymode(self):
-        """file copy mode for local directory ('symlink', 'hardlink' or None)"""
-        return self.__table["local_copymode"]
-
-    @local_copymode.setter
-    def local_copymode(self, value):
-        if value not in frozenset({None, "symlink", "hardlink"}):
-            raise ValueError("local_copymode: None, 'symlink' or 'hardlink'")
-        self.__table["local_copymode"] = value
+        prefix = self.macro_prefix.upper()
+        return "GL" if prefix == "GL" else f"GL_{prefix}"
 
 
-    @property
-    def tests(self):
-        """include unit tests for the included modules?"""
-        return bool(self.__options & BaseConfig._Option.Tests)
-
-    @tests.setter
-    def tests(self, value):
-        _type_assert("tests", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.Tests
-        else:
-            self.__options &= ~BaseConfig._Option.Tests
-
-
-    @property
-    def obsolete(self):
-        """include obsolete modules when they occur among the modules?"""
-        return bool(self.__options & BaseConfig._Option.Obsolete)
-
-    @obsolete.setter
-    def obsolete(self, value):
-        _type_assert("obsolete", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.Obsolete
-        else:
-            self.__options &= ~BaseConfig._Option.Obsolete
-
-
-    @property
-    def cxx_tests(self):
-        """include even unit tests for C++ interoperability?"""
-        return bool(self.__options & BaseConfig._Option.CXX)
-
-    @cxx_tests.setter
-    def cxx_tests(self, value):
-        _type_assert("cxx_tests", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.CXX
-        else:
-            self.__options &= ~BaseConfig._Option.CXX
-
-
-    @property
-    def longrunning_tests(self):
-        """include even unit tests that are long-runners?"""
-        return bool(self.__options & BaseConfig._Option.Longrunning)
-
-    @longrunning_tests.setter
-    def longrunning_tests(self, value):
-        _type_assert("longrunning_tests", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.Longrunning
-        else:
-            self.__options &= ~BaseConfig._Option.Longrunning
-
-
-    @property
-    def privileged_tests(self):
-        """include even unit tests that require root privileges?"""
-        return bool(self.__options & BaseConfig._Option.Privileged)
-
-    @privileged_tests.setter
-    def privileged_tests(self, value):
-        _type_assert("privileged_tests", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.Privileged
-        else:
-            self.__options &= ~BaseConfig._Option.Privileged
-
-
-    @property
-    def unportable_tests(self):
-        """include even unit tests that fail on some platforms?"""
-        return bool(self.__options & BaseConfig._Option.Unportable)
-
-    @unportable_tests.setter
-    def unportable_tests(self, value):
-        _type_assert("unportable_tests", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.Unportable
-        else:
-            self.__options &= ~BaseConfig._Option.Unportable
-
-
-    @property
-    def all_tests(self):
-        """include all kinds of problematic unit tests?"""
-        return (self.__options & BaseConfig._Option.AllTests) == BaseConfig._Option.AllTests
-
-    @all_tests.setter
-    def all_tests(self, value):
-        if value:
-            self.__options |= BaseConfig._Option.AllTests
-        else:
-            self.__options &= BaseConfig._Option.AllTests
-
-
-    @property
-    def libtool(self):
-        """use libtool rules?"""
-        return bool(self.__options & BaseConfig._Option.Libtool)
-
-    @libtool.setter
-    def libtool(self, value):
-        _type_assert("libtool", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.Libtool
-        else:
-            self.__options &= ~BaseConfig._Option.Libtool
-
-
-    @property
-    def conditionals(self):
-        """support conditional dependencies (may save configure time and object code)?"""
-        return bool(self.__options & BaseConfig._Option.Conditionals)
-
-    @conditionals.setter
-    def conditionals(self, value):
-        _type_assert("conditionals", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.Conditionals
-        else:
-            self.__options &= ~BaseConfig._Option.Conditionals
-
-
-    @property
-    def copyrights(self):
-        """update the license copyright text?"""
-        return bool(self.__options & BaseConfig._Option.Copyrights)
-
-    @copyrights.setter
-    def copyrights(self, value):
-        _type_assert("copyrights", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.Copyrights
-        else:
-            self.__options &= ~BaseConfig._Option.Copyrights
-
-
-    @property
-    def gnumake(self):
-        """update the license copyright text?"""
-        return bool(self.__options & BaseConfig._Option.GNUMake)
-
-    @gnumake.setter
-    def gnumake(self, value):
-        _type_assert("gnumake", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.GNUMake
-        else:
-            self.__options &= ~BaseConfig._Option.GNUMake
-
-
-    @property
-    def single_configure(self):
-        """generate a single configure file?"""
-        return bool(self.__options & BaseConfig._Option.SingeConfigure)
-
-    @single_configure.setter
-    def single_configure(self, value):
-        _type_assert("single_configure", value, bool)
-        if value:
-            self.__options |= BaseConfig._Option.SingeConfigure
-        else:
-            self.__options &= ~BaseConfig._Option.SingeConfigure
-
-
-    @property
-    def vc_files(self):
-        """update version control related files?"""
-        return self.__table["vc_files"]
-
-    @vc_files.setter
-    def vc_files(self, value):
-        _type_assert("vc_files", value, bool)
-        self.__table["vc_files"] = value
-
-
-    def __getitem__(self, key):
-        if key not in KEYS:
+    def explicit(self, key):
+        """Determine if option is set to a non-default value."""
+        if key not in BaseConfig.__PROPERTIES:
             key = key.replace("-", "_")
-            if key not in KEYS:
-                raise KeyError("unsupported option: {0}".format(key))
-        return getattr(self, key)
-
-
-    def __setitem__(self, key, value):
-        if key not in KEYS:
-            key = key.replace("_", "-")
-            if key not in KEYS:
+            if key not in BaseConfig.__PROPERTIES:
                 raise KeyError("unsupported option: {0}".format(key))
-        return setattr(self, key, value)
+        return self.__active.get(key, False)
 
 
     def items(self):
         """a set-like object providing a view on configuration items"""
-        return self.__table.items()
+        for key in BaseConfig.__PROPERTIES:
+            value = self[key]
+            yield (key, value)
 
 
     def keys(self):
         """a set-like object providing a view on configuration keys"""
-        return self.__table.keys()
+        return iter(BaseConfig.__PROPERTIES)
 
 
     def values(self):
         """a set-like object providing a view on configuration values"""
-        return self.__table.values()
+        for key in BaseConfig.__PROPERTIES:
+            yield self[key]
 
 
 
 class CachedConfig(BaseConfig):
     """gnulib cached configuration"""
-    _COMMENTS = _compile(r"((?:(?:#)|(?:^dnl\s+)|(?:\s+dnl\s+)).*?)$")
-    _AUTOCONF = {
+    __slots__ = ()
+
+
+    __COMMENTS = _compile(r"((?:(?:#)|(?:^dnl\s+)|(?:\s+dnl\s+)).*?)$")
+    __AUTOCONF = {
         "ac_version" : _compile(r"AC_PREREQ\(\[(.*?)\]\)"),
         "auxdir"     : _compile(r"AC_CONFIG_AUX_DIR\(\[(.*?)\]\)$"),
         "libtool"    : _compile(r"A[CM]_PROG_LIBTOOL")
     }
-    _GNULIB_CACHE = {
+    __GNULIB_CACHE = {
         "overrides"         : (list, _compile(r"gl_LOCAL_DIR\(\[(.*?)\]\)")),
         "libtool"           : (bool, _compile(r"gl_LIBTOOL\(\[(.*?)\]\)")),
         "conditionals"      : (bool, _compile(r"gl_CONDITIONAL_DEPENDENCIES\(\[(.*?)\]\)")),
@@ -692,26 +478,34 @@ class CachedConfig(BaseConfig):
     }
 
 
-    def __init__(self, root, ac_file=None, **kwargs):
+    def __init__(self, root, gnulib_comp=False, gnulib_cache=False, ac_file=None, **kwargs):
         if ac_file is None:
             ac_file = "configure.ac"
-        _type_assert("ac_file", ac_file, str)
+        if not isinstance(ac_file, str):
+            raise TypeError("ac_file: str expected")
 
         ac_path = _os.path.join(root, ac_file)
         if not _os.path.exists(ac_path):
             ac_file = "configure.in"
             ac_path = _os.path.join(root, ac_file)
-        ac_path = _os.path.normpath(ac_path)
         super().__init__(root=root, **kwargs)
         self.__autoconf(ac_path, kwargs)
-        self.__gnulib_cache(kwargs)
-        self.__gnulib_comp(kwargs)
+        try:
+            self.__gnulib_cache(kwargs)
+        except FileNotFoundError:
+            if gnulib_cache:
+                raise
+        try:
+            self.__gnulib_comp(kwargs)
+        except FileNotFoundError:
+            if gnulib_comp:
+                raise
 
 
     def __autoconf(self, ac_path, explicit):
         with _codecs.open(ac_path, "rb", "UTF-8") as stream:
-            data = CachedConfig._COMMENTS.sub("", stream.read())
-        for (key, pattern) in CachedConfig._AUTOCONF.items():
+            data = CachedConfig.__COMMENTS.sub("", stream.read())
+        for (key, pattern) in CachedConfig.__AUTOCONF.items():
             match = pattern.findall(data)
             if match and key not in explicit:
                 self[key] = match[-1]
@@ -722,8 +516,8 @@ class CachedConfig(BaseConfig):
         if not _os.path.exists(path):
             raise FileNotFoundError(path)
         with _codecs.open(path, "rb", "UTF-8") as stream:
-            data = CachedConfig._COMMENTS.sub("", stream.read())
-        for (key, (typeid, pattern)) in CachedConfig._GNULIB_CACHE.items():
+            data = CachedConfig.__COMMENTS.sub("", stream.read())
+        for (key, (typeid, pattern)) in CachedConfig.__GNULIB_CACHE.items():
             match = pattern.findall(data)
             if match and key not in explicit:
                 if key == "licenses":
@@ -749,7 +543,7 @@ class CachedConfig(BaseConfig):
         if not _os.path.exists(path):
             raise FileNotFoundError(path)
         with _codecs.open(path, "rb", "UTF-8") as stream:
-            data = CachedConfig._COMMENTS.sub("", stream.read())
+            data = CachedConfig.__COMMENTS.sub("", stream.read())
         pattern = _compile(r"AC_DEFUN\(\[{0}_FILE_LIST\], \[(.*?)\]\)".format(macro_prefix))
         match = pattern.findall(data)
         if match and "files" not in explicit:
index 933d28732b5a976eb1fbe45ac23a85eb0be3fefa..1c667fb4fbf835b378c9a49de711d985fdfdabc5 100644 (file)
@@ -4,25 +4,6 @@
 
 
 
-def type_assert(key, value, types):
-    """panic if value has a type different than mentioned in types"""
-    typeset = []
-    if isinstance(types, type):
-        types = [types]
-    types = tuple(types)
-    if not isinstance(value, types):
-        for typeid in types:
-            module = typeid.__module__
-            name = typeid.__name__
-            if module == "builtins":
-                typeset += [name]
-            else:
-                typeset += [module + "." + name]
-        typeset = "{0}".format(", ".join(typeset))
-        raise TypeError("{0}: {1} expected".format(key, typeset))
-
-
-
 class AutoconfVersionError(Exception):
     """minimum supported autoconf version mismatch"""
     def __init__(self, version):
index 8b2be1c3c4aba3e13e9d8f760d2e06ffa2e8a85c..274abcca799046b8eb1a581602e2f80f0ced2f33 100644 (file)
@@ -11,7 +11,6 @@ import subprocess as _sp
 from datetime import datetime as _datetime
 
 
-from .error import type_assert as _type_assert
 from .config import BaseConfig as _BaseConfig
 from .module import BaseModule as _BaseModule
 from .module import Database as _Database
@@ -98,7 +97,6 @@ __PO_MAKE_VARS = (
 
 def po_make_vars(config, **override):
     """Generate PO Makefile parameterization."""
-    _type_assert("config", config, _BaseConfig)
     config = _BaseConfig(**config)
     for (key, value) in override.items():
         config[key] = value
@@ -122,7 +120,6 @@ def po_make_vars(config, **override):
 
 def POTFILES(config, files, **override):
     """Generate file list to be passed to xgettext."""
-    _type_assert("config", config, _BaseConfig)
     config = _BaseConfig(**config)
     for (key, value) in override.items():
         config[key] = value
@@ -138,30 +135,17 @@ def POTFILES(config, files, **override):
 
 def autoconf_snippet(config, module, toplevel, no_libtool, no_gettext, **override):
     """
-    Generate autoconf snippet for a standalone module.
-
-    <config> gnulib configuration
-    <module> gnulib module instance
-    <toplevel> make a subordinate use of gnulib if False
-    <no_libtool> disable libtool (regardless of configuration)
-    <no_gettext> disable AM_GNU_GETTEXT invocations if True
-    """
-    _type_assert("config", config, _BaseConfig)
-    _type_assert("module", module, _BaseModule)
-    _type_assert("toplevel", toplevel, bool)
-    _type_assert("no_libtool", no_libtool, bool)
-    _type_assert("no_gettext", no_gettext, bool)
+    Generate autoconf snippet for a standalone module."""
     config = _BaseConfig(**config)
     for (key, value) in override.items():
         config[key] = value
 
-    libtool = config.libtool and not no_libtool
     gettext = not no_gettext
     if module.name not in ("gnumakefile", "maintainer-makefile") or toplevel:
         snippet = module.autoconf_snippet
         include_guard_prefix = config.include_guard_prefix
         snippet.replace(r"${gl_include_guard_prefix}", include_guard_prefix)
-        if not config.libtool:
+        if no_libtool:
             table = (
                 (r"$gl_cond_libtool", "false"),
                 (r"gl_libdeps", "gltests_libdeps"),
@@ -169,7 +153,7 @@ def autoconf_snippet(config, module, toplevel, no_libtool, no_gettext, **overrid
             )
             for (src, dst) in table:
                 snippet = snippet.replace(src, dst)
-        if nogettext:
+        if no_gettext:
             src = "AM_GNU_GETTEXT([external])"
             dst = "dnl you must add AM_GNU_GETTEXT([external]) or similar to configure.ac.'"
             snippet = snippet.replace(src, dst)
@@ -180,7 +164,7 @@ def autoconf_snippet(config, module, toplevel, no_libtool, no_gettext, **overrid
         lines = filter(lambda line: line.strip(), snippet.split("\n"))
         for line in lines:
             yield line
-        if module.name == "alloca" and libtool:
+        if module.name == "alloca" and config.libtool and not no_libtool:
             yield "changequote(,)dnl"
             yield "LTALLOCA=`echo \"$ALLOCA\" | sed -e 's/\\.[^.]* /.lo /g;s/\\.[^.]*$/.lo/'`"
             yield "changequote([, ])dnl"
@@ -188,23 +172,8 @@ def autoconf_snippet(config, module, toplevel, no_libtool, no_gettext, **overrid
 
 
 
-def autoconf_snippet_sequence(config, database, modules, toplevel, no_libtool, no_gettext, **override):
-    """
-    Generate an autoconf snippet for multiple modules.
-
-    <config> gnulib configuration
-    <module> gnulib module instance
-    <toplevel> make a subordinate use of gnulib if False
-    <no_libtool> disable libtool (regardless of configuration)
-    <no_gettext> disable AM_GNU_GETTEXT invocations if True
-    [macro_prefix] the prefix of the macros 'gl_EARLY' and 'gl_INIT'
-    """
-    _type_assert("config", config, _BaseConfig)
-    _type_assert("database", database, _Database)
-    _type_assert("modules", modules, _ITERABLES)
-    _type_assert("toplevel", toplevel, bool)
-    _type_assert("no_libtool", no_libtool, bool)
-    _type_assert("no_gettext", no_gettext, bool)
+def autoconf_snippets(config, database, modules, toplevel, no_libtool, no_gettext, **override):
+    """Generate an autoconf snippet for multiple modules."""
     config = _BaseConfig(**config)
     for (key, value) in override.items():
         config[key] = value
@@ -326,12 +295,7 @@ __INIT_MACRO_HEADER = (
 )
 
 def init_macro_header(config, **override):
-    """
-    Generate the first few statements of the gl_INIT macro.
-
-    [macro_prefix] the prefix of the macros 'gl_EARLY' and 'gl_INIT'
-    """
-    _type_assert("config", config, _BaseConfig)
+    """Generate the first few statements of the gl_INIT macro."""
     config = _BaseConfig(**config)
     for (key, value) in override.items():
         config[key] = value
@@ -382,7 +346,6 @@ __INIT_MACRO_FOOTER = (
 
 def init_macro_footer(config, **override):
     """Generate the last few statements of the gl_INIT macro."""
-    _type_assert("config", config, _BaseConfig)
     config = _BaseConfig(**config)
     for (key, value) in override.items():
         config[key] = value
@@ -429,7 +392,6 @@ def init_macro_done(config, **override):
 
     [macro_prefix] the prefix of the macros 'gl_EARLY' and 'gl_INIT'
     """
-    _type_assert("config", config, _BaseConfig)
     config = _BaseConfig(**config)
     for (key, value) in override.items():
         config[key] = value
@@ -441,7 +403,7 @@ def init_macro_done(config, **override):
 
 __COMMAND_LINE_PATHS = (
     ("libname", lambda k, v, d: f"--lib={v}"),
-    ("source_base", lambda k, v, d: f"--source_base={v}"),
+    ("source_base", lambda k, v, d: f"--source-base={v}"),
     ("m4_base", lambda k, v, d: f"--m4-base={v}"),
     ("po_base", lambda k, v, d: f"--po-base={v}" if k in d else None),
     ("doc_base", lambda k, v, d: f"--doc-base={v}"),
@@ -457,21 +419,18 @@ __COMMAND_LINE_TESTS = (
     ("all_tests", lambda v: "--with-all-tests" if v else None),
 )
 __COMMAND_LINE_MISC = (
-    ("conditionals", lambda k, v, d: ("--no", "--")[v] + "conditional-dependencies"),
-    ("libtool", lambda k, v, d: ("--no", "--")[v] + "libtool"),
+    ("makefile_name", lambda k, v, d: f"--makefile-name={v}"),
+    ("conditionals", lambda k, v, d: ("--no-", "--")[v] + "conditional-dependencies"),
+    ("libtool", lambda k, v, d: ("--no-", "--")[v] + "libtool"),
     ("macro_prefix", lambda k, v, d: f"--macro-prefix={v}"),
     ("gnumake", lambda k, v, d: "--gnu-make" if v else None),
-    ("makefile_name", lambda k, v, d: f"--makefile-name={v}"),
     ("po_domain", lambda k, v, d: f"--po-domain={v}" if k in d else None),
     ("witness_c_macro", lambda k, v, d: f"--witness-c-macro={v}" if k in d else None),
-    ("vc_files", lambda k, v, d: ("--no", "--")[v] + "vc-files" if k in d else None),
+    ("vc_files", lambda k, v, d: ("--no-", "--")[v] + "vc-files" if k in d else None),
 )
 
 def command_line(config, explicit, **override):
     """Generate gnulib command-line invocation."""
-    _type_assert("config", config, _BaseConfig)
-    _type_assert("explicit", explicit, _ITERABLES)
-
     yield "gnulib-tool --import"
     for path in config.overrides:
         yield f"--local-dir={path}"
@@ -487,7 +446,7 @@ def command_line(config, explicit, **override):
             yield option
     for module in config.avoids:
         yield "--avoid={module.name}"
-    if config.licenses in _LGPL:
+    if frozenset(config.licenses) in _LGPL:
         lgpl = _LGPL[config.licenses]
         if lgpl != "yes":
             yield f"--lgpl={lgpl}"
@@ -498,8 +457,8 @@ def command_line(config, explicit, **override):
         option = hook(key, value, explicit)
         if option is not None:
             yield option
-    for module in sorted(config.modules):
-        yield "{}".format(module)
+    for module in config.modules:
+        yield f"{module}"
 
 
 
@@ -545,23 +504,14 @@ def _lib_makefile_callback(conditionals, gnumake):
     return callbacks[conditionals][gnumake]
 
 
-def lib_makefile(path, config, explicit, database, modules, mkedits, testing, **override):
+def lib_makefile(path, config, explicit, modules, mkedits, testing, **override):
     """Generate library Makefile.am file."""
-    _type_assert("path", path, str)
-    _type_assert("config", config, _BaseConfig)
-    _type_assert("explicit", explicit, _ITERABLES)
-    _type_assert("database", database, _Database)
-    _type_assert("modules", modules, _ITERABLES)
-    _type_assert("mkedits", mkedits, _ITERABLES)
-    _type_assert("testing", testing, bool)
-
     date = _datetime.now()
-    kwargs = {
-        "libname": config.libname,
-        "macro_prefix": config.macro_prefix,
-        "libext": "la" if config.libtool else "a",
-        "perhaps_LT": "LT" if config.libtool else "",
-    }
+    libname = config.libname
+    po_domain = config.po_domain
+    macro_prefix = config.macro_prefix
+    libext = "la" if config.libtool else "a"
+    perhaps_LT = "LT" if config.libtool else ""
     assign = "+=" if config.gnumake or "makefile_name" in explicit else "="
     eliminate_LDFLAGS = True if config.libtool else False
 
@@ -573,7 +523,7 @@ def lib_makefile(path, config, explicit, database, modules, mkedits, testing, **
 
     yield "## DO NOT EDIT! GENERATED AUTOMATICALLY!"
     yield "## Process this file with automake to produce Makefile.in."
-    yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
+    yield f"# Copyright (C) 2002-{date.year} Free Software Foundation, Inc."
     for line in __DISCLAIMER[1:]:
         yield line
     yield ""
@@ -584,14 +534,14 @@ def lib_makefile(path, config, explicit, database, modules, mkedits, testing, **
     # similarly, the IRIX 6.5 awk fails if a line has length >= 3072.
     actioncmd = " ".join(command_line(config, explicit))
     if len(actioncmd) <= 3000:
-        yield "# Reproduce by: {}".format(actioncmd)
+        yield f"# Reproduce by: {actioncmd}"
     yield ""
 
     callback = _lib_makefile_callback(config.conditionals, config.gnumake)
     def _snippet():
         lines = []
         subdirs = False
-        for module in database.main_modules:
+        for module in modules:
             if module.test:
                 continue
             conditional = module.conditional_automake_snippet
@@ -599,21 +549,21 @@ def lib_makefile(path, config, explicit, database, modules, mkedits, testing, **
             conditional = conditional.replace("lib_LTLIBRARIES", "lib%_LTLIBRARIES")
             if eliminate_LDFLAGS:
                 conditional = __MAKEFILE_LDFLAGS.sub("", conditional)
-            conditional = __MAKEFILE_LIBNAME.sub("{libname}_{libext}_\\1".format(**kwargs), conditional)
+            conditional = __MAKEFILE_LIBNAME.sub(f"{libname}_{libext}_\\1", conditional)
             conditional = conditional.replace("lib%_LIBRARIES", "lib_LIBRARIES")
             conditional = conditional.replace("lib%_LTLIBRARIES", "lib_LTLIBRARIES")
             if transform_check_PROGRAMS:
                 conditional = conditional.replace("check_PROGRAMS", "noinst_PROGRAMS")
             conditional = conditional.replace(r"${gl_include_guard_prefix}", config.include_guard_prefix)
             unconditional = module.unconditional_automake_snippet.format(auxdir=config.auxdir)
-            unconditional = __MAKEFILE_LIBNAME.sub("{libname}_{libext}_\\1".format(**kwargs), unconditional)
+            unconditional = __MAKEFILE_LIBNAME.sub(f"{libname}_{libext}_\\1", unconditional)
             if (conditional + unconditional).strip():
-                lines.append("## begin gnulib module {}".format(module.name))
+                lines.append(f"## begin gnulib module {module.name}")
                 if module.name == "alloca":
-                    lines.append("{libname}_{libext}_LIBADD += @{perhaps_LT}ALLOCA@".format(**kwargs))
-                    lines.append("{libname}_{libext}_DEPENDENCIES += @{perhaps_LT}ALLOCA@".format(**kwargs))
+                    lines.append(f"{libname}_{libext}_LIBADD += @{perhaps_LT}ALLOCA@")
+                    lines.append(f"{libname}_{libext}_DEPENDENCIES += @{perhaps_LT}ALLOCA@")
                 lines += list(callback(module, conditional, unconditional, config.macro_prefix))
-                lines.append("## end   gnulib module {}".format(module.name))
+                lines.append(f"## end   gnulib module {module.name}")
                 lines.append("")
             subdirs |= any(__MAKEFILE_SUBDIRS.match(file) for file in module.files)
         return (subdirs, lines)
@@ -640,7 +590,7 @@ def lib_makefile(path, config, explicit, database, modules, mkedits, testing, **
         yield "EXTRA_DIST ="
         yield "BUILT_SOURCES ="
         yield "SUFFIXES ="
-    yield "MOSTLYCLEANFILES {} core *.stackdump".format(assign)
+    yield f"MOSTLYCLEANFILES {assign} core *.stackdump"
     if "makefile_name" not in explicit:
         yield "MOSTLYCLEANDIRS ="
         yield "CLEANFILES ="
@@ -661,7 +611,7 @@ def lib_makefile(path, config, explicit, database, modules, mkedits, testing, **
             else:
                 yield "== gnulib-tool GNU Make output failed as follows =="
                 for line in stderr.splitlines():
-                    yield "# stderr: {}".format(line)
+                    yield f"# stderr: {line}"
         yield "# End of GNU Make output."
     else:
         yield "# No GNU Make output."
@@ -677,11 +627,11 @@ def lib_makefile(path, config, explicit, database, modules, mkedits, testing, **
     ))
     if "makefile_name" not in explicit:
         yield ""
-        yield "AM_CPPFLAGS ={}".format(cppflags)
+        yield f"AM_CPPFLAGS ={cppflags}"
         yield "AM_CFLAGS ="
     elif "".join(cppflags):
         yield ""
-        yield "AM_CPPFLAGS +={}".format(cppflags)
+        yield f"AM_CPPFLAGS +={cppflags}"
     yield ""
 
     snippet = "\n".join(lines)
@@ -694,36 +644,35 @@ def lib_makefile(path, config, explicit, database, modules, mkedits, testing, **
     # installation location for the library. Don't confuse automake by saying
     # it should not be installed.
     # By default, the generated library should not be installed.
-    regex = "^[a-zA-Z0-9_]*_{perhaps_LT}LIBRARIES\\s*\\+?\\=\\s*{libname}\\.{libext}$"
-    pattern = _re.compile(regex.format(**kwargs), _re.S)
+    pattern = _re.compile(f"^[a-zA-Z0-9_]*_{perhaps_LT}LIBRARIES\\s*\\+?\\=\\s*{libname}\\.{libext}$", _re.S)
     if not pattern.findall(snippet):
-        yield "noinst_{perhaps_LT}LIBRARIES += {libname}.{libext}".format(**kwargs)
+        yield f"noinst_{perhaps_LT}LIBRARIES += {libname}.{libext}"
 
     yield ""
-    yield "{libname}_{libext}_SOURCES =".format(**kwargs)
+    yield f"{libname}_{libext}_SOURCES ="
     # Here we use $(LIBOBJS), not @LIBOBJS@. The value is the same. However,
     # automake during its analysis looks for $(LIBOBJS), not for @LIBOBJS@.
-    yield "{libname}_{libext}_LIBADD = $({macro_prefix}_{perhaps_LT}LIBOBJS)".format(**kwargs)
-    yield "{libname}_{libext}_DEPENDENCIES = $({macro_prefix}_{perhaps_LT}LIBOBJS)".format(**kwargs)
-    yield "EXTRA_{libname}_{libext}_SOURCES =".format(**kwargs)
+    yield f"{libname}_{libext}_LIBADD = $({macro_prefix}_{perhaps_LT}LIBOBJS)"
+    yield f"{libname}_{libext}_DEPENDENCIES = $({macro_prefix}_{perhaps_LT}LIBOBJS)"
+    yield f"EXTRA_{libname}_{libext}_SOURCES ="
     if config.libtool:
-        yield "{libname}_{libext}_LDFLAGS = $(AM_LDFLAGS)".format(**kwargs)
-        yield "{libname}_{libext}_LDFLAGS += -no-undefined".format(**kwargs)
+        yield f"{libname}_{libext}_LDFLAGS = $(AM_LDFLAGS)"
+        yield f"{libname}_{libext}_LDFLAGS += -no-undefined"
         # Synthesize an ${libname}_${libext}_LDFLAGS augmentation by combining
         # the link dependencies of all modules.
         def _directives(modules):
-            directives = (module.link_directive for module in sorted(modules))
-            for directive in filter(lambda directive: directive.strip(), directives):
-                index = directive.find("when linking with libtool")
-                if index != -1:
-                    directive = directive[:index].strip(" ")
-                yield directive
-        for directive in _directives(database.main_modules):
-            yield "{libname}_{libext}_LDFLAGS += {directive}".format(directive=directive, **kwargs)
+            for module in modules:
+                for directive in module.link_directives:
+                    index = directive.find("when linking with libtool")
+                    if index != -1:
+                        directive = directive[:index].strip(" ")
+                    yield directive
+        for directive in sorted(set(_directives(modules))):
+            yield f"{libname}_{libext}_LDFLAGS += {directive}"
     yield ""
 
     if "po_base" in explicit:
-        yield "AM_CPPFLAGS += -DDEFAULT_TEXT_DOMAIN=\\\"{}-gnulib\\\"".format(config.po_domain)
+        yield f"AM_CPPFLAGS += -DDEFAULT_TEXT_DOMAIN=\\\"{po_domain}-gnulib\\\""
         yield ""
 
     for line in lines:
@@ -747,24 +696,17 @@ def _tests_makefile_callback(gnumake):
         yield snippet
 
     def _gnumake(module, snippet):
-        yield "ifeq (,$(OMIT_GNULIB_MODULE_{}))".format(module.name)
+        yield f"ifeq (,$(OMIT_GNULIB_MODULE_{module.name}))"
         yield ""
         yield snippet
         yield "endif"
 
     return _gnumake if gnumake else _automake
 
-def tests_makefile(path, config, explicit, database, modules, mkedits, testing):
+def tests_makefile(path, config, explicit, modules, mkedits, testing, libtests):
     """Generate tests Makefile.am file."""
-    _type_assert("path", path, str)
-    _type_assert("config", config, _BaseConfig)
-    _type_assert("explicit", explicit, _ITERABLES)
-    _type_assert("database", database, _Database)
-    _type_assert("modules", modules, _ITERABLES)
-    _type_assert("mkedits", mkedits, _ITERABLES)
-    _type_assert("testing", testing, bool)
-
-    if testing and not config.single_configure:
+    single_configure = config.single_configure
+    if testing and not single_configure:
         modules = sorted(filter(lambda module: module.test, modules))
 
     date = _datetime.now()
@@ -775,12 +717,6 @@ def tests_makefile(path, config, explicit, database, modules, mkedits, testing):
     witness_c_macro = config.witness_c_macro
     tests_base_inverse = "/".join(".." for _ in config.tests_base.split(_os.path.sep))
     (libname, libext) = (config.libname, "la" if config.libtool else "a")
-    kwargs = {
-        "libname": config.libname,
-        "macro_prefix": config.macro_prefix,
-        "libext": "la" if config.libtool else "a",
-        "perhaps_LT": "LT" if config.libtool else "",
-    }
     assign = "+=" if config.gnumake or "makefile_name" in explicit else "="
     eliminate_LDFLAGS = True if config.libtool else False
 
@@ -792,7 +728,7 @@ def tests_makefile(path, config, explicit, database, modules, mkedits, testing):
 
     yield "## DO NOT EDIT! GENERATED AUTOMATICALLY!"
     yield "## Process this file with automake to produce Makefile.in."
-    yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
+    yield f"# Copyright (C) 2002-{date.year} Free Software Foundation, Inc."
     for line in __DISCLAIMER[1:]:
         yield line
     yield ""
@@ -815,14 +751,14 @@ def tests_makefile(path, config, explicit, database, modules, mkedits, testing):
             if transform_check_PROGRAMS:
                 snippet = snippet.replace("check_PROGRAMS", "noinst_PROGRAMS")
             snippet = snippet.replace(r"${gl_include_guard_prefix}", config.include_guard_prefix)
-            if database.libtests and module.name == "alloca":
+            if libtests and module.name == "alloca":
                 lines += ["libtests_a_LIBADD += @ALLOCA@"]
                 lines += ["libtests_a_DEPENDENCIES += @ALLOCA@"]
             subdirs |= any(__MAKEFILE_SUBDIRS.match(file) for file in module.files)
             if snippet.strip():
-                lines.append("## begin gnulib module {}".format(module.name))
+                lines.append(f"## begin gnulib module {module.name}")
                 lines += list(callback(module, snippet))
-                lines.append("## end   gnulib module {}".format(module.name))
+                lines.append(f"## end   gnulib module {module.name}")
                 lines.append("")
             if module.longrunning_test:
                 longrunning += lines
@@ -856,7 +792,7 @@ def tests_makefile(path, config, explicit, database, modules, mkedits, testing):
     yield "EXTRA_PROGRAMS ="
     yield "noinst_HEADERS ="
     yield "noinst_LIBRARIES ="
-    if database.libtests:
+    if libtests:
         if testing:
             yield "noinst_LIBRARIES += libtests.a"
         else:
@@ -906,12 +842,12 @@ def tests_makefile(path, config, explicit, database, modules, mkedits, testing):
     # module whose dependency to 'progname' is voluntarily omitted).
     # The LIBTESTS_LIBDEPS can be passed to the linker once or twice, it does
     # not matter.
-    local_ldadd_before = " libtests.a" if database.libtests else ""
-    local_ldadd_after = "  libtests.a $(LIBTESTS_LIBDEPS)" if database.libtests else ""
+    local_ldadd_before = " libtests.a" if libtests else ""
+    local_ldadd_after = "  libtests.a $(LIBTESTS_LIBDEPS)" if libtests else ""
     yield f"LDADD ={local_ldadd_before} {tests_base_inverse}/{source_base}/{libname}.{libext}{local_ldadd_after}"
     yield ""
 
-    if database.libtests:
+    if libtests:
         yield "libtests_a_SOURCES ="
         # Here we use $(LIBOBJS), not @LIBOBJS@. The value is the same. However,
         # automake during its analysis looks for $(LIBOBJS), not for @LIBOBJS@.
@@ -954,16 +890,14 @@ __GNULIB_CACHE_OPTIONS = (
     ("privileged", "gl_WITH_PRIVILEGED_TESTS"),
 )
 
-def gnulib_cache(config):
+def gnulib_cache(config, explicit):
     """
     Generate gnulib-cache.m4 file.
     """
-    _type_assert("config", config, _BaseConfig)
-
     date = _datetime.now()
     yield "## DO NOT EDIT! GENERATED AUTOMATICALLY!"
     yield "## Process this file with automake to produce Makefile.in."
-    yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
+    yield f"# Copyright (C) 2002-{date.year} Free Software Foundation, Inc."
     for line in __DISCLAIMER:
         yield line
     yield "#"
@@ -974,10 +908,13 @@ def gnulib_cache(config):
     yield ""
     yield ""
     yield "# Specification in the form of a command-line invocation:"
-    yield "gl_LOCAL_DIR([$relative_local_gnulib_path])"
+    yield "#   " + " ".join(command_line(config, explicit))
+    yield ""
+    yield "# Specification in the form of a few gnulib-tool.m4 macro invocations:"
+    yield "gl_LOCAL_DIR([{}])".format(" ".join(config.overrides))
     yield "gl_MODULES(["
     for module in sorted(config.modules):
-        yield "  {}".format(module)
+        yield f"  {module}"
     yield "])"
     for key in ("obsolete", "cxx_tests", "longrunning_tests", "privileged_tests", "unportable_tests"):
         if config[key]:
@@ -985,35 +922,32 @@ def gnulib_cache(config):
     if config.all_tests:
         yield "gl_WITH_ALL_TESTS"
     yield "gl_AVOID([{}])".format(" ".join(sorted(config.avoids)))
-    yield "gl_SOURCE_BASE([{}])".format(config.source_base)
-    yield "gl_M4_BASE([{}])".format(config.m4_base)
-    yield "gl_PO_BASE([{}])".format(config.po_base)
-    yield "gl_DOC_BASE([{}])".format(config.doc_base)
-    yield "gl_TESTS_BASE([{}])".format(config.tests_base)
+    yield f"gl_SOURCE_BASE([{config.source_base}])"
+    yield f"gl_M4_BASE([{config.m4_base}])"
+    yield f"gl_PO_BASE([{config.po_base}])"
+    yield f"gl_DOC_BASE([{config.doc_base}])"
+    yield f"gl_TESTS_BASE([{config.tests_base}])"
     if config.tests:
         yield "gl_WITH_TESTS"
     yield "gl_LIB([{}])".format(config.libname)
-    if config.licenses in _LGPL:
+    if frozenset(config.licenses) in _LGPL:
         lgpl = _LGPL[config.licenses]
         yield "gl_LGPL([{}])".format(lgpl) if lgpl != "yes" else "gl_LGPL"
-    yield "gl_MAKEFILE_NAME([{}])".format(config.makefile_name)
+    yield f"gl_MAKEFILE_NAME([{config.makefile_name}])"
     if config.conditionals:
         yield "gl_CONDITIONAL_DEPENDENCIES"
     if config.libtool:
         yield "gl_LIBTOOL"
-    yield "gl_MACRO_PREFIX([{}])".format(config.macro_prefix)
-    yield "gl_PO_DOMAIN([{}])".format(config.po_domain)
-    yield "gl_WITNESS_C_MACRO([{}])".format(config.witness_c_macro)
+    yield f"gl_MACRO_PREFIX([{config.macro_prefix}])"
+    yield f"gl_PO_DOMAIN([{config.po_domain}])"
+    yield f"gl_WITNESS_C_MACRO([{config.witness_c_macro}])"
     if config.vc_files:
-        yield "gl_VC_FILES([{}])".format(" ".join(sorted(config.vc_files)))
+        yield "gl_VC_FILES([{}])".format(" ".join(config.vc_files))
 
 
 
 def gnulib_comp(config, explicit, database, subdirs, **override):
     """gnulib-comp.m4 generator"""
-    _type_assert("config", config, _BaseConfig)
-    _type_assert("explicit", explicit, _ITERABLES)
-    _type_assert("database", database, _Database)
     config = _BaseConfig(**config)
     for (key, value) in override.items():
         config[key] = value
@@ -1022,8 +956,9 @@ def gnulib_comp(config, explicit, database, subdirs, **override):
     test_modules = database.test_modules
 
     date = _datetime.now()
+    ac_file = config.ac_file
     yield "# DO NOT EDIT! GENERATED AUTOMATICALLY!"
-    yield "# Copyright (C) 2002-{} Free Software Foundation, Inc.".format(date.year)
+    yield f"# Copyright (C) 2002-{date.year} Free Software Foundation, Inc."
     for line in __DISCLAIMER[1:]:
         yield line
 
@@ -1035,10 +970,10 @@ def gnulib_comp(config, explicit, database, subdirs, **override):
     yield "# other built files."
     yield ""
     yield ""
-    yield "# This macro should be invoked from {}, in the section".format(config.ac_file)
+    yield f"# This macro should be invoked from {ac_file}, in the section"
     yield "# \"Checks for programs\", right after AC_PROG_CC, and certainly before"
     yield "# any checks for libraries, header files, types and library functions."
-    yield "AC_DEFUN([{}_EARLY],".format(config.macro_prefix)
+    yield f"AC_DEFUN([{macro_prefix}_EARLY],"
     yield "["
     yield "  m4_pattern_forbid([^gl_[A-Z]])dnl the gnulib macro namespace"
     yield "  m4_pattern_allow([^gl_ES$])dnl a valid locale name"
@@ -1053,15 +988,15 @@ def gnulib_comp(config, explicit, database, subdirs, **override):
     if not config.gnumake and subdirs:
         yield "  AC_REQUIRE([AM_PROG_CC_C_O])"
     for module in database.final_modules:
-        yield "  # Code from module {}:".format(module.name)
+        yield f"  # Code from module {module.name}:"
         lines = module.early_autoconf_snippet.split("\n")
         for line in filter(lambda line: line.strip(), lines):
-            yield "  {}".format(line)
+            yield f"  {line}"
     yield "])"
     yield ""
-    yield "# This macro should be invoked from {}, in the section".format(config.ac_file)
+    yield f"# This macro should be invoked from {ac_file}, in the section"
     yield "# \"Check for header files, types and library functions\"."
-    yield "AC_DEFUN([{}_INIT],".format(macro_prefix)
+    yield f"AC_DEFUN([{macro_prefix}_INIT],"
     yield "["
     if config.libtool:
         yield "  AM_CONDITIONAL([GL_COND_LIBTOOL], [true])"
@@ -1071,13 +1006,13 @@ def gnulib_comp(config, explicit, database, subdirs, **override):
         yield "  gl_cond_libtool=false"
         yield "  gl_libdeps="
         yield "  gl_ltlibdeps="
-    yield "  gl_m4_base='{}'".format(config.m4_base)
+    yield f"  gl_m4_base='{config.m4_base}'"
     for line in init_macro_header(config, macro_prefix=macro_prefix):
         yield line
-    yield "  gl_source_base='{}'".format(config.source_base)
+    yield f"  gl_source_base='{config.source_base}'"
     if "witness_c_macro" in explicit:
-        yield "  m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [{}])".format(config.witness_c_macro)
-    for line in autoconf_snippet_sequence(config, database, main_modules, True, False, True, macro_prefix=macro_prefix):
+        yield f"  m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [{config.witness_c_macro}])"
+    for line in autoconf_snippets(config, database, main_modules, True, False, True, macro_prefix=macro_prefix):
         yield line
     if "witness_c_macro" in explicit:
         yield "  m4_popdef([gl_MODULE_INDICATOR_CONDITION])"
@@ -1088,13 +1023,13 @@ def gnulib_comp(config, explicit, database, subdirs, **override):
     yield "  gltests_ltlibdeps="
     for line in init_macro_header(config, macro_prefix=(macro_prefix + "tests")):
         yield line
-    yield "  gl_source_base='{}'".format(config.tests_base)
+    yield f"  gl_source_base='{config.tests_base}'"
     # Define a tests witness macro that depends on the package.
     # PACKAGE is defined by AM_INIT_AUTOMAKE, PACKAGE_TARNAME is defined by AC_INIT.
     # See <http://lists.gnu.org/archive/html/automake/2009-05/msg00145.html>.
     yield "changequote(,)dnl"
     yield "".join((
-        "  {}tests_WITNESS=IN_`".format(macro_prefix),
+        f"  {macro_prefix}tests_WITNESS=IN_`",
         "echo \"${PACKAGE-$PACKAGE_TARNAME}\"",
         " | ",
         "LC_ALL=C tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ",
@@ -1103,10 +1038,10 @@ def gnulib_comp(config, explicit, database, subdirs, **override):
         "`_GNULIB_TESTS",
     ))
     yield "changequote([, ])dnl"
-    yield "  AC_SUBST([{}tests_WITNESS])".format(macro_prefix)
-    yield "  gl_module_indicator_condition=${}tests_WITNESS".format(macro_prefix)
+    yield f"  AC_SUBST([{macro_prefix}tests_WITNESS])"
+    yield f"  gl_module_indicator_condition=${macro_prefix}tests_WITNESS"
     yield "  m4_pushdef([gl_MODULE_INDICATOR_CONDITION], [$gl_module_indicator_condition])"
-    for line in autoconf_snippet_sequence(config, database, test_modules, True, True, True, macro_prefix=macro_prefix):
+    for line in autoconf_snippets(config, database, test_modules, True, True, True, macro_prefix=macro_prefix):
         yield line
     yield "  m4_popdef([gl_MODULE_INDICATOR_CONDITION])"
     for line in init_macro_footer(config, macro_prefix=(macro_prefix + "tests")):
@@ -1116,10 +1051,10 @@ def gnulib_comp(config, explicit, database, subdirs, **override):
     # created using libtool, because libtool already handles the dependencies.
     if not config.libtool:
         libname = config.libname.upper()
-        yield "  {}_LIBDEPS=\"$gl_libdeps\"".format(libname)
-        yield "  AC_SUBST([{}_LIBDEPS])".format(libname)
-        yield "  {}_LTLIBDEPS=\"$gl_ltlibdeps\"".format(libname)
-        yield "  AC_SUBST([{}_LTLIBDEPS])".format(libname)
+        yield f"  {libname}_LIBDEPS=\"$gl_libdeps\""
+        yield f"  AC_SUBST([{libname}_LIBDEPS])"
+        yield f"  {libname}_LTLIBDEPS=\"$gl_ltlibdeps\""
+        yield f"  AC_SUBST([{libname}_LTLIBDEPS])"
     if database.libtests:
         yield "  LIBTESTS_LIBDEPS=\"$gltests_libdeps\""
         yield "  AC_SUBST([LIBTESTS_LIBDEPS])"
@@ -1131,7 +1066,13 @@ def gnulib_comp(config, explicit, database, subdirs, **override):
     yield ""
     yield "# This macro records the list of files which have been installed by"
     yield "# gnulib-tool and may be removed by future gnulib-tool invocations."
-    yield "AC_DEFUN([{}_FILE_LIST], [".format(macro_prefix)
-    for file in sorted(set(database.main_files + database.test_files)):
-        yield "  {}".format(file)
+    yield f"AC_DEFUN([{macro_prefix}_FILE_LIST], ["
+    test_files = set()
+    main_files = set(database.main_files)
+    for file in database.test_files:
+        if file.startswith("lib/"):
+            file = ("tests=lib/" + file[len("lib/"):])
+        test_files.add(file)
+    for file in sorted(main_files | test_files):
+        yield f"  {file}"
     yield "])"
diff --git a/pygnulib/misc.py b/pygnulib/misc.py
new file mode 100644 (file)
index 0000000..275ac55
--- /dev/null
@@ -0,0 +1,175 @@
+#!/usr/bin/python
+# encoding: UTF-8
+"""miscellaneous tools and traits"""
+
+
+
+import os as _os
+from collections import OrderedDict as _OrderedDict
+
+
+
+class Property(property):
+    """generic property"""
+    @staticmethod
+    def __fget(*args, **kwargs):
+        raise AttributeError("unreadable attribute")
+
+    @staticmethod
+    def __fset(*args, **kwargs):
+        raise AttributeError("can't set attribute")
+
+
+    def __init__(self, fget=None, fset=None, doc=None, check=None):
+        """
+        doc  : any arbitrary string
+        fget : function to get the value
+        fset : function to set the value
+
+        def fget(self):
+            return self.value
+
+        def fset(self, value):
+            self.value = value
+        """
+        fget = Property.__fget if fget is None else fget
+        fset = Property.__fset if fset is None else fset
+        # if not callable(fget):
+        #     raise TypeError("fget: callable expected")
+        # if not callable(fset):
+        #     raise TypeError("fset: callable expected")
+        super().__init__(fget=fget, fset=fset, doc=doc)
+        self.__check = check
+        self.__fget = fget
+        self.__fset = fset
+        self.__doc__ = doc
+
+
+    def __set__(self, obj, val):
+        if self.__check:
+            if not self.__check(val):
+                raise ValueError("value check failed")
+        return super().__set__(obj, val)
+
+
+class PathProperty(Property):
+    """file system path property"""
+    def __init__(self, fget=None, fset=None, doc=None):
+        """
+        doc   : any arbitrary string
+        fget  : function to get the value
+        fset  : function to set the value
+
+        def fget(self):
+            return self.path
+
+        def fset(self, path):
+            self.path = path
+        """
+        super().__init__(fget=fget, fset=fset, doc=doc)
+
+
+    def __set__(self, obj, val):
+        if not isinstance(val, str):
+            raise TypeError("value: str expected")
+        if not val:
+            raise ValueError("value: empty path")
+        val = _os.path.normpath(val)
+        return super().__set__(obj, val)
+
+
+
+class BitwiseProperty(Property):
+    """bitwise flag property"""
+    def __init__(self, mask, fget=None, fset=None, doc=None):
+        """
+        mask : int bitwise mask
+        doc  : any arbitrary string
+        fget : function to get the value
+        fset : function to set the value
+
+        def fget(self, mask):
+            return bool(self.mask & mask)
+
+        def fset(self, mask, state):
+            if state:
+                self.mask |= mask
+            else:
+                self.mask &= ~mask
+        """
+        if not isinstance(mask, int):
+            raise TypeError("mask: int expected")
+
+        def _fget(obj):
+            return self.__fget(obj, self.__mask)
+
+        def _fset(obj, state):
+            return self.__fset(obj, self.__mask, state)
+
+        super().__init__(doc=doc, fget=_fget, fset=_fset)
+        self.__mask = mask
+        self.__fget = fget
+        self.__fset = fset
+
+
+    @property
+    def mask(self):
+        return self.__mask
+
+
+    def __set__(self, obj, val):
+        if not isinstance(val, bool):
+            raise TypeError("value: bool expected")
+        return super().__set__(obj, val)
+
+
+
+class StringListProperty(Property):
+    """string sequence property"""
+    __TYPES = (list, tuple, set, frozenset, type({}.keys()), type({}.values()))
+
+    def __init__(self, sorted=False, unique=False, fget=None, fset=None, doc=None):
+        if not isinstance(sorted, bool):
+            raise TypeError("sorted: bool expected")
+        if not isinstance(unique, bool):
+            raise TypeError("unique: bool expected")
+        super().__init__(fget=fget, fset=fset, doc=doc)
+        self.__sorted = sorted
+        self.__unique = unique
+
+
+    def __set__(self, obj, val):
+        if not isinstance(val, StringListProperty.__TYPES):
+            raise TypeError("value: iterable expected")
+        res = list()
+        for item in val:
+            if not isinstance(item, str):
+                raise TypeError("item: str expected")
+            res.append(item)
+        res = tuple(_OrderedDict.fromkeys(res) if self.__unique else res)
+        val = tuple(sorted(res) if self.__sorted else res)
+        return super().__set__(obj, val)
+
+
+
+class PathListProperty(StringListProperty):
+    """path sequence property"""
+    __TYPES = (list, tuple, set, frozenset, type({}.keys()), type({}.values()))
+
+
+    def __init__(self, sorted=False, unique=False, fget=None, fset=None, doc=None):
+        super().__init__(sorted=sorted, unique=unique, fget=fget, fset=fset, doc=doc)
+
+
+    def __set__(self, obj, val):
+        if not isinstance(val, PathListProperty.__TYPES):
+            raise TypeError("value: iterable expected")
+        res = list()
+        for item in val:
+            if not isinstance(item, str):
+                raise TypeError("item: str expected")
+            if not item:
+                raise TypeError("item: empty path")
+            item = _os.path.normpath(item)
+            res.append(item)
+        return super().__set__(obj, res)
index 7e15427fe730f465848eccdca9255fe4c196d7b2..40a782e27e83f9d7e88ce2967cdec05c3d25eb92 100644 (file)
@@ -11,39 +11,44 @@ import hashlib as _hashlib
 import json as _json
 import os as _os
 import re as _re
+import sys as _sys
 
 
-from .error import type_assert as _type_assert
 from .error import UnknownModuleError as _UnknownModuleError
 from .config import BaseConfig as _BaseConfig
-
-
-
-_ITERABLES = frozenset((list, tuple, set, frozenset, type({}.keys()), type({}.values())))
+from .misc import Property as _Property
+from .misc import PathProperty as _PathProperty
+from .misc import StringListProperty as _StringListProperty
+from .misc import PathListProperty as _PathListProperty
 
 
 
 class BaseModule:
     """base module"""
-    _TABLE = {
-        "description"                  : (0x00, str, "Description"),
-        "comment"                      : (0x01, str, "Comment"),
-        "status"                       : (0x02, tuple, "Status"),
-        "notice"                       : (0x03, str, "Notice"),
-        "applicability"                : (0x04, str, "Applicability"),
-        "files"                        : (0x05, tuple, "Files"),
-        "dependencies"                 : (0x06, tuple, "Depends-on"),
-        "early_autoconf_snippet"       : (0x07, str, "configure.ac-early"),
-        "autoconf_snippet"             : (0x08, str, "configure.ac"),
-        "conditional_automake_snippet" : (0x09, str, "Makefile.am"),
-        "include_directive"            : (0x0A, str, "Include"),
-        "link_directive"               : (0x0B, str, "Link"),
-        "licenses"                     : (0x0C, tuple, "License"),
-        "maintainers"                  : (0x0D, tuple, "Maintainer"),
-        # unconditional_automake_snippet
-        # automake_snippet
+    __slots__ = ("__options", "__flags")
+
+
+    __PROPERTIES = {
+        "name"                           : None,
+        "description"                    : "",
+        "comment"                        : "",
+        "status"                         : "",
+        "notice"                         : "",
+        "applicability"                  : "",
+        "files"                          : "",
+        "dependencies"                   : "",
+        "early_autoconf_snippet"         : "",
+        "autoconf_snippet"               : "",
+        "conditional_automake_snippet"   : "",
+        "unconditional_automake_snippet" : None,
+        "automake_snippet"               : None,
+        "include_directives"             : "",
+        "link_directives"                : "",
+        "licenses"                       : "",
+        "maintainers"                    : "",
+        "test"                           : "",
     }
-    _PROPERTIES = {
+    __OPTIONS = {
         "name",
         "description",
         "comment",
@@ -57,289 +62,267 @@ class BaseModule:
         "conditional_automake_snippet",
         "unconditional_automake_snippet",
         "automake_snippet",
-        "include_directive",
-        "link_directive",
+        "include_directives",
+        "link_directives",
         "licenses",
         "maintainers",
         "test",
     }
+    __FLAGS = {}
     __LIB_SOURCES = _re.compile(r"^lib_SOURCES\s*\+\=\s*(.*?)$", _re.S | _re.M)
 
 
     def __init__(self, name, **kwargs):
-        _type_assert("name", name, str)
-        if "licenses" in kwargs:
-            licenses = set()
-            for license in kwargs.get("licenses", tuple()):
-                _type_assert("license", license, str)
-                licenses.add(license)
-            kwargs["licenses"] = licenses
-        if "maintainers" not in kwargs:
-            kwargs["maintainers"] = {"all"}
-        self.__name = name
-        self.__table = _collections.OrderedDict()
-        for (key, (_, typeid, _)) in BaseModule._TABLE.items():
-            self.__table[key] = typeid(kwargs.get(key, typeid()))
-
-
-    def __repr__(self):
-        return "<" + self.__name + ">"
+        if not isinstance(name, str):
+            raise TypeError("name: str expected")
+        if not name.strip():
+            raise ValueError("name: invalid name")
+
+        self.__flags = 0
+        self.__options = {}
+        for key in BaseModule.__OPTIONS:
+            value = BaseModule.__PROPERTIES[key]
+            if value is not None:
+                self.__set_option(key, value)
+        for key in BaseModule.__FLAGS:
+            state = BaseModule.__PROPERTIES[key]
+            mask = getattr(self.__class__, key).mask
+            self.__set_flags(mask, state)
+        self.__set_option("name", name)
 
-
-    def __str__(self):
-        return "<" + self.__name + ">"
+        for (key, value) in kwargs.items():
+            setattr(self, key, value)
 
 
     def __enter__(self):
         return self
 
 
-    def __exit__(self, exctype, excval, exctrace):
-        pass
+    def __hash__(self):
+        return hash(_json.dumps(dict(self.__options), sort_keys=True))
 
 
-    def __hash__(self):
-        return hash(self.__name) ^ hash(_json.dumps(dict(self.__table), sort_keys=True))
+    def __repr__(self):
+        module = self.__class__.__module__
+        name = self.__class__.__name__
+        return f"{module}.{name}[{self.name}]"
 
 
     def __getitem__(self, key):
-        _type_assert("key", key, str)
-        if key not in BaseModule._PROPERTIES:
+        if key not in BaseModule.__PROPERTIES:
             key = key.replace("-", "_")
-            if key not in BaseModule._PROPERTIES:
+            if key not in BaseModule.__PROPERTIES:
                 raise KeyError(repr(key))
         return getattr(self, key)
 
 
     def __setitem__(self, key, value):
-        _type_assert("key", key, str)
-        if key not in BaseModule._PROPERTIES:
+        if key not in BaseModule.__PROPERTIES:
             key = key.replace("-", "_")
-            if key not in BaseModule._PROPERTIES:
+            if key not in BaseModule.__PROPERTIES:
                 raise KeyError(repr(key))
         return setattr(self, key, value)
 
 
+    def __get_option(self, key):
+        return self.__options[key]
+
+    def __set_option(self, key, value):
+        self.__options[key] = value
+
+
     @property
-    def gnulib(self):
+    def gnulib_package(self):
         """gnulib-compatible module textual representation"""
         def _gnulib():
             yield "Description:"
-            yield self.__table["description"]
+            yield self.description
             yield "Comment:"
-            yield self.__table["comment"]
+            yield self.comment
             yield "Status:"
-            for status in sorted(self.__table["status"]):
+            for status in sorted(self.status):
                 yield status
             yield "Notice:"
-            yield self.__table["notice"]
+            yield self.notice
             yield "Applicability:"
-            yield self.__table["applicability"]
+            yield self.applicability
             yield "Files:"
-            for file in sorted(self.__table["files"]):
+            for file in self.files:
                 yield file
             yield "Depends-on:"
-            for (module, condition) in self.__table["dependencies"]:
+            for (module, condition) in self.dependencies:
                 yield "{}    {}".format(module, condition)
             yield "configure.ac-early:"
-            yield self.__table["early_autoconf_snippet"]
+            yield self.early_autoconf_snippet
             yield "configure.ac:"
-            yield self.__table["autoconf_snippet"]
+            yield self.autoconf_snippet
             yield "Makefile.am:"
-            yield self.__table["conditional_automake_snippet"]
+            yield self.conditional_automake_snippet
             yield "Include:"
-            yield self.__table["include_directive"]
+            for include in self.include_directives:
+                yield include
             yield "Link:"
-            yield self.__table["link_directive"]
+            for link in self.link_directives:
+                yield link
             yield "License:"
-            for license in sorted(self.__table["licenses"]):
+            for license in self.licenses:
                 yield license
             yield "Maintainer:"
-            for maintainer in self.__table["maintainers"]:
+            for maintainer in maintainers:
                 yield maintainer
         return "\n".join(_gnulib())
 
 
-    @property
-    def name(self):
-        """name"""
-        return self.__name
-
-    @name.setter
-    def name(self, value):
-        _type_assert("name", value, str)
-        self.__name = value
-
-
-    @property
-    def description(self):
-        """description"""
-        return self.__table["description"]
-
-    @description.setter
-    def description(self, value):
-        _type_assert("description", value, str)
-        self.__table["description"] = value
-
-
-    @property
-    def comment(self):
-        """comment"""
-        return self.__table["comment"]
-
-    @comment.setter
-    def comment(self, value):
-        _type_assert("comment", value, str)
-        self.__table["comment"] = value
-
-
-    @property
-    def status(self):
-        """status"""
-        return self.__table["status"]
-
-    @status.setter
-    def status(self, value):
-        _type_assert("status", value, _ITERABLES)
-        result = set()
-        for item in value:
-            _type_assert("status", item, str)
-            result.add(item)
-        self.__table["status"] = tuple(result)
-
-
-    @property
-    def obsolete(self):
-        """module is obsolete?"""
-        return "obsolete" in self.status
-
-    @property
-    def cxx_test(self):
-        """module is a C++ test?"""
-        return "c++-test" in self.status
-
-    @property
-    def longrunning_test(self):
-        """module is a C++ test?"""
-        return "longrunning-test" in self.status
-
-    @property
-    def privileged_test(self):
-        """module is a privileged test?"""
-        return "privileged-test" in self.status
-
-    @property
-    def unportable_test(self):
-        """module is a unportable test?"""
-        return "unportable-test" in self.status
-
-
-    @property
-    def notice(self):
-        """notice"""
-        return self.__table["notice"]
-
-    @notice.setter
-    def notice(self, value):
-        _type_assert("notice", value, str)
-        self.__table["notice"] = value
-
-
-    @property
-    def applicability(self):
-        """applicability (usually "main" or "tests")"""
-        return self.__table["applicability"]
-
-    @applicability.setter
-    def applicability(self, value):
-        _type_assert("applicability", value, str)
-        if value not in ("all", "main", "tests"):
-            raise ValueError("applicability: \"main\", \"tests\" or \"all\"")
-        self.__table["applicability"] = value
-
-
-    @property
-    def files(self):
-        """file dependencies iterator (set of strings)"""
-        return self.__table["files"]
-
-    @files.setter
-    def files(self, value):
-        _type_assert("files", value, _ITERABLES)
-        result = set()
-        for item in value:
-            _type_assert("file", item, str)
-            result.add(item)
-        self.__table["files"] = tuple(result)
-
-
-    @property
+    name = _Property(
+        fget=lambda self: self.__get_option("name"),
+        fset=lambda self, string: self.__set_option("name", string),
+        check=lambda value: isinstance(value, str) and value,
+        doc="name",
+    )
+    description = _Property(
+        fget=lambda self: self.__get_option("description"),
+        fset=lambda self, string: self.__set_option("description", string),
+        check=lambda value: isinstance(value, str),
+        doc="description",
+    )
+    comment = _Property(
+        fget=lambda self: self.__get_option("comment"),
+        fset=lambda self, string: self.__set_option("comment", string),
+        check=lambda value: isinstance(value, str),
+        doc="comment",
+    )
+    status = _StringListProperty(
+        sorted=True,
+        unique=True,
+        fget=lambda self: self.__get_option("comment"),
+        fset=lambda self, string: self.__set_option("comment", string),
+        doc="status list",
+    )
+    obsolete = _Property(
+        fget=lambda self: "obsolete" in self.status,
+        doc="module is obsolete?",
+    )
+    cxx_test = _Property(
+        fget=lambda self: "c++-test" in self.status,
+        doc="module is a C++ test?",
+    )
+    longrunning_test = _Property(
+        fget=lambda self: "longrunning-test" in self.status,
+        doc="module is a longrunning test?",
+    )
+    privileged_test = _Property(
+        fget=lambda self: "privileged-test" in self.status,
+        doc="module is a privileged test?",
+    )
+    unportable_test = _Property(
+        fget=lambda self: "unportable-test" in self.status,
+        doc="module is an unportable test?",
+    )
+    mask = _Property(
+        fget=lambda self: ((0, (1 << 0))[self.obsolete]
+                          |(0, (1 << 1))[self.cxx_test]
+                          |(0, (1 << 2))[self.longrunning_test]
+                          |(0, (1 << 3))[self.privileged_test]
+                          |(0, (1 << 4))[self.unportable_test]),
+        doc="module acceptibility mask",
+    )
+    notice = _Property(
+        fget=lambda self: self.__get_option("notice"),
+        fset=lambda self, string: self.__set_option("notice", string),
+        check=lambda value: isinstance(value, str),
+        doc="module notice or disclaimer",
+    )
+    applicability = _Property(
+        fget=lambda self: self.__get_option("applicability"),
+        fset=lambda self, string: self.__set_option("applicability", string),
+        check=lambda value: isinstance(value, str) and value in {"main", "tests", "all"},
+        doc="applicability ('main', 'tests' or 'all')",
+    )
+    files = _PathListProperty(
+        sorted=True,
+        unique=True,
+        fget=lambda self: self.__get_option("files"),
+        fset=lambda self, string: self.__set_option("files", string),
+        doc="file dependencies",
+    )
+    early_autoconf_snippet = _Property(
+        fget=lambda self: self.__get_option("early_autoconf_snippet"),
+        fset=lambda self, string: self.__set_option("early_autoconf_snippet", string),
+        check=lambda value: isinstance(value, str),
+        doc="early configure.ac snippet",
+    )
+    autoconf_snippet = _Property(
+        fget=lambda self: self.__get_option("autoconf_snippet"),
+        fset=lambda self, string: self.__set_option("autoconf_snippet", string),
+        check=lambda value: isinstance(value, str),
+        doc="configure.ac snippet",
+    )
+    conditional_automake_snippet = _Property(
+        fget=lambda self: self.__get_option("conditional_automake_snippet"),
+        fset=lambda self, string: self.__set_option("conditional_automake_snippet", string),
+        check=lambda value: isinstance(value, str),
+        doc="configure.ac snippet",
+    )
+    automake_snippet = _Property(
+        fget=lambda self: "\n".join((self.conditional_automake_snippet, self.unconditional_automake_snippet)),
+        doc="full automake snippet (conditional + unconditional parts)",
+    )
+    include_directives = _StringListProperty(
+        fget=lambda self: self.__get_option("include_directives"),
+        fset=lambda self, string: self.__set_option("include_directives", string),
+        doc="include directive",
+    )
+    link_directives = _StringListProperty(
+        fget=lambda self: self.__get_option("link_directives"),
+        fset=lambda self, string: self.__set_option("link_directives", string),
+        doc="link directive",
+    )
+    licenses = _StringListProperty(
+        sorted=True,
+        unique=True,
+        fget=lambda self: self.__get_option("licenses"),
+        fset=lambda self, name: self.__set_option("licenses", name),
+        doc="acceptable licenses for modules",
+    )
+    maintainers = _StringListProperty(
+        sorted=False,
+        unique=True,
+        fget=lambda self: self.__get_option("maintainers"),
+        fset=lambda self, name: self.__set_option("maintainers", name),
+        doc="module maintainers list",
+    )
+    test = _Property(
+        fget=lambda self: self.__get_option("test"),
+        fset=lambda self, name: self.__set_option("test", name),
+        check=lambda value: isinstance(value, bool),
+        doc="module is a test?",
+    )
+
+
+    @_Property
     def dependencies(self):
         """dependencies iterator (name, condition)"""
-        entries = set()
-        for line in self.__table["dependencies"]:
-            line = line.replace("\t", " ").strip()
-            index = line.find(" ")
-            if index == -1:
-                module = line
-                condition = ""
-            else:
-                module = line[:index].strip()
-                condition = line[index:].strip()
-            if condition.startswith("["):
-                condition = condition[1:]
-            if condition.endswith("]"):
-                condition = condition[:-1]
-            if not condition:
-                condition = None
-            entries.add((module, condition))
-        return tuple(entries)
+        return self.__options["dependencies"]
 
     @dependencies.setter
     def dependencies(self, value):
-        _type_assert("files", value, _ITERABLES)
-        result = set()
-        for (name, condition) in value:
-            _type_assert("name", name, str)
-            _type_assert("condition", condition, str)
-            result.add((name, condition))
-        self.__table["dependencies"] = tuple(result)
-
-
-    @property
-    def early_autoconf_snippet(self):
-        """early configure.ac snippet"""
-        return self.__table["early_autoconf_snippet"]
-
-    @early_autoconf_snippet.setter
-    def early_autoconf_snippet(self, value):
-        _type_assert("early_autoconf_snippet", value, str)
-        self.__table["early_autoconf_snippet"] = value
-
-
-    @property
-    def autoconf_snippet(self):
-        """configure.ac snippet"""
-        return self.__table["autoconf_snippet"]
-
-    @autoconf_snippet.setter
-    def autoconf_snippet(self, value):
-        _type_assert("autoconf_snippet", value, str)
-        self.__table["autoconf_snippet"] = value
-
-
-    @property
-    def conditional_automake_snippet(self):
-        """Makefile.am snippet that can be put inside Automake conditionals"""
-        return self.__table["conditional_automake_snippet"]
-
-    @conditional_automake_snippet.setter
-    def conditional_automake_snippet(self, value):
-        _type_assert("conditional_automake_snippet", value, str)
-        self.__table["conditional_automake_snippet"] = value
-
-
-    @property
+        result = []
+        types = (list, tuple, set, frozenset, type({}.keys()), type({}.values()))
+        if not isinstance(value, types):
+            raise TypeError("value: iterable expected")
+        for item in value:
+            if not isinstance(value, (list, tuple)):
+                raise TypeError("item: pair expected")
+            (module, condition) = item
+            if not isinstance(module, str):
+                raise TypeError("module: str expected")
+            if condition is not None and not isinstance(condition, str):
+                raise TypeError("condition: str or None expected")
+            result.append((module, condition))
+        self.__options["dependencies"] = tuple(result)
+
+
+    @_Property
     def unconditional_automake_snippet(self):
         """Makefile.am snippet that must stay outside of Automake conditionals"""
         result = ""
@@ -403,78 +386,6 @@ class BaseModule:
         return result
 
 
-    @property
-    def automake_snippet(self):
-        """full automake snippet (conditional + unconditional parts)"""
-        return "\n".join((self.conditional_automake_snippet, self.unconditional_automake_snippet))
-
-
-    @property
-    def include_directive(self):
-        """include directive"""
-        value = self.__table["include_directive"]
-        if value.startswith("<") or value.startswith("\""):
-            return "#include {0}".format(value)
-        return self.__table["include_directive"]
-
-    @include_directive.setter
-    def include_directive(self, value):
-        _type_assert("include_directive", value, str)
-        self.__table["include_directive"] = value
-
-
-    @property
-    def link_directive(self):
-        """link directive"""
-        return self.__table["link_directive"]
-
-    @link_directive.setter
-    def link_directive(self, value):
-        _type_assert("link_directive", value, str)
-        self.__table["link_directive"] = value
-
-
-    @property
-    def licenses(self):
-        """licenses set"""
-        return self.__table["licenses"]
-
-    @licenses.setter
-    def licenses(self, value):
-        _type_assert("licenses", value, _ITERABLES)
-        result = set()
-        for item in value:
-            _type_assert("license", item, str)
-            result.add(value)
-        self.__table["licenses"] = tuple(result)
-
-
-    @property
-    def maintainers(self):
-        """maintainers"""
-        return self.__table["maintainers"]
-
-    @maintainers.setter
-    def maintainers(self, value):
-        _type_assert("maintainers", value, _ITERABLES)
-        result = set()
-        for item in value:
-            _type_assert("maintainer", item, str)
-            result.add(item)
-        self.__table["maintainers"] = tuple(result)
-
-
-    @property
-    def test(self):
-        """module is tests-related?"""
-        return self.__table["test"]
-
-    @test.setter
-    def test(self, value):
-        _type_assert("test", value, bool)
-        self.__table["test"] = value
-
-
     def shell_variable(self, macro_prefix="gl"):
         """Get the name of the shell variable set to true once m4 macros have been executed."""
         module = self.name
@@ -504,37 +415,40 @@ class BaseModule:
 
     def items(self):
         """a set-like object providing a view on module items"""
-        for key in BaseModule._PROPERTIES:
+        for key in BaseModule.__PROPERTIES:
             yield (key, self[key])
 
 
+    @classmethod
     def keys(self):
         """a set-like object providing a view on module keys"""
-        for (key, _) in self.items():
+        for key in BaseModule.__PROPERTIES:
             yield key
 
 
     def values(self):
         """a set-like object providing a view on module values"""
-        for (_, value) in self.items():
-            yield value
+        for key in BaseModule.__PROPERTIES:
+            yield self[key]
 
 
     def __lt__(self, value):
-        _type_assert("value", value, BaseModule)
-        return self.name < value.name
+        if value is not None:
+            return self.name < value.name
+        return False
 
     def __le__(self, value):
         return self.__lt__(value) or self.__eq__(value)
 
     def __eq__(self, value):
-        _type_assert("value", value, BaseModule)
-        if self.name != value.name:
-            return False
-        for key in BaseModule._PROPERTIES:
-            if self[key] != value[key]:
+        if value is not None:
+            if self.name != value.name:
                 return False
-        return True
+            for key in BaseModule.__PROPERTIES:
+                if self[key] != value[key]:
+                    return False
+            return True
+        return False
 
     def __ne__(self, value):
         return not self.__eq__(value)
@@ -547,6 +461,108 @@ class BaseModule:
 
 
 
+class FileModule(BaseModule):
+    """text-based module"""
+    __slots__ = ("__path")
+
+
+    __DEPENDENCY = _re.compile(r"(\S+)(?:\s+(\[.*?\]))?$", _re.M)
+    __STRING = lambda text: text.strip()
+    __MULTILINE = lambda text: tuple(filter(
+        lambda line: line.strip() and not line.strip().startswith("#"),
+        [line.strip() for line in text.strip().splitlines()],
+    ))
+    __INCLUDE_DIRECTIVES = lambda text: tuple(filter(
+        lambda line: line.strip(),
+        [line.strip() for line in text.strip().splitlines()],
+    ))
+    __DEPENDENCIES = lambda text: FileModule.__DEPENDENCY.findall("\n".join(FileModule.__MULTILINE(text)))
+    __MAINTAINERS = lambda text: tuple(filter(
+        lambda line: line.strip() and not line.strip().startswith("#"),
+        {line.strip() for line in text.split((",", "\n")["\n" in text.strip()])},
+    ))
+    __TABLE = {
+        "Description": (
+            "description",
+            __STRING,
+        ),
+        "Comment": (
+            "comment",
+            __STRING,
+        ),
+        "Status": (
+            "status",
+            __MULTILINE,
+        ),
+        "Notice": (
+            "notice",
+            __STRING,
+        ),
+        "Applicability": (
+            "applicability",
+            __STRING,
+        ),
+        "Files": (
+            "files",
+            __MULTILINE,
+        ),
+        "Depends-on": (
+            "dependencies",
+            __DEPENDENCIES,
+        ),
+        "configure.ac-early": (
+            "early_autoconf_snippet",
+            __STRING,
+        ),
+        "configure.ac": (
+            "autoconf_snippet",
+            __STRING,
+        ),
+        "Makefile.am": (
+            "conditional_automake_snippet",
+            __STRING,
+        ),
+        "Include": (
+            "include_directives",
+            __INCLUDE_DIRECTIVES,
+        ),
+        "Link": (
+            "link_directives",
+            __MULTILINE,
+        ),
+        "License": (
+            "licenses",
+            __MULTILINE,
+        ),
+        "Maintainer": (
+            "maintainers",
+            __MAINTAINERS,
+        ),
+    }
+    __PATTERN = _re.compile("({}):".format("|".join(__TABLE)))
+
+
+    path = _Property(
+        fget=lambda self: self.__path,
+        doc="module file path",
+    )
+
+
+    def __init__(self, path, name, **kwargs):
+        if not isinstance(path, str):
+            raise TypeError("path: str expected")
+        if not isinstance(name, str):
+            raise TypeError("name: str expected")
+        with _codecs.open(path, "rb", "UTF-8") as stream:
+            match = FileModule.__PATTERN.split(stream.read())[1:]
+        for (group, text) in zip(match[::2], match[1::2]):
+            (key, hook) = FileModule.__TABLE[group]
+            kwargs.setdefault(key, hook(text))
+        super().__init__(name=name, **kwargs)
+        self.__path = path
+
+
+
 class _DummyModuleMeta(type):
     __INSTANCE = None
     __PROPERTIES = {
@@ -559,8 +575,8 @@ class _DummyModuleMeta(type):
         "dependencies": tuple(),
         "early_autoconf_snippet": "",
         "autoconf_snippet": "",
-        "include_directive": "",
-        "link_directive": "",
+        "include_directives": "",
+        "link_directives": "",
         "licenses": tuple({"public domain"}),
         "maintainers": tuple({"all"}),
         "automake_snippet": "lib_SOURCES += dummy.c",
@@ -568,15 +584,18 @@ class _DummyModuleMeta(type):
         "unconditional_automake_snippet": "",
     }
 
+
     def __new__(mcs, name, parents, attributes):
-        cls = super().__new__(mcs, name, parents, attributes)
         for (key, value) in _DummyModuleMeta.__PROPERTIES.items():
-            setattr(cls, key, property(lambda cls, value=value: value))
-        return cls
+            fget=lambda self, value=value: value
+            doc = getattr(BaseModule, key).__doc__
+            attributes[key] = _Property(fget=fget, doc=doc)
+        return super().__new__(mcs, name, parents, attributes)
+
 
     def __call__(cls, *args, **kwargs):
         if _DummyModuleMeta.__INSTANCE is None:
-            _DummyModuleMeta.__INSTANCE = super(_DummyModuleMeta, cls).__call__(*args, **kwargs)
+            _DummyModuleMeta.__INSTANCE = super().__call__(*args, **kwargs)
         return _DummyModuleMeta.__INSTANCE
 
 
@@ -587,47 +606,50 @@ class DummyModule(BaseModule, metaclass=_DummyModuleMeta):
 
 
 
-class FileModule(BaseModule):
-    """text-based module"""
-    _TABLE = {_value[2]:(_value[1], _key) for (_key, _value) in BaseModule._TABLE.items()}
-    _FIELDS = [_field for (_, _, _field) in BaseModule._TABLE.values()]
-    _PATTERN = _re.compile("({}):".format("|".join(_FIELDS)))
+class _GnulibModuleMeta(type):
+    def __new__(mcs, name, parents, attributes):
+        for key in BaseModule.keys():
+            if key in attributes:
+                continue
+            fget = lambda self, key=key: self.__getitem__(key)
+            doc = getattr(BaseModule, key).__doc__
+            attributes[key] = _Property(fget=fget, doc=doc)
+        return super().__new__(mcs, name, parents, attributes)
 
 
-    def __init__(self, path, name=None, **kwargs):
-        _type_assert("path", path, str)
-        _type_assert("name", name, str)
-        self.__path = path
-        with _codecs.open(path, "rb", "UTF-8") as stream:
-            match = FileModule._PATTERN.split(stream.read())[1:]
-        table = {}
-        for (group, value) in zip(match[::2], match[1::2]):
-            (typeid, key) = FileModule._TABLE[group]
-            if typeid in _ITERABLES:
-                lines = []
-                for line in value.splitlines():
-                    if line.strip() and not line.startswith("#"):
-                        lines.append(line)
-                table[key] = typeid(lines)
-            else:
-                table[key] = value.strip()
-        for (key, value) in kwargs.items():
-            table[key] = value
-        super().__init__(name, **table)
+class GnulibModule(FileModule, metaclass=_GnulibModuleMeta):
+    """read-only gnulib standard module"""
+    __slots__ = ("__cache", "__hash", "__mask", "__path", "__test")
 
 
+    __OBSOLETE = (1 << 0)
+    __CXX_TEST = (1 << 1)
+    __LONGRUNNING_TEST = (1 << 2)
+    __PRIVILEGED_TEST = (1 << 3)
+    __UNPORTABLE_TEST = (1 << 4)
+
 
-class GnulibModule(FileModule):
-    """read-only gnulib standard module"""
     def __init__(self, path, name):
-        super().__init__(path=path, name=name)
+        super(FileModule, self).__init__(name=name)
+        try:
+            module = FileModule(path=path, name=name, test=name.endswith("-tests"))
+        except FileNotFoundError:
+            raise _UnknownModuleError(name)
+        self.__cache = {_sys.intern(k):v for (k,v) in module.items()}
         self.__hash = super().__hash__()
+        self.__mask = module.mask
+        self.__path = module.path
+        self.__test = module.test
 
 
-    def __setattr__(self, key, value):
-        if key in BaseModule._PROPERTIES:
-            raise AttributeError("can't set property")
-        super().__setattr__(key, value)
+    def __repr__(self):
+        module = self.__class__.__module__
+        name = self.__class__.__name__
+        return f"{module}.{name}{{{self.name}}}"
+
+
+    def __getitem__(self, key):
+        return self.__cache[key]
 
 
     def __hash__(self):
@@ -636,41 +658,63 @@ class GnulibModule(FileModule):
 
     def __eq__(self, other):
         if isinstance(other, GnulibModule):
-            return hash(self) == hash(other)
+            return self.__hash == hash(other)
         return super().__eq__(other)
 
 
-    @property
+    obsolete = _Property(
+        fget=lambda self: bool(self.__mask & GnulibModule.__OBSOLETE),
+        doc="module is obsolete?",
+    )
+    cxx_test = _Property(
+        fget=lambda self: bool(self.__mask & GnulibModule.__CXX_TEST),
+        doc="module is a C++ test?",
+    )
+    longrunning_test = _Property(
+        fget=lambda self: bool(self.__mask & GnulibModule.__LONGRUNNING_TEST),
+        doc="module is a longrunning test?",
+    )
+    privileged_test = _Property(
+        fget=lambda self: bool(self.__mask & GnulibModule.__PRIVILEGED_TEST),
+        doc="module is a privileged test?",
+    )
+    unportable_test = _Property(
+        fget=lambda self: bool(self.__mask & GnulibModule.__UNPORTABLE_TEST),
+        doc="module is an unportable test?",
+    )
+    mask = _Property(
+        fget=lambda self: self.__mask,
+        doc="module acceptibility mask",
+    )
+    test = _Property(
+        fget=lambda self: self.name.endswith("-tests"),
+        doc="module is tests-related?",
+    )
+    path = _Property(
+        fget=lambda self: self.__path,
+        doc="module file path",
+    )
+
+
+    @_Property
     def applicability(self):
-        """applicability (usually "main" or "tests")"""
+        """WAGH applicability (usually "main" or "tests")"""
         default = "tests" if self.test else "main"
-        result = super().applicability
-        return result if result else default
-
-
-    @property
-    def test(self):
-        """module is tests-related?"""
-        return self.name.endswith("-tests")
+        current = self.__cache["applicability"]
+        return current if current else default
 
 
 
 class TransitiveClosure:
     """transitive closure table"""
-    def __init__(self, lookup, config, tests):
+    def __init__(self, lookup, modules, mask, gnumake, tests=False):
         if not callable(lookup):
             raise TypeError("lookup: callable expected")
-        _type_assert("config", config, _BaseConfig)
-        _type_assert("tests", tests, bool)
+        demanders = _collections.defaultdict(dict)
+        dependencies = _collections.defaultdict(dict)
 
         def _exclude(module):
-            return any((
-                (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),
-            ))
+            return mask != module.mask
 
         def _lookup(module):
             if not (module is None or isinstance(module, BaseModule)):
@@ -687,26 +731,31 @@ class TransitiveClosure:
             dependencies[dependency][demander] = condition
             current.add(dependency)
 
+        testdb = {}
+        mapping = {}
         current = set()
         previous = set()
-        demanders = _collections.defaultdict(dict)
-        dependencies = _collections.defaultdict(dict)
-        for module in config.modules:
+        for module in modules:
             dependency = lookup(module)
             _update(None, dependency, None)
         while current != previous:
             previous.update(current)
             for demander in previous:
-                if tests and not demander.test:
+                if tests and not demander.test and testdb.get(demander.name, None) is None:
                     try:
-                        dependency = lookup("{}-tests".format(demander.name))
+                        name = (demander.name + "-tests")
+                        path = (demander.path + "-tests")
+                        dependency = GnulibModule(name=name, path=path)
                         if not _exclude(dependency):
-                            _update(None, dependency, None)
+                            _update(demander, dependency, None)
+                            testdb[demander.name] = True
+                        else:
+                            testdb[demander.name] = False
                     except _UnknownModuleError:
-                        pass # ignore non-existent tests
+                        testdb[demander.name] = False
                 for (dependency, condition) in demander.dependencies:
                     dependency = lookup(dependency)
-                    if config.gnumake and condition and condition.startswith("if "):
+                    if gnumake and condition and condition.startswith("if "):
                         # A module whose Makefile.am snippet contains a reference to an
                         # automake conditional. If we were to use it conditionally, we
                         # would get an error
@@ -724,10 +773,11 @@ class TransitiveClosure:
         conditional = set()
         unconditional = set()
         for (dependency, demanders) in self.__dependencies.items():
-            for (demander, _) in demanders.items():
+            for demander in demanders:
                 if demander is None:
-                    unconditional.add(demander)
+                    unconditional.add(dependency)
                     break
+
         previous = set()
         current = set(unconditional)
         while previous != current:
@@ -786,14 +836,9 @@ class TransitiveClosure:
         demanders = _collections.defaultdict(dict)
         dependencies = _collections.defaultdict(dict)
         collection = _ast.literal_eval(string)
-        _type_assert("collection", collection, dict)
         for key in collection:
-            _type_assert("key", key, str)
             value = collection[key]
-            _type_assert("value", value, dict)
             for (subkey, subvalue) in value.items():
-                _type_assert("key", key, str)
-                _type_assert("dict", value, dict)
                 (dependency, demander, condition) = (key, subkey, subvalue)
                 dependency = self.__lookup(dependency)
                 demander = self.__lookup(demander)
@@ -852,7 +897,10 @@ class Database:
     def __init__(self, lookup, config):
         if not callable(lookup):
             raise TypeError("lookup: callable expected")
-        _type_assert("config", config, _BaseConfig)
+
+        mask = config.mask
+        gnumake = config.gnumake
+        lookup = lambda module, lookup=lookup: module if isinstance(module, BaseModule) else lookup(module)
 
         def _applicability(module):
             return module.applicability in ({"main", "all"}, {"main"})[config.tests]
@@ -885,26 +933,23 @@ class Database:
                         return True
             return False
 
-        base_closure = TransitiveClosure(lookup, config, False)
-        full_closure = TransitiveClosure(lookup, config, True)
+        # Perform a transitive closure for modules from the configuration.
+        # The result of this transitive closure is a set of main modules.
+        explicit_modules = {lookup(module) for module in config.modules}
+        base_closure = TransitiveClosure(lookup, explicit_modules, mask, gnumake)
+        full_closure = TransitiveClosure(lookup, set(base_closure), mask, gnumake, True)
+
+        # Once the full transitive closure is completed, populate the database.
         main_modules = set(base_closure)
-        explicit_modules = set()
-        for module in full_closure:
-            if module.name in config.modules:
-                explicit_modules.add(module)
         final_modules = set(full_closure) if config.tests else main_modules
-        test_modules = (final_modules - set(filter(_applicability, main_modules)))
+        test_modules = (final_modules - set(filter(_applicability, sorted(main_modules))))
         libtests = _libtests(test_modules)
         if _dummy(main_modules):
             main_modules.add(DummyModule())
         if _dummy(test_modules) and libtests:
             test_modules.add(DummyModule())
         main_files = _files(main_modules)
-        test_files = set()
-        for file in _files(test_modules):
-            if file.startswith("lib/"):
-                file = ("tests=lib/" + file[len("lib/"):])
-            test_files.add(file)
+        test_files = _files(test_modules)
 
         self.__libtests = libtests
         self.__closure = full_closure
@@ -916,6 +961,14 @@ class Database:
         self.__test_files = tuple(sorted(test_files))
 
 
+    def __iter__(self):
+        def _iter():
+            for dependency in self.__closure:
+                for (demander, condition) in self.__closure.demanders(dependency):
+                    yield (dependency, demander, condition)
+        return iter(sorted(_iter()))
+
+
     def conditional(self, module):
         """
         Test whether module is a conditional dependency.
@@ -936,12 +989,12 @@ class Database:
 
     def demanders(self, module):
         """For each demander which requires the module yield the demander and the corresponding condition."""
-        return self.__closure.demanders(module)
+        return sorted(self.__closure.demanders(module))
 
 
     def dependencies(self, module):
         """For each dependency of the module yield this dependency and the corresponding condition."""
-        return self.__closure.dependencies(module)
+        return sorted(self.__closure.dependencies(module))
 
 
     @property
index 0ef96a3ee06f576396caa8e86bef29b61e3725d5..055798ecc552a3fab43e7b0d8817d9aeb9363e72 100644 (file)
@@ -647,6 +647,7 @@ class CommandLine:
                         "specify the library name; defaults to 'libgnu'",
                     ),
                     "action": _Option,
+                    "dest": "libname",
                     "metavar": "LIBRARY",
                 }),
                 (["--source-base"], {
@@ -942,7 +943,10 @@ class CommandLine:
             fmt = "--{0}: expected at least one argument"
             self.__parser.error(fmt.format(mode))
         verbosity = namespace.pop("verbosity", 0)
+        namespace.pop("no_changelog", None)
+
         options = dict(namespace)
+        namespace.setdefault("root", ".")
         options.setdefault("dry_run", False)
         namespace["overrides"] = list(reversed(namespace.get("overrides", [])))
         return (namespace, mode, verbosity, options)
index a169393c0a2769bbf3bfb4da6a9129734218210e..aff7742aaf80865a7aa388f2e23061f1926fa63a 100644 (file)
@@ -4,13 +4,9 @@
 
 
 
-import os as _os
 import subprocess as _sp
 
 
-from .error import type_assert as _type_assert
-
-
 
 class _PipeMeta(type):
     __INSTANCE = None
@@ -29,11 +25,6 @@ class Pipe(metaclass=_PipeMeta):
 class Executable:
     """command-line program or script"""
     def __init__(self, name, path=None, encoding=None):
-        _type_assert("name", name, str)
-        if path is not None:
-            _type_assert("path", path, str)
-        if encoding is not None:
-            _type_assert("encoding", encoding, str)
         self.__name = name
         self.__path = path
         self.__encoding = encoding
index 537cea2e985b0ed798c5f9d2700f8209557dcf07..bcd55f572ac43922ff75a0d7d604da4a5285b1b4 100644 (file)
@@ -12,7 +12,6 @@ import tempfile as _tempfile
 import subprocess as _sp
 
 
-from .error import type_assert as _type_assert
 from .error import UnknownModuleError as _UnknownModuleError
 from .module import DummyModule as _DummyModule
 from .module import GnulibModule as _GnulibModule
@@ -21,19 +20,21 @@ from .module import GnulibModule as _GnulibModule
 
 class BaseVFS:
     """gnulib generic virtual file system"""
-    def __init__(self, prefix, **table):
-        _type_assert("prefix", prefix, str)
+    __slots__ = ("__origin", "__root", "__table")
+
+
+    def __init__(self, origin, **table):
         self.__table = {}
         for (key, value) in table.items():
-            _type_assert(key, value, str)
             self.__table[key] = _os.path.normpath(value)
-        self.__prefix = prefix
+        self.__origin = _os.path.normpath(origin)
+        self.__root = _os.path.abspath(origin)
 
 
     def __repr__(self):
         module = self.__class__.__module__
         name = self.__class__.__name__
-        return "{}.{}{{{}}}".format(module, name, repr(self.__prefix))
+        return "{}.{}{{{}}}".format(module, name, repr(self.__origin))
 
 
     def __enter__(self):
@@ -45,15 +46,13 @@ class BaseVFS:
 
 
     def __contains__(self, name):
-        path = _os.path.normpath(name)
         if _os.path.isabs(name):
             raise ValueError("name must be a relative path")
-        path = _os.path.join(self.absolute, self[name])
+        path = _os.path.join(self.__root, self[name])
         return _os.path.exists(path)
 
 
     def __getitem__(self, name):
-        _type_assert("name", name, str)
         parts = []
         replaced = False
         name = _os.path.normpath(name)
@@ -67,13 +66,11 @@ class BaseVFS:
                 part = self.__table[part]
                 replaced = True
             parts += [part]
-        name = _os.path.sep.join(parts)
-        return _os.path.normpath(name)
+        return _os.path.sep.join(parts)
 
 
     def __setitem__(self, src, dst):
         for name in (src, dst):
-            _type_assert("name", name, str)
             if _os.path.isabs(name):
                 raise ValueError("name cannot be an absoule path")
         src = _os.path.normpath(src)
@@ -82,15 +79,15 @@ class BaseVFS:
 
 
     @property
-    def relative(self):
-        """BaseVFS VFS name"""
-        return self.__prefix
+    def origin(self):
+        """origin VFS path"""
+        return self.__origin
 
 
     @property
-    def absolute(self):
+    def root(self):
         """absolute VFS path"""
-        return _os.path.abspath(self.__prefix)
+        return self.__root
 
 
 
@@ -114,8 +111,6 @@ def lookup(name, primary, secondary, patch="patch"):
 
     NOTE: It is up to the caller to unlink files obtained after dynamic patching.
     """
-    _type_assert("primary", primary, BaseVFS)
-    _type_assert("secondary", secondary, BaseVFS)
     if name in secondary:
         return (secondary, name)
     diff = "{}.diff".format(name)
@@ -141,14 +136,14 @@ def lookup(name, primary, secondary, patch="patch"):
 def mkdir(root, name):
     """Create a leaf directory and all intermediate ones recursively."""
     root = BaseVFS(".") if root is None else root
-    path = name if _os.path.isabs(name) else _os.path.join(root.absolute, root[name])
+    path = name if _os.path.isabs(name) else _os.path.join(root.root, root[name])
     _os.makedirs(root[name], exist_ok=True)
 
 
 def backup(root, name):
     """Backup the given file."""
     root = BaseVFS(".") if root is None else root
-    original_path = _os.path.join(root.absolute, root[name])
+    original_path = _os.path.join(root.root, root[name])
     backup_path = "{}~".format(original_path)
     try:
         _os.unlink(backup_path)
@@ -163,9 +158,9 @@ def compare(lhs_root, lhs_name, rhs_root, rhs_name):
     rhs_root = BaseVFS(".") if rhs_root is None else rhs_root
     (lhs_path, rhs_path) = (lhs_name, rhs_name)
     if not _os.path.isabs(lhs_name):
-        lhs_path = _os.path.join(lhs_root.absolute, lhs_root[lhs_name])
+        lhs_path = _os.path.join(lhs_root.root, lhs_root[lhs_name])
     if not _os.path.isabs(rhs_name):
-        rhs_path = _os.path.join(rhs_root.absolute, rhs_root[rhs_name])
+        rhs_path = _os.path.join(rhs_root.root, rhs_root[rhs_name])
     return _filecmp.cmp(lhs_path, rhs_path, shallow=False)
 
 
@@ -181,9 +176,9 @@ def copy(src_root, src_name, dst_root, dst_name):
     mkdir(dst_root, _os.path.dirname(dst_name))
     (src_path, dst_path) = (src_name, dst_name)
     if not _os.path.isabs(src_name):
-        src_path = _os.path.join(src_root.absolute, src_root[src_name])
+        src_path = _os.path.join(src_root.root, src_root[src_name])
     if not _os.path.isabs(dst_name):
-        dst_path = _os.path.join(dst_root.absolute, dst_root[dst_name])
+        dst_path = _os.path.join(dst_root.root, dst_root[dst_name])
     with _codecs.open(src_path, "rb") as istream:
         with _codecs.open(dst_path, "wb") as ostream:
             while 1:
@@ -196,7 +191,7 @@ def copy(src_root, src_name, dst_root, dst_name):
 def exists(root, name):
     """Check whether the given file exists."""
     root = BaseVFS(".") if root is None else root
-    path = name if _os.path.isabs(name) else _os.path.join(root.absolute, root[name])
+    path = name if _os.path.isabs(name) else _os.path.join(root.root, root[name])
     return _os.path.exists(path)
 
 
@@ -212,9 +207,9 @@ def hardlink(src_root, src_name, dst_root, dst_name):
     mkdir(dst_root, _os.path.dirname(dst_name))
     (src_path, dst_path) = (src_name, dst_name)
     if not _os.path.isabs(src_name):
-        src_path = _os.path.join(src_root.absolute, src_root[src_name])
+        src_path = _os.path.join(src_root.root, src_root[src_name])
     if not _os.path.isabs(dst_name):
-        dst_path = _os.path.join(dst_root.absolute, dst_root[dst_name])
+        dst_path = _os.path.join(dst_root.root, dst_root[dst_name])
     _os.link(src_path, dst_path)
 
 
@@ -229,16 +224,16 @@ def move(src_root, src_name, dst_root, dst_name):
     mkdir(dst_root, _os.path.dirname(dst_name))
     (src_path, dst_path) = (src_name, dst_name)
     if not _os.path.isabs(src_name):
-        src_path = _os.path.join(src_root.absolute, src_root[src_name])
+        src_path = _os.path.join(src_root.root, src_root[src_name])
     if not _os.path.isabs(dst_name):
-        dst_path = _os.path.join(dst_root.absolute, dst_root[dst_name])
+        dst_path = _os.path.join(dst_root.root, dst_root[dst_name])
     _os.rename(src_path, dst_path)
 
 
 def iostream(root, name, mode="r", encoding=None):
     """Open file and return a stream. Raise IOError upon failure."""
     root = BaseVFS(".") if root is None else root
-    path = name if _os.path.isabs(name) else _os.path.join(root.absolute, root[name])
+    path = name if _os.path.isabs(name) else _os.path.join(root.root, root[name])
     return _codecs.open(path, mode, encoding)
 
 
@@ -246,7 +241,7 @@ def readlink(root, name):
     """Obtain the path to which the symbolic link points."""
     root = BaseVFS(".") if root is None else root
     mkdir(root, _os.path.dirname(name))
-    path = name if _os.path.isabs(name) else _os.path.join(root.absolute, root[name])
+    path = name if _os.path.isabs(name) else _os.path.join(root.root, root[name])
     return _os.readlink(path)
 
 
@@ -262,16 +257,16 @@ def symlink(src_root, src_name, dst_root, dst_name, relative=True):
     if not relative:
         (src_path, dst_path) = (src_name, dst_name)
         if not _os.path.isabs(src_name):
-            src_path = _os.path.join(src_root.absolute, src_root[src_name])
+            src_path = _os.path.join(src_root.root, src_root[src_name])
         if not _os.path.isabs(dst_name):
-            dst_path = _os.path.join(dst_root.absolute, dst_root[dst_name])
+            dst_path = _os.path.join(dst_root.root, dst_root[dst_name])
     else:
-        src_path = _os.path.join(src_root.relative, src_root[src_name])
-        dst_path = _os.path.join(dst_root.relative, dst_root[dst_name])
+        src_path = _os.path.join(src_root.origin, src_root[src_name])
+        dst_path = _os.path.join(dst_root.origin, dst_root[dst_name])
         prefix = _os.path.relpath(_os.path.dirname(src_path), _os.path.dirname(dst_path))
         suffix = _os.path.basename(src_root[src_name])
         src_path = _os.path.join(prefix, suffix)
-        dst_path = _os.path.join(dst_root.absolute, dst_root[dst_name])
+        dst_path = _os.path.join(dst_root.root, dst_root[dst_name])
     _os.symlink(src_path, dst_path)
 
 
@@ -279,13 +274,16 @@ def unlink(root, name, backup=True):
     """Unlink a file, backing it up if necessary."""
     root = BaseVFS(".") if root is None else root
     mkdir(root, _os.path.dirname(name))
-    path = name if _os.path.isabs(name) else _os.path.join(root.absolute, root[name])
+    path = name if _os.path.isabs(name) else _os.path.join(root.root, root[name])
     _os.unlink(path)
 
 
 
 class GnulibGitVFS(BaseVFS):
     """gnulib git repository"""
+    __slots__ = ("__cache", "__modules")
+
+
     _EXCLUDE = {
         "."                 : str.startswith,
         "~"                 : str.endswith,
@@ -299,23 +297,23 @@ class GnulibGitVFS(BaseVFS):
     }
 
 
-    def __init__(self, prefix, **table):
-        super().__init__(prefix, **table)
+    def __init__(self, origin):
+        super().__init__(origin=origin)
         self.__cache = {"dummy": _DummyModule()}
-        if not _os.path.exists(self.absolute):
-            raise FileNotFoundError(self.absolute)
-        if not _os.path.isdir(self.absolute):
-            raise NotADirectoryError(self.absolute)
-        if not _os.path.isdir(_os.path.join(self.absolute, ".git")):
+        self.__modules = _os.path.join(self.root, "modules")
+        if not _os.path.exists(self.root):
+            raise FileNotFoundError(self.root)
+        if not _os.path.isdir(self.root):
+            raise NotADirectoryError(self.root)
+        if not _os.path.isdir(_os.path.join(self.root, ".git")):
             raise TypeError("{} is not a gnulib repository".format(prefix))
 
 
     def module(self, name):
         """instantiate a module"""
-        _type_assert("name", name, str)
         if name in self.__cache:
             return self.__cache[name]
-        path = _os.path.join(self.absolute, self["modules"], name)
+        path = _os.path.join(self.__modules, name)
         try:
             self.__cache[name] = _GnulibModule(path=path, name=name)
             return self.__cache[name]
@@ -325,8 +323,7 @@ class GnulibGitVFS(BaseVFS):
 
     def modules(self):
         """iterate over all available modules"""
-        prefix = _os.path.join(self.absolute, self["modules"])
-        for root, _, files in _os.walk(prefix):
+        for root, _, files in _os.walk(self.__modules):
             names = []
             for name in files:
                 exclude = False
@@ -338,5 +335,5 @@ class GnulibGitVFS(BaseVFS):
                     names += [name]
             for name in names:
                 path = _os.path.join(root, name)
-                name = path[len(prefix) + 1:]
+                name = path[len(self.__modules) + 1:]
                 yield self.module(name)