view pygnulib/generator.py @ 38933:e896a3753833

generator: do not export private class members into inherited classes
author Dmitry Selyutin <ghostmansd@gmail.com>
date Sat, 09 Sep 2017 19:42:55 +0300
parents 8ee46377bd5f
children 19139a91b9e1
line wrap: on
line source

#!/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")
        super().__init__()
        self.__config = config


    @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)