--- /dev/null
+#!/usr/bin/python
+# encoding: UTF-8
+
+
+import argparse
+import codecs
+import os
+import re
+
+from .error import AutoconfVersionError
+
+
+class Config:
+ """gnulib generic configuration"""
+ _TABLE_ = {
+ "root" : "",
+ "local_dir" : "",
+ "source_base" : "lib",
+ "m4_base" : "m4",
+ "po_base" : "po",
+ "doc_base" : "doc",
+ "tests_base" : "tests",
+ "auxdir" : "",
+ "lib" : "libgnu",
+ "makefile_name" : "Makefile.am",
+ "macro_prefix" : "gl",
+ "po_domain" : "",
+ "witness_c_macro" : "",
+ "lgpl" : 0,
+ "tests" : False,
+ "obsolete" : 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" : [],
+ "avoid" : [],
+ "files" : [],
+ }
+
+
+ def __init__(self, **kwargs):
+ self._table_ = dict()
+ for key in Config._TABLE_:
+ self._table_[key] = Config._TABLE_[key]
+ for key, value in kwargs.items():
+ self[key] = value
+
+
+ def __repr__(self):
+ return repr(self._table_)
+
+
+ def __iter__(self):
+ for key in Config._TABLE_:
+ value = self[key]
+ yield key, value
+
+
+ @property
+ def source_base(self):
+ """directory relative to ROOT where source code is placed; defaults to 'lib'"""
+ return self["source_base"]
+
+ @source_base.setter
+ def source_base(self, value):
+ self["source_base"] = value
+
+
+ @property
+ def m4_base(self):
+ """directory relative to ROOT where *.m4 macros are placed; defaults to 'm4'"""
+ return self["m4_base"]
+
+ @m4_base.setter
+ def m4_base(self, value):
+ self["m4_base"] = value
+
+
+ @property
+ def po_base(self):
+ """directory relative to ROOT where *.po files are placed; defaults to 'po'"""
+ return self["po_base"]
+
+ @po_base.setter
+ def po_base(self, value):
+ self["po_base"] = value
+
+
+ @property
+ def doc_base(self):
+ """directory relative to ROOT where doc files are placed; defaults to 'doc'"""
+ return self["doc_base"]
+
+ @doc_base.setter
+ def doc_base(self, value):
+ self["doc_base"] = value
+
+
+ @property
+ def tests_base(self):
+ """directory relative to ROOT where unit tests are placed; defaults to 'tests'"""
+ return self["tests_base"]
+
+ @tests_base.setter
+ def tests_base(self, value):
+ self["tests_base"] = value
+
+
+ @property
+ def auxdir(self):
+ """directory relative to ROOT where auxiliary build tools are placed"""
+ return self["auxdir"]
+
+ @auxdir.setter
+ def auxdir(self, value):
+ self["auxdir"] = value
+
+
+ @property
+ def lib(self):
+ """library name; defaults to 'libgnu'"""
+ return self["lib"]
+
+ @lib.setter
+ def lib(self, value):
+ self["lib"] = value
+
+
+ @property
+ def makefile_name(self):
+ """name of makefile in automake syntax in the source-base and tests-base directories"""
+ return self["makefile_name"]
+
+ @makefile_name.setter
+ def makefile_name(self, value):
+ self["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["macro_prefix"]
+
+ @macro_prefix.setter
+ def macro_prefix(self, value):
+ self["macro_prefix"] = value
+
+
+ @property
+ def po_domain(self):
+ """the prefix of the i18n domain"""
+ return self["po_domain"]
+
+ @po_domain.setter
+ def po_domain(self, value):
+ self["po_domain"] = value
+
+
+ @property
+ def witness_c_macro(self):
+ """the C macro that is defined when the sources are compiled or used"""
+ return self["witness_c_macro"]
+
+ @witness_c_macro.setter
+ def witness_c_macro(self, value):
+ self["witness_c_macro"] = value
+
+
+ @property
+ def lgpl(self):
+ """abort if modules aren't available under the LGPL; also modify license template"""
+ return self["lgpl"]
+
+ @lgpl.setter
+ def lgpl(self, value):
+ if value is None:
+ value = 0
+ if value not in [0, 2, 3]:
+ raise TypeError("lgpl option must be either None or integral version (2 or 3)")
+ self["lgpl"] = value
+
+
+ @property
+ def tests(self):
+ """include unit tests for the included modules"""
+ return self["tests"]
+
+ @tests.setter
+ def tests(self, value):
+ self["tests"] = value
+
+
+ @property
+ def obsolete(self):
+ """include obsolete modules when they occur among the modules"""
+ return self["obsolete"]
+
+ @obsolete.setter
+ def obsolete(self, value):
+ self["obsolete"] = value
+
+
+ @property
+ def cxx_tests(self):
+ """include even unit tests for C++ interoperability"""
+ return self["cxx_tests"]
+
+ @cxx_tests.setter
+ def cxx_tests(self, value):
+ self["cxx_tests"] = value
+
+
+ @property
+ def longrunning_tests(self):
+ """include even unit tests that are long-runners"""
+ return self["longrunning_tests"]
+
+ @longrunning_tests.setter
+ def longrunning_tests(self, value):
+ self["longrunning_tests"] = value
+
+
+ @property
+ def privileged_tests(self):
+ """include even unit tests that require root privileges"""
+ return self["privileged_tests"]
+
+ @privileged_tests.setter
+ def privileged_tests(self, value):
+ self["privileged_tests"] = value
+
+
+ @property
+ def unportable_tests(self):
+ """include even unit tests that fail on some platforms"""
+ return self["unportable_tests"]
+
+ @unportable_tests.setter
+ def unportable_tests(self, value):
+ self["unportable_tests"] = value
+
+
+ @property
+ def all_tests(self):
+ """include all kinds of problematic unit tests"""
+ result = True
+ result &= self.tests
+ result &= self.cxx_tests
+ result &= self.privileged_tests
+ result &= self.unportable_tests
+ result &= self.longrunning_tests
+ return result
+
+ @all_tests.setter
+ def all_tests(self, value):
+ self.tests = value
+ self.cxx_tests = value
+ self.privileged_tests = value
+ self.unportable_tests = value
+ self.longrunning_tests = value
+
+
+ @property
+ def libtool(self):
+ """use libtool rules"""
+ return self["libtool"]
+
+ @libtool.setter
+ def libtool(self, value):
+ self["libtool"] = value
+
+
+ @property
+ def conddeps(self):
+ """support conditional dependencies (may save configure time and object code)"""
+ return self["conddeps"]
+
+ @conddeps.setter
+ def conddeps(self, value):
+ self["conddeps"] = value
+
+
+ @property
+ def vc_files(self):
+ """update version control related files"""
+ return self["vc_files"]
+
+ @vc_files.setter
+ def vc_files(self, value):
+ self["vc_files"] = value
+
+
+ @property
+ def autoconf(self):
+ """autoconf version"""
+ return self["autoconf"]
+
+ @autoconf.setter
+ def autoconf(self, value):
+ self["autoconf"] = value
+
+
+ @property
+ def modules(self):
+ """list of modules"""
+ return self["modules"]
+
+ @modules.setter
+ def modules(self, value):
+ self["modules"] = tuple(value)
+
+
+ @property
+ def avoid(self):
+ """list of modules to avoid"""
+ return self["avoid"]
+
+ @avoid.setter
+ def avoid(self, value):
+ self["avoid"] = tuple(value)
+
+
+ @property
+ def files(self):
+ """list of files to be processed"""
+ return self["files"]
+
+ @files.setter
+ def files(self, value):
+ self["files"] = list(value)
+
+
+ @property
+ def include_guard_prefix(self):
+ """include guard prefix"""
+ prefix = self["macro_prefix"].upper()
+ default = Config._TABLE_["macro_prefix"]
+ return "GL_%s" % prefix if prefix == default else "GL"
+
+
+ def __getitem__(self, key):
+ if key not in Config._TABLE_:
+ key = key.replace("-", "_")
+ if key not in Config._TABLE_:
+ raise KeyError("unsupported option: %r" % key)
+ return self._table_[key]
+
+
+ def __setitem__(self, key, value):
+ if key not in Config._TABLE_:
+ key = key.replace("_", "-")
+ if key not in Config._TABLE_:
+ raise KeyError("unsupported option: %r" % key)
+ key = key.replace("-", "_")
+ typeid = type(Config._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 AutoconfVersionError(2.59)
+ elif not isinstance(value, typeid):
+ raise TypeError("%r option must be of %r type" % (key, typeid))
+ self._table_[key] = value
+
+
+ def items(self):
+ """a set-like object providing a view on configuration items"""
+ return self._table_.items()
+
+
+ def keys(self):
+ """a set-like object providing a view on configuration keys"""
+ return self._table_.keys()
+
+
+ def values(self):
+ """a set-like object providing a view on configuration values"""
+ return self._table_.values()
+
+
+class Cache(Config):
+ """gnulib cached configuration"""
+ _AUTOCONF_ = {
+ "autoconf" : re.compile(".*AC_PREREQ\\(\\[(.*?)\\]\\)", re.S | re.M),
+ "auxdir" : 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"),
+ "avoid" : (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, root, autoconf=None, **kwargs):
+ if not isinstance(root, str):
+ raise TypeError("root must be of 'str' type")
+ super().__init__(**kwargs)
+ self._autoconf_(root, autoconf)
+ self._gnulib_cache_(root)
+ self._gnulib_comp_(root)
+
+ def _autoconf_(self, root, autoconf):
+ 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)
+ autoconf = os.path.normpath(autoconf)
+ with codecs.open(autoconf, "rb", "UTF-8") as stream:
+ data = stream.read()
+ for key, pattern in Cache._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):
+ 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 Cache._GNULIB_CACHE_BOOL_:
+ (_, macro) = Cache._GNULIB_CACHE_[key]
+ if key in data:
+ self[key] = True
+ match = dict(Cache._GNULIB_CACHE_PATTERN_.findall(data))
+ for key in Cache._GNULIB_CACHE_STR_:
+ (_, macro) = Cache._GNULIB_CACHE_[key]
+ if macro in match:
+ self[key] = match[macro].strip()
+ for key in Cache._GNULIB_CACHE_LIST_:
+ (_, macro) = Cache._GNULIB_CACHE_[key]
+ if macro in match:
+ self[key] = [_.strip() for _ in match[macro].split("\n") if _.strip()]
+
+ def _gnulib_comp_(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()]
+
+
+
+class CommandLine(Config):
+ """gnulib-tool command line configuration"""
+ _LIST_ = (1 << 0)
+ _FIND_ = (1 << 1)
+ _IMPORT_ = (1 << 2)
+ _ADD_IMPORT_ = (1 << 3)
+ _REMOVE_IMPORT_ = (1 << 4)
+ _UPDATE_ = (1 << 5)
+ _TEST_DIRECTORY_ = (1 << 6)
+ _MEGA_TEST_DIRECTORY_ = (1 << 7)
+ _TEST_ = (1 << 8)
+ _MEGA_TEST_ = (1 << 9)
+ _COPY_FILE_ = (1 << 10)
+ _EXTRACT_DESCRIPTION_ = (1 << 11)
+ _EXTRACT_COMMENT_ = (1 << 12)
+ _EXTRACT_STATUS_ = (1 << 13)
+ _EXTRACT_NOTICE_ = (1 << 14)
+ _EXTRACT_APPLICABILITY_ = (1 << 15)
+ _EXTRACT_FILELIST_ = (1 << 16)
+ _EXTRACT_DEPENDENCIES_ = (1 << 17)
+ _EXTRACT_AUTOCONF_SNIPPET_ = (1 << 18)
+ _EXTRACT_AUTOMAKE_SNIPPET_ = (1 << 19)
+ _EXTRACT_INCLUDE_DIRECTIVE_ = (1 << 20)
+ _EXTRACT_LINK_DIRECTIVE_ = (1 << 21)
+ _EXTRACT_LICENSE_ = (1 << 22)
+ _EXTRACT_MAINTAINER_ = (1 << 23)
+ _EXTRACT_TESTS_MODULE_ = (1 << 24)
+ _ANY_IMPORT_ = _IMPORT_ | _ADD_IMPORT_ | _REMOVE_IMPORT_ | _UPDATE_
+ _ANY_TEST_ = _TEST_ | _MEGA_TEST_ | _TEST_DIRECTORY_ | _MEGA_TEST_DIRECTORY_
+ _ALL_ = _LIST_ | _FIND_ | _ANY_IMPORT_ | _ANY_TEST_
+ _MODES_ = (
+ (_LIST_, "list", ""),
+ (_FIND_, "find", "filename"),
+ (_IMPORT_, "import", "[module1 ... moduleN]"),
+ (_ADD_IMPORT_, "add-import", "[module1 ... moduleN]"),
+ (_REMOVE_IMPORT_, "remove-import", "[module1 ... moduleN]"),
+ (_UPDATE_, "update", ""),
+ (_TEST_DIRECTORY_, "testdir", "--dir=directory [module1 ... moduleN]"),
+ (_MEGA_TEST_DIRECTORY_, "megatestdir", "--dir=directory [module1 ... moduleN]"),
+ (_TEST_, "test", "--dir=directory [module1 ... moduleN]"),
+ (_MEGA_TEST_, "megatest", "--dir=directory [module1 ... moduleN]"),
+ (_EXTRACT_DESCRIPTION_, "extract-description", "module"),
+ (_EXTRACT_COMMENT_, "extract-comment", "module"),
+ (_EXTRACT_STATUS_, "extract-status", "module"),
+ (_EXTRACT_NOTICE_, "extract-notice", "module"),
+ (_EXTRACT_APPLICABILITY_, "extract-applicability", "module"),
+ (_EXTRACT_FILELIST_, "extract-filelist", "module"),
+ (_EXTRACT_DEPENDENCIES_, "extract-dependencies", "module"),
+ (_EXTRACT_AUTOCONF_SNIPPET_, "extract-autoconf-snippet", "module"),
+ (_EXTRACT_AUTOMAKE_SNIPPET_, "extract-automake-snippet", "module"),
+ (_EXTRACT_INCLUDE_DIRECTIVE_, "extract-include-directive", "module"),
+ (_EXTRACT_LINK_DIRECTIVE_, "extract-link-directive", "module"),
+ (_EXTRACT_LICENSE_, "extract-license", "module"),
+ (_EXTRACT_MAINTAINER_, "extract-maintainer", "module"),
+ (_EXTRACT_TESTS_MODULE_, "extract-tests-module", "module"),
+ (_COPY_FILE_, "copy", "file [destination]"),
+ )
+
+
+ class _AvoidAction_(argparse.Action):
+ def __call__(self, parser, namespace, value, option=None):
+ values = getattr(namespace, self.dest)
+ values += value
+
+
+ class _VerboseAction_(argparse.Action):
+ def __call__(self, parser, namespace, value, option=None):
+ value = getattr(namespace, self.dest)
+ verbose = option in ("-v", "--verbose")
+ value += +1 if verbose else -1
+ setattr(namespace, self.dest, value)
+
+
+ # section0: (section0_modes, (([section0_option0, section0_optionN], **section0_kwargs)))
+ # sectionN: (sectionN_modes, (([sectionN_option0, sectionN_optionN], **sectionN_kwargs)))
+ #
+ # for (name, flags, arguments) in sections:
+ # for argument in arguments:
+ # (options, kwargs) = argument
+ _SECTIONS_ = (
+ (
+ "Operation modes",
+ None,
+ (
+ (["-l", "--list"], {
+ "help": (
+ "print the available module names",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _LIST_,
+ }),
+ (["-f", "--find"], {
+ "help": (
+ "find the modules which contain the specified file",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _FIND_,
+ }),
+ (["-i", "--import"], {
+ "help": (
+ "import the given modules into the current package",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _IMPORT_,
+ }),
+ (["-a", "--add-import"], {
+ "help": (
+ "augment the list of imports from gnulib into the",
+ "current package, by adding the given modules;",
+ "if no modules are specified, update the current",
+ "package from the current gnulib",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _ADD_IMPORT_,
+ }),
+ (["-r", "--remove-import"], {
+ "help": (
+ "reduce the list of imports from gnulib into the",
+ "current package, by removing the given modules",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _REMOVE_IMPORT_,
+ }),
+ (["-u", "--update"], {
+ "help": (
+ "update the current package, restore files omitted",
+ "from version control",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _UPDATE_,
+ }),
+
+ (["--extract-description"], {
+ "help": (
+ "extract the description",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_DESCRIPTION_,
+ }),
+ (["--extract-comment"], {
+ "help": (
+ "extract the comment",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_COMMENT_,
+ }),
+ (["--extract-status"], {
+ "help": (
+ "extract the status (obsolete etc.)",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_STATUS_,
+ }),
+ (["--extract-notice"], {
+ "help": (
+ "extract the notice or banner",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_NOTICE_,
+ }),
+ (["--extract-applicability"], {
+ "help": (
+ "extract the applicability",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_APPLICABILITY_,
+ }),
+ (["--extract-filelist"], {
+ "help": (
+ "extract the list of files",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_FILELIST_,
+ }),
+ (["--extract-dependencies"], {
+ "help": (
+ "extract the dependencies",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_DEPENDENCIES_,
+ }),
+ (["--extract-autoconf-snippet"], {
+ "help": (
+ "extract the snippet for configure.ac",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_AUTOCONF_SNIPPET_,
+ }),
+ (["--extract-automake-snippet"], {
+ "help": (
+ "extract the snippet for library makefile",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_AUTOMAKE_SNIPPET_,
+ }),
+ (["--extract-include-directive"], {
+ "help": (
+ "extract the #include directive",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_INCLUDE_DIRECTIVE_,
+ }),
+ (["--extract-link-directive"], {
+ "help": (
+ "extract the linker directive",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_LINK_DIRECTIVE_,
+ }),
+ (["--extract-license"], {
+ "help": (
+ "report the license terms of the source files",
+ "under lib/",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_LICENSE_,
+ }),
+ (["--extract-maintainer"], {
+ "help": (
+ "report the maintainer(s) inside gnulib",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_MAINTAINER_,
+ }),
+ (["--extract-tests-module"], {
+ "help": (
+ "report the unit test module, if it exists",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _EXTRACT_TESTS_MODULE_,
+ }),
+
+ (["--copy-file"], {
+ "help": (
+ "copy a file that is not part of any module",
+ ),
+ "dest": "mode",
+ "default": None,
+ "action": "store_const",
+ "const": _COPY_FILE_,
+ }),
+ ),
+ ),
+
+
+ (
+ "General options",
+ _ALL_,
+ (
+ (["--dir"], {
+ "help": (
+ "specify the target directory; on --import,",
+ "this specifies where your configure.ac",
+ "can be found",
+ ),
+ "dest": "root",
+ "default": ".",
+ "metavar": "DIRECTORY",
+ }),
+ (["--local-dir"], {
+ "help": (
+ "pecify a local override directory where to look",
+ "up files before looking in gnulib's directory",
+ ),
+ "dest": "local_dir",
+ "default": ".",
+ "nargs": 1,
+ "metavar": "DIRECTORY",
+ }),
+ (["-v", "--verbose"], {
+ "help": (
+ "increase verbosity; may be repeated",
+ ),
+ "dest": "verbosity",
+ "action": _VerboseAction_,
+ "default": 0,
+ "nargs": 0,
+ }),
+ (["-q", "--quiet"], {
+ "help": (
+ "decrease verbosity; may be repeated",
+ ),
+ "dest": "verbosity",
+ "action": _VerboseAction_,
+ "default": 0,
+ "nargs": 0,
+ }),
+ ),
+ ),
+
+
+ (
+ "Options for --import, --add/remove-import,\n"
+ " --create-[mega]testdir, --[mega]test",
+ (_ANY_IMPORT_ & ~_UPDATE_) | _ANY_TEST_,
+ (
+ (["--with-tests"], {
+ "help": (
+ "include unit tests for the included modules",
+ ),
+ "dest": "tests",
+ "action": "store_true",
+ "default": False,
+ }),
+ (["--with-obsolete"], {
+ "help": (
+ "include obsolete modules when they occur among the",
+ "dependencies; by default, dependencies to obsolete",
+ "modules are ignored",
+ ),
+ "dest": "obsolete",
+ "action": "store_true",
+ "default": False,
+ }),
+ (["--with-c++-tests"], {
+ "help": (
+ "include even unit tests for C++ interoperability",
+ ),
+ "dest": "cxx_tests",
+ "action": "store_true",
+ "default": False,
+ }),
+ (["--with-longrunning-tests"], {
+ "help": (
+ "include even unit tests that are long-runners",
+ ),
+ "dest": "longrunning_tests",
+ "action": "store_true",
+ "default": False,
+ }),
+ (["--with-privileged-tests"], {
+ "help": (
+ "include even unit tests that require root",
+ "privileges",
+ ),
+ "dest": "privileged_tests",
+ "action": "store_true",
+ "default": False,
+ }),
+ (["--with-unportable-tests"], {
+ "help": (
+ "include even unit tests that fail on some platforms",
+ ),
+ "dest": "unportable_tests",
+ "action": "store_true",
+ "default": False,
+ }),
+ (["--with-all-tests"], {
+ "help": (
+ "include all kinds of problematic unit tests",
+ ),
+ "dest": "all_tests",
+ "action": "store_true",
+ "default": False,
+ }),
+ (["--avoid"], {
+ "help": (
+ "avoid including the given MODULE; useful if you",
+ "have code that provides equivalent functionality",
+ "this option can be repeated",
+ ),
+ "action": _AvoidAction_,
+ "nargs": 1,
+ "default": [],
+ "metavar": "MODULE",
+ }),
+ (["--conditional-dependencies"], {
+ "help": (
+ "support conditional dependencies (may save configure",
+ "time and object code)",
+ ),
+ "action": "store_true",
+ "default": False,
+ "dest": "conddeps",
+ }),
+ (["--no-conditional-dependencies"], {
+ "help": (
+ "don't use conditional dependencies",
+ ),
+ "action": "store_false",
+ "default": False,
+ "dest": "conddeps",
+ }),
+ (["--libtool"], {
+ "help": (
+ "use libtool rules",
+ ),
+ "action": "store_true",
+ "default": False,
+ "dest": "libtool",
+ }),
+ (["--no-libtool"], {
+ "help": (
+ "don't use libtool rules",
+ ),
+ "action": "store_false",
+ "default": False,
+ "dest": "libtool",
+ }),
+ ),
+ ),
+
+
+ (
+ "Options for --import, --add/remove-import",
+ (_ANY_IMPORT_ & ~_UPDATE_),
+ (
+ (["--lib"], {
+ "help": (
+ "specify the library name; defaults to 'libgnu'",
+ ),
+ "default": "libgnu",
+ "metavar": "LIBRARY",
+ }),
+ (["--source-base"], {
+ "help": (
+ "directory relative to --dir where source code is",
+ "placed (default \"lib\")",
+ ),
+ "default": "lib",
+ "metavar": "DIRECTORY",
+ }),
+ (["--m4-base"], {
+ "help": (
+ "directory relative to --dir where *.m4 macros are",
+ "placed (default \"m4\")",
+ ),
+ "default": "m4",
+ "metavar": "DIRECTORY",
+ }),
+ (["--po-base"], {
+ "help": (
+ "directory relative to --dir where *.po files are",
+ "placed (default \"po\")",
+ ),
+ "default": "po",
+ "metavar": "DIRECTORY",
+ }),
+ (["--doc-base"], {
+ "help": (
+ "directory relative to --dir where doc files are",
+ "placed (default \"doc\")",
+ ),
+ "default": "doc",
+ "metavar": "DIRECTORY",
+ }),
+ (["--tests-base"], {
+ "help": (
+ "directory relative to --dir where unit tests are",
+ "placed (default \"tests\")",
+ ),
+ "default": "tests",
+ "metavar": "DIRECTORY",
+ }),
+ (["--aux-dir"], {
+ "help": (
+ "directory relative to --dir where auxiliary build",
+ "tools are placed (default comes from configure.ac);",
+ ),
+ "dest": "auxdir",
+ "default": "",
+ "metavar": "DIRECTORY",
+ }),
+ (["--lgpl"], {
+ "help": (
+ "abort if modules aren't available under the LGPL;",
+ "also modify license template from GPL to LGPL;",
+ "the version number of the LGPL can be specified;",
+ "the default is currently LGPLv3.",
+ ),
+ "choices": (2, 3),
+ "type": int,
+ "metavar": "[=2|=3]",
+ }),
+ (["--makefile-name"], {
+ "help": (
+ "name of makefile in automake syntax in the",
+ "source-base and tests-base directories",
+ "(default \"Makefile.am\")",
+ ),
+ "default": "Makefile.am",
+ "metavar": "NAME",
+ }),
+ (["--macro-prefix"], {
+ "help": (
+ "specify the prefix of the macros 'gl_EARLY' and",
+ "'gl_INIT'; default is 'gl'",
+ ),
+ "default": "gl",
+ "metavar": "PREFIX",
+ }),
+ (["--po-domain"], {
+ "help": (
+ "specify the prefix of the i18n domain; usually use",
+ "the package name; a suffix '-gnulib' is appended",
+ ),
+ "default": "",
+ "metavar": "NAME",
+ }),
+ (["--witness-c-macro"], {
+ "help": (
+ "specify the C macro that is defined when the",
+ "sources in this directory are compiled or used",
+ ),
+ "default": "",
+ "metavar": "NAME",
+ }),
+ (["--vc-files"], {
+ "help": (
+ "update version control related files",
+ "(.gitignore and/or .cvsignore)",
+ ),
+ "action": "store_true",
+ "default": False,
+ "dest": "vc_files",
+ }),
+ (["--no-vc-files"], {
+ "help": (
+ "don't update version control related files",
+ "(.gitignore and/or .cvsignore)",
+ ),
+ "action": "store_false",
+ "default": False,
+ "dest": "libtool",
+ }),
+ (["--no-changelog"], {
+ "help": (
+ "don't update or create ChangeLog files;",
+ "this option is currently deprecated",
+ ),
+ "default": None,
+ "action": "store_const",
+ "const": None,
+ }),
+ ),
+ ),
+ )
+
+
+ def _usage_(self):
+ iterable = iter(CommandLine._MODES_)
+ (_, cmd, args) = next(iterable)
+ fmt = (" --{cmd}" + (" {args}" if args else ""))
+ lines = ["usage: {program}" + fmt.format(cmd=cmd, args=args)]
+ for (_, cmd, args) in iterable:
+ fmt = (" --{cmd}" + (" {args}" if args else ""))
+ lines += [" {program}" + fmt.format(cmd=cmd, args=args)]
+ lines += ["", ""]
+ return "\n".join(lines).format(program=self._program_)
+
+
+ def _help_(self):
+ lines = [""]
+ for (name, _, args) in CommandLine._SECTIONS_:
+ offset = -1
+ lines += ["", "%s:" % name, ""]
+ for arg in args:
+ (options, kwargs) = arg
+ options = ", ".join(options)
+ if "metavar" in kwargs:
+ options += (" " + kwargs["metavar"])
+ length = len(options)
+ if length > offset:
+ offset = length
+ fmt1 = " %-{0}s %s".format(offset)
+ fmt2 = " " + " " * offset + " %s"
+ for arg in args:
+ (options, kwargs) = arg
+ options = ", ".join(options)
+ if "metavar" in kwargs:
+ sep = "" if "choices" in kwargs else "="
+ options += ("%s%s" % (sep, kwargs["metavar"]))
+ description = iter(kwargs["help"])
+ line = next(description)
+ lines += [fmt1 % (options, line)]
+ for line in description:
+ lines += [fmt2 % line]
+ lines += [""]
+ return self._usage_()[:-1] + "\n".join(lines)
+
+
+ def __init__(self, program, argv, **kwargs):
+ super().__init__(**kwargs)
+
+ exclude = {}
+ parser = argparse.ArgumentParser(prog=program, allow_abbrev=False)
+ modes = parser.add_mutually_exclusive_group()
+ for (options, kwargs) in CommandLine._SECTIONS_[0][2]:
+ for (flag, cmd, _) in CommandLine._MODES_:
+ if "--%s" % cmd not in options:
+ continue
+ exclude[flag] = [options, []]
+ modes.add_argument(*options, **kwargs)
+ for (_, flags, xargs) in CommandLine._SECTIONS_[1:]:
+ if not flags & flag:
+ for xarg in xargs:
+ (options, kwargs) = xarg
+ exclude[flag][1] += options
+ break
+
+ for (_, _, args) in CommandLine._SECTIONS_[1:]:
+ for arg in args:
+ (options, kwargs) = arg
+ parser.add_argument(*options, **kwargs)
+ parser.add_argument("modules", nargs="+", metavar="module1 ... moduleN")
+
+ self._program_ = os.path.basename(program)
+ parser.format_usage = self._usage_
+ parser.format_help = self._help_
+
+ namespace = vars(parser.parse_args(argv))
+ self._mode_ = namespace.pop("mode")
+ if self._mode_ is None:
+ parser.error("no operating mode selected")
+ self._verbosity_ = namespace.pop("verbosity")
+
+ (mode, conflict) = exclude[self._mode_]
+ conflict = set(conflict).intersection(argv)
+ if conflict:
+ conflict = list(conflict)[0]
+ fmt = "argument {0}: not allowed with argument {1}"
+ parser.error(fmt.format(conflict, "/".join(mode)))
+ _ = namespace.pop("no_changelog", None)
+ for (key, value) in namespace.items():
+ self[key] = value
+
+
+ @property
+ def mode(self):
+ for (flag, cmd, _) in CommandLine._MODES_:
+ if flag == self._mode_:
+ return cmd
+ return ""
--- /dev/null
+#!/usr/bin/python
+# encoding: UTF-8
+
+
+
+class AutoconfVersionError(Exception):
+ """minimum supported autoconf version mismatch"""
+ def __init__(self, version):
+ fmt = "minimum supported autoconf version is %f"
+ message = fmt % version
+ super().__init__(message)
+
+
+
+class M4BaseMismatchError(Exception):
+ """<gnulib-comp.m4> is expected to contain gl_M4_BASE([m4base])"""
+ def __init__(self, m4_base):
+ fmt = "<gnulib-comp.m4> is expected to contain gl_M4_BASE([%s])"
+ message = fmt % m4_base
+ super().__init__(message)
+
+
+
+class ConditionalDependenciesUnavailableError(Exception):
+ """conditional dependencies are not supported with tests"""
+ def __init__(self):
+ message = "conditional dependencies are not supported with tests"
+ super().__init__(message)
+
+
+
+class IncompatibleLicenseError(Exception):
+ """incompatible licenses on modules"""
+ def __init__(self, modules):
+ fmt = "incompatible licenses on modules: %r"
+ message = fmt % modules
+ super().__init__(message)
+
+
+
+class EmptyFileListError(Exception):
+ """cannot process empty file list"""
+ def __init__(self):
+ message = "cannot process empty file list"
+ super().__init__(message)
+
+
+
+class UnknownLicenseError(Exception):
+ """module lacks a license"""
+ def __init__(self, module):
+ fmt = "module lacks a license: %r"
+ message = fmt % module
+ super().__init__(message)
--- /dev/null
+#!/usr/bin/python
+# encoding: UTF-8
+
+
+
+import os
+
+from .config import Config
+from .module import Module
+from .module import FileModule
+
+
+
+class Directory:
+ """gnulib generic virtual file system"""
+ _SUBST_ = {
+ "build-aux" : "aux-dir",
+ "doc" : "doc-base",
+ "lib" : "source-base",
+ "m4" : "m4-base",
+ "tests" : "tests-base",
+ "tests=lib" : "tests-base",
+ "po" : "po-base",
+ }
+
+
+ def __init__(self, root, config):
+ if not isinstance(root, str):
+ raise TypeError("root must be of 'str' type")
+ if not isinstance(config, Config):
+ raise TypeError("config must be of 'Config' type")
+ if not os.path.exists(root):
+ raise FileNotFoundError(root)
+ if not os.path.isdir(root):
+ raise NotADirectoryError(root)
+ self._config_ = config
+ self._root_ = os.path.realpath(root)
+
+
+ def __getitem__(self, name):
+ """retrieve the canonical path of the specified file name"""
+ parts = []
+ replaced = False
+ if not isinstance(name, str):
+ raise TypeError("name must be of 'str' type")
+ path = os.path.normpath(name)
+ if os.path.isabs(path):
+ raise ValueError("name must be a relative path")
+ for part in path.split(os.path.sep):
+ if part == "..":
+ parts += [part]
+ continue
+ if not replaced:
+ for old, new in Directory._SUBST_.items():
+ if part == old:
+ part = self._config_[new]
+ replaced = True
+ parts += [part]
+ path = os.path.sep.join([self._root_] + parts)
+ if not os.path.exists(path):
+ raise FileNotFoundError(name)
+ return path
+
+
+
+class Git(Directory):
+ """gnulib Git-based virtual file system"""
+ _EXCLUDE_ = {
+ "." : str.startswith,
+ "~" : str.endswith,
+ "-tests" : str.endswith,
+ "CVS" : str.startswith,
+ "ChangeLog" : str.__eq__,
+ "COPYING" : str.__eq__,
+ "README" : str.__eq__,
+ "TEMPLATE" : str.__eq__,
+ "TEMPLATE-TESTS" : str.__eq__,
+ "TEMPLATE-EXTENDED" : str.__eq__,
+ }
+
+
+ def __init__(self, root, config):
+ if not os.path.isdir(root):
+ raise FileNotFoundError(root)
+ super().__init__(root, config)
+ if not os.path.isdir(os.path.join(self._root_, ".git")):
+ raise TypeError("%r is not a gnulib repository")
+
+
+ def module(self, name, full=True):
+ """instantiate gnulib module by its name"""
+ if name in Git._EXCLUDE_:
+ raise ValueError("illegal module name")
+ path = os.path.join(self["modules"], name)
+ return FileModule(path, name=name) if full else Module(name)
+
+
+ def modules(self, full=True):
+ """iterate over all available modules"""
+ prefix = self["modules"]
+ for root, _, files in os.walk(prefix):
+ names = []
+ for name in files:
+ exclude = False
+ for key, method in Git._EXCLUDE_.items():
+ if method(name, key):
+ exclude = True
+ break
+ if not exclude:
+ names += [name]
+ for name in names:
+ path = os.path.join(root, name)
+ name = path[len(prefix) + 1:]
+ yield self.module(name, full)
--- /dev/null
+#!/usr/bin/python
+# encoding: UTF-8
+
+
+
+import os
+
+from .config import Config
+from .module import Module
+
+
+
+class Generator:
+ """gnulib file content generator"""
+ _TEMPLATE_ = (
+ "## DO NOT EDIT! GENERATED AUTOMATICALLY!",
+ "#",
+ "# This file is free software; you can redistribute it and/or modify",
+ "# it under the terms of the GNU General Public License as published by",
+ "# the Free Software Foundation; either version 3 of the License, or",
+ "# (at your option) any later version.",
+ "#",
+ "# This file is distributed in the hope that it will be useful,",
+ "# but WITHOUT ANY WARRANTY; without even the implied warranty of",
+ "# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
+ "# GNU General Public License for more details.",
+ "#",
+ "# You should have received a copy of the GNU General Public License",
+ "# along with this file. If not, see <http://www.gnu.org/licenses/>.",
+ "#",
+ "# As a special exception to the GNU General Public License,",
+ "# this file may be distributed as part of a program that",
+ "# contains a configuration script generated by Autoconf, under",
+ "# the same distribution terms as the rest of that program.",
+ "#",
+ "# Generated by gnulib-tool.",
+ )
+
+ def __repr__(self):
+ return "pygnulib.generator.Generator"
+
+ def __str__(self):
+ return "\n".join([_ for _ in self])
+
+ def __iter__(self):
+ for line in Generator._TEMPLATE_:
+ yield line
+
+
+
+class POMakefile(Generator):
+ """PO Makefile parameterization"""
+ _TEMPLATE_ = (
+ "# These options get passed to xgettext.",
+ "XGETTEXT_OPTIONS = \\",
+ " --keyword=_ --flag=_:1:pass-c-format \\",
+ " --keyword=N_ --flag=N_:1:pass-c-format \\",
+ " --keyword='proper_name:1,\"This is a proper name." # comma omitted
+ " See the gettext manual, section Names.\"' \\",
+ " --keyword='proper_name_utf8:1,\"This is a proper name." # comma omitted
+ " See the gettext manual, section Names.\"' \\",
+ " --flag=error:3:c-format --flag=error_at_line:5:c-format",
+ "",
+ "# This is the copyright holder that gets inserted into the header of the",
+ "# $(DOMAIN).pot file. gnulib is copyrighted by the FSF.",
+ "COPYRIGHT_HOLDER = Free Software Foundation, Inc.",
+ "",
+ "# This is the email address or URL to which the translators shall report",
+ "# bugs in the untranslated strings:",
+ "# - Strings which are not entire sentences, see the maintainer guidelines",
+ "# in the GNU gettext documentation, section 'Preparing Strings'.",
+ "# - Strings which use unclear terms or require additional context to be",
+ "# understood.",
+ "# - Strings which make invalid assumptions about notation of date, time or",
+ "# money.",
+ "# - Pluralisation problems.",
+ "# - Incorrect English spelling.",
+ "# - Incorrect formatting.",
+ "# It can be your email address, or a mailing list address where translators",
+ "# can write to without being subscribed, or the URL of a web page through",
+ "# which the translators can contact you.",
+ "MSGID_BUGS_ADDRESS = bug-gnulib@gnu.org",
+ "",
+ "# This is the list of locale categories, beyond LC_MESSAGES, for which the",
+ "# message catalogs shall be used. It is usually empty.",
+ "EXTRA_LOCALE_CATEGORIES =",
+ "",
+ "# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'",
+ "# context. Possible values are \"yes\" and \"no\". Set this to yes if the",
+ "# package uses functions taking also a message context, like pgettext(), or",
+ "# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.",
+ "USE_MSGCTXT = no"
+ )
+ def __init__(self, config):
+ if not isinstance(config, Config):
+ raise TypeError("config must be of pygnulib.Config type")
+ self._config_ = config
+ super().__init__()
+
+
+ @property
+ def po_base(self):
+ """directory relative to ROOT where *.po files are placed; defaults to 'po'"""
+ return self._config_.po_base
+
+
+ @property
+ def po_domain(self):
+ """the prefix of the i18n domain"""
+ return self._config_.po_domain
+
+
+ def __repr__(self):
+ fmt = "pygnulib.generator.POMakefile(po_base=%r, po_domain=%r)"
+ return fmt % (self.po_base, self.po_domain)
+
+
+ def __iter__(self):
+ for line in super().__iter__():
+ yield line
+ yield "# Usually the message domain is the same as the package name."
+ yield "# But here it has a '-gnulib' suffix."
+ yield "DOMAIN = %s-gnulib" % self.po_domain
+ yield ""
+ yield "# These two variables depend on the location of this directory."
+ yield "subdir = %s" % self.po_domain
+ yield "top_subdir = %s" % "/".join([".." for _ in self.po_base.split(os.path.sep)])
+ for line in POMakefile._TEMPLATE_:
+ yield line
+
+
+
+class POTFILES(Generator):
+ """file list to be passed to xgettext"""
+ def __init__(self, config, files):
+ if not isinstance(config, Config):
+ raise TypeError("config must be of pygnulib.Config type")
+ super().__init__()
+ self._config_ = config
+ self._files_ = tuple(files)
+
+
+ @property
+ def files(self):
+ """list of files"""
+ return tuple(self.files)
+
+
+ def __repr__(self):
+ fmt = "pygnulib.generator.POTFILES(files=%r)"
+ return fmt % self.files
+
+
+ def __iter__(self):
+ for line in super().__iter__():
+ yield line
+ yield "# List of files which contain translatable strings."
+ for file in [_ for _ in self.files if _.startswith("lib/")]:
+ yield os.path.join(self._config_.source_base, file[4:])
+
+
+
+class AutoconfSnippet(Generator):
+ """autoconf snippet generator for standalone module"""
+ def __init__(self, config, module, toplevel, no_libtool, no_gettext):
+ """
+ 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
+ """
+ if not isinstance(config, Config):
+ raise TypeError("config must be of pygnulib.config.Config type")
+ if not isinstance(module, Module):
+ raise TypeError("module must be of pygnulib.module.Module type")
+ if not isinstance(toplevel, bool):
+ raise TypeError("toplevel must be of bool type")
+ if not isinstance(no_libtool, bool):
+ raise TypeError("no_libtool must be of bool type")
+ if not isinstance(no_gettext, bool):
+ raise TypeError("no_gettext must be of bool type")
+ super().__init__()
+ self._config_ = config
+ self._module_ = module
+ self._toplevel_ = toplevel
+ self._no_libtool_ = no_libtool
+ self._no_gettext_ = no_gettext
+
+
+ @property
+ def toplevel(self):
+ """top level indicator; subordinate use of pygnulib"""
+ return self._toplevel_
+
+
+ @property
+ def libtool(self):
+ """libtool switch, disabling libtool configuration parameter"""
+ return self._config_.libtool and not self._no_libtool_
+
+
+ @property
+ def gettext(self):
+ """gettext switch, disabling AM_GNU_GETTEXT invocations"""
+ return not self._no_gettext_
+
+
+ def __repr__(self):
+ flags = []
+ if self.toplevel:
+ flags += ["toplevel"]
+ if self.libtool:
+ flags += ["libtool"]
+ if self.gettext:
+ flags += ["gettext"]
+ fmt = "pygnulib.generator.AutoconfSnippet(include_guard_prefix=%r, flags=%s)"
+ include_guard_prefix = self._config_.include_guard_prefix
+ return fmt % (include_guard_prefix, "|".join(flags))
+
+
+ def __iter__(self):
+ module = self._module_
+ if module.name not in ("gnumakefile", "maintainer-makefile") or self.toplevel:
+ snippet = module.configure_ac_snippet
+ include_guard_prefix = self._config_.include_guard_prefix
+ snippet.replace(r"${gl_include_guard_prefix}", include_guard_prefix)
+ if not self.libtool:
+ table = (
+ (r"$gl_cond_libtool", "false"),
+ ("gl_libdeps", "gltests_libdeps"),
+ ("gl_ltlibdeps", "gltests_ltlibdeps"),
+ )
+ for (src, dst) in table:
+ snippet = snippet.replace(src, dst)
+ if not self.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)
+ lines = (_ for _ in snippet.split("\n") if _)
+ for line in lines:
+ yield line
+ if module.name == "alloca" and self.libtool:
+ yield "changequote(,)dnl"
+ yield "LTALLOCA=`echo \"$ALLOCA\" | sed -e 's/\\.[^.]* /.lo /g;s/\\.[^.]*$/.lo/'`"
+ yield "changequote([, ])dnl"
+ yield "AC_SUBST([LTALLOCA])"
+
+
+
+class InitMacro(Generator):
+ """basic gl_INIT macro generator"""
+ def __init__(self, config, macro_prefix=None):
+ """
+ config: gnulib configuration
+ macro_prefix: macro prefix; if None, consider configuration
+ """
+ if not isinstance(config, Config):
+ raise TypeError("config must be of pygnulib.config.Config type")
+ if macro_prefix is None:
+ macro_prefix = config.macro_prefix
+ if not isinstance(macro_prefix, str):
+ raise TypeError("macro_prefix must be of str type")
+ self._macro_prefix_ = macro_prefix
+
+
+ @property
+ def macro_prefix(self):
+ """the prefix of the macros 'gl_EARLY' and 'gl_INIT'"""
+ return self._macro_prefix_
+
+
+ def __repr__(self):
+ fmt = "pygnulib.generator.InitMacro(macro_prefix=%r)"
+ return fmt % self.macro_prefix
+
+
+
+class InitMacroHeader(InitMacro):
+ """the first few statements of the gl_INIT macro"""
+ def __init__(self, config, macro_prefix=None):
+ """
+ config: gnulib configuration
+ macro_prefix: macro prefix; if None, consider configuration
+ """
+ super().__init__(config=config, macro_prefix=macro_prefix)
+
+
+ def __repr__(self):
+ fmt = "pygnulib.generator.InitMacroHeader(macro_prefix=%r)"
+ return fmt % self.macro_prefix
+
+
+ def __iter__(self):
+ # Overriding AC_LIBOBJ and AC_REPLACE_FUNCS has the effect of storing
+ # platform-dependent object files in ${macro_prefix_arg}_LIBOBJS instead
+ # of LIBOBJS. The purpose is to allow several gnulib instantiations under
+ # a single configure.ac file. (AC_CONFIG_LIBOBJ_DIR does not allow this
+ # flexibility).
+ # Furthermore it avoids an automake error like this when a Makefile.am
+ # that uses pieces of gnulib also uses $(LIBOBJ):
+ # automatically discovered file `error.c' should not be explicitly
+ # mentioned.
+ yield " m4_pushdef([AC_LIBOBJ], m4_defn([%s_LIBOBJ]))" % self.macro_prefix
+ yield " m4_pushdef([AC_REPLACE_FUNCS], m4_defn([%s_REPLACE_FUNCS]))" % self.macro_prefix
+
+ # Overriding AC_LIBSOURCES has the same purpose of avoiding the automake
+ # error when a Makefile.am that uses pieces of gnulib also uses $(LIBOBJ):
+ # automatically discovered file `error.c' should not be explicitly
+ # mentioned
+ # We let automake know about the files to be distributed through the
+ # EXTRA_lib_SOURCES variable.
+ yield " m4_pushdef([AC_LIBSOURCES], m4_defn([%s_LIBSOURCES]))" % self.macro_prefix
+
+ # Create data variables for checking the presence of files that are
+ # mentioned as AC_LIBSOURCES arguments. These are m4 variables, not shell
+ # variables, because we want the check to happen when the configure file is
+ # created, not when it is run. ${macro_prefix_arg}_LIBSOURCES_LIST is the
+ # list of files to check for. ${macro_prefix_arg}_LIBSOURCES_DIR is the
+ # subdirectory in which to expect them.
+ yield " m4_pushdef([%s_LIBSOURCES_LIST], [])" % self.macro_prefix
+ yield " m4_pushdef([%s_LIBSOURCES_DIR], [])" % self.macro_prefix
+ yield " gl_COMMON"
+
+
+
+class InitMacroFooter(InitMacro):
+ """the last few statements of the gl_INIT macro"""
+ _TEMPLATE_ = (
+ " m4_ifval({macro_prefix}_LIBSOURCES_LIST, [",
+ " m4_syscmd([test ! -d ]m4_defn([{macro_prefix}_LIBSOURCES_DIR])[ ||",
+ " for gl_file in ]{macro_prefix}_LIBSOURCES_LIST[ ; do",
+ " if test ! -r ]m4_defn([{macro_prefix}_LIBSOURCES_DIR])[/$gl_file ; then",
+ " echo \"missing file ]m4_defn([{macro_prefix}_LIBSOURCES_DIR])[/$gl_file\" >&2",
+ " exit 1",
+ " fi",
+ " done])dnl",
+ " m4_if(m4_sysval, [0], [],",
+ " [AC_FATAL([expected source file, required through AC_LIBSOURCES, not found])])",
+ " ])",
+ " m4_popdef([{macro_prefix}_LIBSOURCES_DIR])",
+ " m4_popdef([{macro_prefix}_LIBSOURCES_LIST])",
+ " m4_popdef([AC_LIBSOURCES])",
+ " m4_popdef([AC_REPLACE_FUNCS])",
+ " m4_popdef([AC_LIBOBJ])",
+ " AC_CONFIG_COMMANDS_PRE([",
+ " {macro_prefix}_libobjs=",
+ " {macro_prefix}_ltlibobjs=",
+ " if test -n \"${macro_prefix}_LIBOBJS\"; then",
+ " # Remove the extension.",
+ " sed_drop_objext='s/\\.o$//;s/\\.obj$//'",
+ " for i in `for i in ${macro_prefix}_LIBOBJS; " # comma omitted
+ "do echo \"$i\"; done | sed -e \"$sed_drop_objext\" | sort | uniq`; do",
+ " {macro_prefix}_libobjs=\"${macro_prefix}_libobjs $i.$ac_objext\"",
+ " {macro_prefix}_ltlibobjs=\"${macro_prefix}_ltlibobjs $i.lo\"",
+ " done",
+ " fi",
+ " AC_SUBST([{macro_prefix}_LIBOBJS], [${macro_prefix}_libobjs])",
+ " AC_SUBST([{macro_prefix}_LTLIBOBJS], [${macro_prefix}_ltlibobjs])",
+ " ])",
+ )
+
+
+ def __init__(self, config, macro_prefix=None):
+ """
+ config: gnulib configuration
+ macro_prefix: macro prefix; if None, consider configuration
+ """
+ super().__init__(config=config, macro_prefix=macro_prefix)
+
+
+ def __repr__(self):
+ fmt = "pygnulib.generator.InitMacroFooter(macro_prefix=%r)"
+ return fmt % self.macro_prefix
+
+
+ def __iter__(self):
+ # Check the presence of files that are mentioned as AC_LIBSOURCES
+ # arguments. The check is performed only when autoconf is run from the
+ # directory where the configure.ac resides; if it is run from a different
+ # directory, the check is skipped.
+ for line in InitMacroFooter._TEMPLATE_:
+ yield line.format(macro_prefix=self.macro_prefix)
+
+
+
+class InitMacroDone(InitMacro):
+ """few statements AFTER the gl_INIT macro"""
+ _TEMPLATE_ = (
+ "",
+ "# Like AC_LIBOBJ, except that the module name goes",
+ "# into {macro_prefix}_LIBOBJS instead of into LIBOBJS.",
+ "AC_DEFUN([{macro_prefix}_LIBOBJ], [",
+ " AS_LITERAL_IF([$1], [{macro_prefix}_LIBSOURCES([$1.c])])dnl",
+ " {macro_prefix}_LIBOBJS=\"${macro_prefix}_LIBOBJS $1.$ac_objext\"",
+ "])",
+ "",
+ "# Like AC_REPLACE_FUNCS, except that the module name goes",
+ "# into {macro_prefix}_LIBOBJS instead of into LIBOBJS.",
+ "AC_DEFUN([{macro_prefix}_REPLACE_FUNCS], [",
+ " m4_foreach_w([gl_NAME], [$1], [AC_LIBSOURCES(gl_NAME[.c])])dnl",
+ " AC_CHECK_FUNCS([$1], , [{macro_prefix}_LIBOBJ($ac_func)])",
+ "])",
+ "",
+ "# Like AC_LIBSOURCES, except the directory where the source file is",
+ "# expected is derived from the gnulib-tool parameterization,",
+ "# and alloca is special cased (for the alloca-opt module).",
+ "# We could also entirely rely on EXTRA_lib..._SOURCES.",
+ "AC_DEFUN([{macro_prefix}_LIBSOURCES], [",
+ " m4_foreach([_gl_NAME], [$1], [",
+ " m4_if(_gl_NAME, [alloca.c], [], [",
+ " m4_define([{macro_prefix}_LIBSOURCES_DIR], [{source_base}])",
+ " m4_append([{macro_prefix}_LIBSOURCES_LIST], _gl_NAME, [ ])",
+ " ])",
+ " ])",
+ "])",
+ )
+
+
+ def __init__(self, config, source_base=None, macro_prefix=None):
+ if source_base is None:
+ source_base = config.source_base
+ if not isinstance(source_base, str):
+ raise TypeError("source_base must be of str type")
+ super().__init__(config=config, macro_prefix=macro_prefix)
+ self._source_base_ = source_base
+
+
+ @property
+ def source_base(self):
+ """directory relative to ROOT where source code is placed; defaults to 'lib'"""
+ return self._source_base_
+
+
+ def __repr__(self):
+ fmt = "pygnulib.generator.InitMacroDone(source_base=%r, macro_prefix=%r)"
+ return fmt % (self.source_base, self.macro_prefix)
+
+
+ def __iter__(self):
+ for line in InitMacroDone._TEMPLATE_:
+ yield line.format(source_base=self._source_base_, macro_prefix=self._macro_prefix_)
--- /dev/null
+#!/usr/bin/python
+# encoding: UTF-8
+
+
+
+import codecs
+import collections
+import hashlib
+import os
+import re
+
+
+
+class Module:
+ """gnulib generic module"""
+ _TABLE_ = {
+ "description" : (0x00, str, "Description"),
+ "comment" : (0x01, str, "Comment"),
+ "status" : (0x02, str, "Status"),
+ "notice" : (0x03, str, "Notice"),
+ "applicability" : (0x04, str, "Applicability"),
+ "files" : (0x05, list, "Files"),
+ "dependencies" : (0x06, list, "Depends-on"),
+ "early_configure_ac_snippet" : (0x07, str, "configure.ac-early"),
+ "configure_ac_snippet" : (0x08, str, "configure.ac"),
+ "makefile_am_snippet" : (0x09, str, "Makefile.am"),
+ "include" : (0x0A, list, "Include"),
+ "link" : (0x0B, list, "Link"),
+ "license" : (0x0C, str, "License"),
+ "maintainers" : (0x0D, list, "Maintainer"),
+ }
+ _PATTERN_DEPENDENCIES_ = re.compile("^(\\S+)(?:\\s+(.+))*$")
+ _PATTERN_INCLUDE_ = re.compile("^[\\<\"]([A-Za-z0-9/\\-_]+\\.h)[\\>\"](?:\\s+.*^)*$")
+
+
+ def __init__(self, name, **kwargs):
+ if not isinstance(name, str):
+ raise TypeError("name must be of 'str' type")
+ self._name_ = name
+ self._table_ = {"maintainers": ["all"]}
+ for key in Module._TABLE_:
+ self._table_[key] = ""
+ for key, value in kwargs.items():
+ self._table_[key] = value
+
+
+ @property
+ def name(self):
+ """name"""
+ return self._name_
+
+ @name.setter
+ def name(self, value):
+ if not isinstance(value, str):
+ raise TypeError("'str' type is expected")
+ self._name_ = value
+
+
+ @property
+ def description(self):
+ """description"""
+ return self._table_["description"]
+
+ @description.setter
+ def description(self, value):
+ if not isinstance(value, str):
+ raise TypeError("'str' type is expected")
+ self._table_["description"] = value
+
+
+ @property
+ def comment(self):
+ """comment"""
+ return self._table_["comment"]
+
+ @comment.setter
+ def comment(self, value):
+ if not isinstance(value, str):
+ raise TypeError("'str' type is expected")
+ self._table_["comment"] = value
+
+
+ @property
+ def status(self):
+ """status"""
+ return self._table_["status"]
+
+ @status.setter
+ def status(self, value):
+ if not isinstance(value, str):
+ raise TypeError("'str' type is expected")
+ self._table_["status"] = value
+
+
+ @property
+ def notice(self):
+ """notice"""
+ return self._table_["notice"]
+
+ @notice.setter
+ def notice(self, value):
+ if not isinstance(value, str):
+ raise TypeError("'str' type is expected")
+ self._table_["notice"] = value
+
+
+ @property
+ def applicability(self):
+ """applicability (usually "main" or "tests")"""
+ default = "main" if self.name.endswith("-tests") else "tests"
+ current = self._table_["applicability"]
+ return current if current.strip() else default
+
+ @applicability.setter
+ def applicability(self, value):
+ if not isinstance(value, str):
+ raise TypeError("'str' type is expected")
+ self._table_["applicability"] = value
+
+
+ @property
+ def files(self):
+ """file dependencies iterator (set of strings)"""
+ for file in self._table_["files"]:
+ yield file
+
+ @files.setter
+ def files(self, iterable):
+ if isinstance(iterable, (bytes, str)) \
+ or not isinstance(iterable, collections.Iterable):
+ raise TypeError("iterable of strings is expected")
+ result = set()
+ for item in iterable:
+ if not isinstance(item, str):
+ raise TypeError("iterable of strings is expected")
+ result.update([item])
+ self._table_["files"] = result
+
+
+ @property
+ def dependencies(self):
+ """dependencies iterator (name, condition)"""
+ for entry in self._table_["dependencies"]:
+ yield Module._PATTERN_DEPENDENCIES_.findall(entry)[0]
+
+ @dependencies.setter
+ def dependencies(self, iterable):
+ error = TypeError("iterable of pairs (name, condition) is expected")
+ if isinstance(iterable, (bytes, str)) \
+ or not isinstance(iterable, collections.Iterable):
+ raise error
+ result = set()
+ try:
+ for pair in iterable:
+ (name, condition) = pair
+ if not isinstance(name, str) \
+ or not isinstance(condition, str):
+ raise error
+ result.update([(name, condition)])
+ except ValueError:
+ raise error
+ self._table_["dependencies"] = result
+
+
+ @property
+ def early_configure_ac_snippet(self):
+ """early configure.ac snippet"""
+ return self._table_["early_configure_ac_snippet"]
+
+ @early_configure_ac_snippet.setter
+ def early_configure_ac_snippet(self, value):
+ if not isinstance(value, str):
+ raise TypeError("'str' type is expected")
+ self._table_["early_configure_ac_snippet"] = value
+
+
+ @property
+ def configure_ac_snippet(self):
+ """configure.ac snippet"""
+ return self._table_["configure_ac_snippet"]
+
+ @configure_ac_snippet.setter
+ def configure_ac_snippet(self, value):
+ if not isinstance(value, str):
+ raise TypeError("'str' type is expected")
+ self._table_["configure_ac_snippet"] = value
+
+
+ @property
+ def makefile_am_snippet(self):
+ """Makefile.am snippet"""
+ return self._table_["makefile_am_snippet"]
+
+ @makefile_am_snippet.setter
+ def makefile_am_snippet(self, value):
+ if not isinstance(value, str):
+ raise TypeError("'str' type is expected")
+ self._table_["makefile_am_snippet"] = value
+
+
+ @property
+ def include(self):
+ """include files iterator (header, comment)"""
+ for entry in self._table_["include"]:
+ match = Module._PATTERN_INCLUDE_.findall(entry)
+ yield match[0] if match else entry
+
+ @include.setter
+ def include(self, iterable):
+ error = TypeError("iterable of pairs (header, comment) is expected")
+ if isinstance(iterable, (bytes, str)) \
+ or not isinstance(iterable, collections.Iterable):
+ raise error
+ result = set()
+ try:
+ for pair in iterable:
+ (header, comment) = pair
+ if not isinstance(header, str) \
+ or not isinstance(comment, str):
+ raise error
+ result.update([(header, comment)])
+ except ValueError:
+ raise error
+ self._table_["include"] = result
+
+
+ @property
+ def link(self):
+ """linkage iterator (string)"""
+ for entry in self._table_["link"]:
+ yield entry
+
+ @link.setter
+ def link(self, iterable):
+ if isinstance(iterable, (bytes, str)) \
+ or not isinstance(iterable, collections.Iterable):
+ raise TypeError("iterable of strings is expected")
+ result = set()
+ for item in iterable:
+ if not isinstance(item, str):
+ raise TypeError("iterable of strings is expected")
+ result.update([item])
+ self._table_["link"] = result
+
+
+ @property
+ def license(self):
+ """license"""
+ return self._table_["license"]
+
+ @license.setter
+ def license(self, value):
+ if not isinstance(value, str):
+ raise TypeError("'str' type is expected")
+ self._table_["license"] = value
+
+
+ @property
+ def maintainers(self):
+ """maintainers iterator (maintainer)"""
+ for entry in self._table_["maintainers"]:
+ yield entry
+
+ @maintainers.setter
+ def maintainers(self, iterable):
+ if isinstance(iterable, (bytes, str)) \
+ or not isinstance(iterable, collections.Iterable):
+ raise TypeError("iterable of strings is expected")
+ result = set()
+ for item in iterable:
+ if not isinstance(item, str):
+ raise TypeError("iterable of strings is expected")
+ result.update([item])
+ self._table_["maintainers"] = result
+
+
+ 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
+ if len(module) != len(module.encode()):
+ module = (module + "\n").encode("UTF-8")
+ module = hashlib.md5(module).hexdigest()
+ return "%s_gnulib_enabled_%s" % (macro_prefix, module)
+
+
+ def shell_function(self, macro_prefix="gl"):
+ """Get the name of the shell function containing the m4 macros."""
+ module = self.name
+ if len(module) != len(module.encode()):
+ module = (module + "\n").encode("UTF-8")
+ module = hashlib.md5(module).hexdigest()
+ return "func_%s_gnulib_m4code_%s" % (macro_prefix, module)
+
+
+ def conditional_name(self, macro_prefix="gl"):
+ """Get the automake conditional name."""
+ module = self.name
+ if len(module) != len(module.encode()):
+ module = (module + "\n").encode("UTF-8")
+ module = hashlib.md5(module).hexdigest()
+ return "%s_GNULIB_ENABLED_%s" % (macro_prefix, module)
+
+
+ def __hash__(self):
+ return hash(str(self))
+
+
+ def __repr__(self):
+ return self._name_
+
+
+ def __str__(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._table_[key])
+ else:
+ value = self._table_[key]
+ if value:
+ result += field
+ result += value
+ result += "\n\n" if value else "\n"
+ return result.strip() + "\n"
+
+
+ def __lt__(self, value):
+ return self.name < value.name
+
+ def __le__(self, value):
+ return self.__lt__(value) or self.__eq__(value)
+
+ def __eq__(self, value):
+ return self.name == value.name
+
+ def __ne__(self, value):
+ return not self.__eq__(value)
+
+ def __ge__(self, value):
+ return value.__le__(self)
+
+ def __gt__(self, value):
+ return value.__lt__(self)
+
+
+
+class FileModule(Module):
+ """gnulib module text file"""
+ _TABLE_ = {
+ "Description" : (str, "description"),
+ "Comment" : (str, "comment"),
+ "Status" : (str, "status"),
+ "Notice" : (str, "notice"),
+ "Applicability" : (str, "applicability"),
+ "Files" : (list, "files"),
+ "Depends-on" : (list, "dependencies"),
+ "configure.ac-early" : (str, "early_configure_ac_snippet"),
+ "configure.ac" : (str, "configure_ac_snippet"),
+ "Makefile.am" : (str, "makefile_am_snippet"),
+ "Include" : (list, "include"),
+ "Link" : (list, "link"),
+ "License" : (str, "license"),
+ "Maintainer" : (list, "maintainers"),
+ }
+ _FIELDS_ = [field for (_, _, field) in Module._TABLE_.values()]
+ _PATTERN_ = re.compile("(%s):" % "|".join(_FIELDS_))
+
+ def __init__(self, path, mode="r", name=None, **kwargs):
+ if name is None:
+ name = os.path.basename(path)
+ if mode not in ("r", "w", "rw"):
+ raise ValueError("illegal mode: %r" % mode)
+ if mode == "r":
+ super().__init__(name)
+ with codecs.open(path, "rb", "UTF-8") as stream:
+ data = ""
+ for line in stream:
+ line = line.strip("\n")
+ if line.startswith("#") \
+ or (line.startswith("/*") and line.endswith("*/")):
+ continue
+ data += (line + "\n")
+ match = FileModule._PATTERN_.split(data)[1:]
+ for (group, value) in zip(match[::2], match[1::2]):
+ (typeid, key) = FileModule._TABLE_[group]
+ if typeid is list:
+ self._table_[key] = [_ for _ in "".join(value).split("\n") if _.strip()]
+ else:
+ self._table_[key] = value.strip()
+ self._stream_ = None
+ elif mode == "w":
+ super().__init__(name)
+ self._stream_ = codecs.open(path, "w+", "UTF-8")
+ elif mode == "rw":
+ self.__init__(path, "r")
+ self._stream_ = codecs.open(path, "w+", "UTF-8")
+ else:
+ raise ValueError("invalid mode: %r" % mode)
+ for key, value in kwargs.items():
+ self._table_[key] = value
+
+
+ def close(self):
+ """Close the underlying stream and write data into the file."""
+ if self._stream_:
+ self._stream_.truncate(0)
+ self._stream_.write(str(self))
+ self._stream_.close()
+
+
+ def __enter__(self):
+ return self
+
+
+ def __exit__(self, exctype, excval, exctrace):
+ self.close()