From ee67e65c57c6936ae9bccc7daf767c345cbc8e7c Mon Sep 17 00:00:00 2001 From: Dmitry Selyutin Date: Mon, 21 Aug 2017 23:17:41 +0300 Subject: [PATCH] [pygnulib] BaseConfig, CachedConfig and Module classes (sketches) --- pygnulib/config.py | 206 +++++++++++++++++++++++++++++++++++++++++++++ pygnulib/module.py | 96 +++++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 pygnulib/config.py create mode 100644 pygnulib/module.py diff --git a/pygnulib/config.py b/pygnulib/config.py new file mode 100644 index 0000000000..faf39fdf39 --- /dev/null +++ b/pygnulib/config.py @@ -0,0 +1,206 @@ +#!/usr/bin/python +# encoding: UTF-8 + + +import codecs +import os +import re + + +class GenericConfig: + """The most basic gnulib configuration holder""" + _TABLE_ = { + "gnulib" : "", + "root" : "", + "local-dir" : "", + "source-base" : "lib", + "m4-base" : "m4", + "po-base" : "po", + "doc-base" : "doc", + "tests-base" : "tests", + "aux-dir" : "", + "lib" : "libgnu", + "makefile-name" : "Makefile.am", + "macro-prefix" : "gl", + "po-domain" : "", + "witness-c-macro" : "", + "lgpl" : 0, + "tests" : False, + "cxx-tests" : False, + "longrunning-tests" : False, + "privileged-tests" : False, + "unportable-tests" : False, + "all-tests" : False, + "libtool" : False, + "conddeps" : False, + "vc-files" : False, + "autoconf" : 2.59, + "modules" : [], + "avoids" : [], + "files" : [], + } + + + def __repr__(self): + return "pygnulib.GenericConfig" + str(self._table_) + + + def __init__(self, gnulib, root, **kwargs): + if not (os.path.isdir(gnulib) and os.path.isdir(os.path.join(gnulib, ".git"))): + raise NotADirectoryError(gnulib) + if not os.path.isdir(root): + raise NotADirectoryError(gnulib) + self._table_ = dict() + for arg in GenericConfig._TABLE_: + self._table_[arg] = GenericConfig._TABLE_[arg] + self._table_["gnulib"] = gnulib + self._table_["root"] = root + for arg in kwargs: + self[arg] = kwargs[arg] + + + def __getitem__(self, key): + if key not in GenericConfig._TABLE_: + key = key.replace("_", "-") + if key not in GenericConfig._TABLE_: + raise KeyError("unsupported option: '%s'" % key) + return self._table_[key] + + + def __setitem__(self, key, value): + if key not in GenericConfig._TABLE_: + key = key.replace("_", "-") + if key not in GenericConfig._TABLE_: + raise KeyError("unsupported option: '%s'" % key) + + typeid = type(GenericConfig._TABLE_[key]) + if key == "lgpl": + if value not in [None, 2, 3]: + raise TypeError("lgpl option must be either None or integral version (2 or 3)") + elif key == "autoconf": + if value < 2.59: + raise NotImplementedError("pygnulib ") + elif not isinstance(value, typeid): + raise TypeError("'%s' option must be of '%s' type" % (key, typeid)) + + tests = ["tests", "cxx-tests", "longrunning-tests", "privileged-tests", "unportable-tests"] + if key == "all-tests": + for _ in tests: + self._table_[_] = value + else: + self._table_[key] = value + + + def path(path): + path = os.path.normpath(path) + os.path.split(os.path.sep) + + +class CachedConfig(GenericConfig): + """Unlike GenericConfig, CachedConfig tries to retrieve variables from the cached files.""" + _AUTOCONF_ = { + "autoconf" : re.compile(".*AC_PREREQ\\(\\[(.*?)\\]\\)", re.S | re.M), + "aux-dir" : re.compile("^AC_CONFIG_AUX_DIR\\(\\[(.*?)\\]\\)$", re.S | re.M), + "libtool" : re.compile("A[CM]_PROG_LIBTOOL", re.S | re.M) + } + _GNULIB_CACHE_ = { + "libtool" : (bool, "gl_LIBTOOL"), + "conddeps" : (bool, "gl_CONDITIONAL_DEPENDENCIES"), + "vc-files" : (bool, "gl_VC_FILES"), + "tests" : (bool, "gl_WITH_TESTS"), + "obsolete" : (bool, "gl_WITH_OBSOLETE"), + "cxx-tests" : (bool, "gl_WITH_CXX_TESTS"), + "longrunning-tests" : (bool, "gl_WITH_LONGRUNNING_TESTS"), + "privileged-tests" : (bool, "gl_WITH_PRIVILEGED_TESTS"), + "unportable-tests" : (bool, "gl_WITH_UNPORTABLE_TESTS"), + "all-tests" : (bool, "gl_WITH_ALL_TESTS"), + "local-dir" : (str, "gl_LOCAL_DIR"), + "source-base" : (str, "gl_SOURCE_BASE"), + "m4-base" : (str, "gl_M4_BASE"), + "po-base" : (str, "gl_PO_BASE"), + "doc-base" : (str, "gl_DOC_BASE"), + "tests-base" : (str, "gl_TESTS_BASE"), + "makefile-name" : (str, "gl_MAKEFILE_NAME"), + "macro-prefix" : (str, "gl_MACRO_PREFIX"), + "po-domain" : (str, "gl_PO_DOMAIN"), + "witness-c-macro" : (str, "gl_WITNESS_C_MACRO"), + "lib" : (str, "gl_LIB"), + "modules" : (list, "gl_MODULES"), + "avoids" : (list, "gl_AVOID"), + "lgpl" : (str, "gl_LGPL"), + } + _GNULIB_CACHE_BOOL_ = [] + _GNULIB_CACHE_STR_ = [] + _GNULIB_CACHE_LIST_ = [] + for _key_, (_typeid_, _) in _GNULIB_CACHE_.items(): + if _typeid_ is bool: + _GNULIB_CACHE_BOOL_ += [_key_] + elif _typeid_ is str: + _GNULIB_CACHE_STR_ += [_key_] + else: + _GNULIB_CACHE_LIST_ += [_key_] + _GNULIB_CACHE_PATTERN_ = re.compile("^(gl_.*?)\\(\\[(.*?)\\]\\)$", re.S | re.M) + + + def __init__(self, gnulib, root, autoconf=None, **kwargs): + super(CachedConfig, self).__init__(gnulib, root, **kwargs) + self._autoconf_(autoconf) + self._gnulib_cache_() + self._gnulib_comp_() + + def _autoconf_(self, autoconf): + root = self["root"] + if not autoconf: + autoconf = os.path.join(root, "configure.ac") + if not os.path.exists(autoconf): + autoconf = os.path.join(root, "configure.in") + if not os.path.isabs(autoconf): + autoconf = os.path.join(root, autoconf) + with codecs.open(autoconf, "rb", "UTF-8") as stream: + data = stream.read() + for key, pattern in CachedConfig._AUTOCONF_.items(): + match = pattern.findall(data) + if not match: + continue + if key == "autoconf": + self["autoconf"] = sorted(set([float(_.strip()) for _ in match if _.strip()]))[-1] + else: + self[key] = match[-1] + + def _gnulib_cache_(self): + root = self["root"] + m4base = self["m4-base"] + gnulib_cache = os.path.join(root, m4base, "gnulib-cache.m4") + if os.path.exists(gnulib_cache): + with codecs.open(gnulib_cache, "rb", "UTF-8") as stream: + data = stream.read() + for key in CachedConfig._GNULIB_CACHE_BOOL_: + (_, macro) = CachedConfig._GNULIB_CACHE_[key] + if key in data: + self[key] = True + match = dict(CachedConfig._GNULIB_CACHE_PATTERN_.findall(data)) + for key in CachedConfig._GNULIB_CACHE_STR_: + (_, macro) = CachedConfig._GNULIB_CACHE_[key] + if macro in match: + self[key] = match[macro].strip() + for key in CachedConfig._GNULIB_CACHE_LIST_: + (_, macro) = CachedConfig._GNULIB_CACHE_[key] + if macro in match: + self[key] = [_.strip() for _ in match[macro].split("\n") if _.strip()] + + def _gnulib_comp_(self): + root = self["root"] + m4base = self["m4-base"] + gnulib_comp = os.path.join(root, m4base, "gnulib-comp.m4") + if os.path.exists(gnulib_comp): + with codecs.open(gnulib_comp, "rb", "UTF-8") as stream: + data = stream.read() + regex = "AC_DEFUN\\(\\[%s_FILE_LIST\\], \\[(.*?)\\]\\)" % self["macro-prefix"] + pattern = re.compile(regex, re.S | re.M) + match = pattern.findall(data) + if match: + self["files"] = [_.strip() for _ in match[-1].split("\n") if _.strip()] + + + def __repr__(self): + return "pygnulib.CachedConfig" + str(self._table_) diff --git a/pygnulib/module.py b/pygnulib/module.py new file mode 100644 index 0000000000..8fd168be27 --- /dev/null +++ b/pygnulib/module.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# encoding: UTF-8 + + +import os +import re +import codecs + + +class Module: + _TABLE_ = { + "description" : (0x00, str, None, "Description"), + "comment" : (0x01, str, None, "Comment"), + "status" : (0x02, str, None, "Status"), + "notice" : (0x03, str, None, "Notice"), + "applicability" : (0x04, str, None, "Applicability"), + "files" : (0x05, list, None, "Files"), + "dependencies" : (0x06, list, None, "Depends-on"), + "preconfigure" : (0x07, str, None, "configure.ac-early"), + "configure" : (0x08, str, None, "configure.ac"), + "makefile" : (0x09, str, None, "Makefile.am"), + "include" : (0x0A, list, None, "Include"), + "link" : (0x0B, list, None, "Link"), + "license" : (0x0C, str, None, "License"), + "maintainers" : (0x0D, list, None, "Maintainer"), + } + _EXCLUDE_ = { + "CVS", + "ChangeLog", + "COPYING", + "README", + "TEMPLATE", + "TEMPLATE-EXTENDED", + } + for key, (uid, typeid, _, field) in _TABLE_.items(): + suffix = ("(?:" + ":|".join([_[3] for _ in _TABLE_.values()]) + ":)") + pattern = re.compile(("^%s:\\s*(.*?)%s" % (field, suffix)), re.S | re.M) + _TABLE_[key] = (uid, typeid, pattern, field) + + + def __init__(self): + self._table_ = dict() + + + def __getitem__(self, key): + if key not in Module._TABLE_: + raise ValueError("unsupported key: '%s'" % key) + return self._table_[key] + + + def __setitem__(self, key, value): + if key not in Module._TABLE_: + raise ValueError("unsupported key: '%s'" % key) + typeid = Module._TABLE_[key][1] + if not isinstance(value, typeid): + raise TypeError("'%s' key expects '%s' type" % (key, typeid)) + self._table_[key] = value + + + @classmethod + def file(cls, path): + module = Module() + with codecs.open(path, "rb", "UTF-8") as stream: + data = stream.read() + for key, (_, typeid, pattern, field) in Module._TABLE_.items(): + match = pattern.findall(data) + if typeid is list: + module[key] = [_ for _ in "".join(match).split("\n") if _.strip()] + else: + module[key] = "\n".join([_.strip() for _ in match]) if match else "" + return module + + + @classmethod + def list(cls, config): + base = os.path.join(config["gnulib"], "modules") + for root, _, files in os.walk(base): + for file in [_ for _ in files if _ not in Module._EXCLUDE_]: + yield file + + + def package(self): + result = "" + for key, (_, typeid, _, field) in sorted(Module._TABLE_.items(), key=lambda k: k[1][0]): + field += ":\n" + if typeid is list: + value = "\n".join(self[key]) + else: + value = self[key] + if key == "maintainers" and not value: + value = "all" + if value: + result += field + result += value + result += "\n\n" if value else "\n" + return result.strip() + "\n" -- 2.39.5