Blob Blame History Raw
#!/usr/bin/python3
# -*- coding: ISO-8859-1 -*-
#
# dblatex python setup script - See the COPYRIGHT
#
from __future__ import print_function

import os
import sys
import re
import glob
import subprocess
from io import open

try:
    from setuptools import setup
    from setuptools.command.install import install
except ImportError:
    from distutils.core import setup
    from distutils.command.install import install

from distutils.command.build import build
from distutils.command.build_scripts import build_scripts
from distutils.command.install_data import install_data
from distutils.command.sdist import sdist
from distutils import log
from subprocess import Popen, PIPE
sys.path.append("lib")
#from contrib.debian.installer import DebianInstaller

#
# Build the command line script
#
class BuildScripts(build_scripts):

    SHELL_SCRIPT = """#!/usr/bin/python3
import sys
import os

package_base = %(package_base)s

%(lib_path)s
%(catalogs)s
%(style_set)s
from %(package_path)s import %(package)s
%(package)s.main(base=package_base)
"""

    CATALOGS = """cat = os.environ.get("SGML_CATALOG_FILES")
if cat:
    cat += ":%s"
else:
    cat = "%s"
os.environ["SGML_CATALOG_FILES"] = cat
"""

    def run(self):
        """
        Create the proper script for the current platform.
        """
        if not self.scripts:
            return

        # The script can only work with package data
        self.data_files = self.distribution.data_files
        if not(self.data_files):
            return

        if self.dry_run:
            return

        # Ensure the destination directory exists
        self.mkpath(self.build_dir)

        # Data useful for building the script
        install = self.distribution.get_command_obj("install")
        if not(install.install_data):
            return

        self._install_lib = os.path.normpath(install.install_lib)
        self._root = install.root
        if self._root:
            self._root = os.path.normpath(self._root)
        self._package_base = os.path.join(install.install_data,
                                          self.data_files[0][0])
        self._catalogs = install.catalogs
        self._style = install.style
        self._use_py_path = install.use_python_path
        print(self._package_base)

        # Build the command line script
        self.build_script()

    def _strip_root(self, *paths):
        if not(self._root):
            return paths

        newpaths = []
        for path in paths:
            if path.startswith(self._root):
                newpaths.append(path[len(self._root):])
            else:
                newpaths.append(path)
        return newpaths

    def build_script(self):
        script_name = self.scripts[0]

        # prepare args for the bang path at the top of the script
        ENV_BIN = '/usr/bin/env'
        env_args = ''
        if self._use_py_path:
            env_exec = ''
            py_exec = sys.executable
        elif os.name == 'posix':
            # Some Solaris platforms may not have an 'env' binary.
            # If /usr/bin/env exists, use '#!/usr/bin/env python'
            # otherwise, use '#!' + sys.executable
            env_exec = os.path.isfile(ENV_BIN) and \
                os.access(ENV_BIN, os.X_OK) and ENV_BIN or ''
            py_exec = env_exec and 'python' or sys.executable
        else:
            # shouldn't matter on non-POSIX; we'll just use defaults
            env_exec = ENV_BIN
            py_exec = 'python'

        # Retrieve actual installation paths
        lib_path, package_base = self._strip_root(self._install_lib,
                                                  self._package_base)

        # Just help for non standard installation paths
        if lib_path in sys.path:
            lib_path = ""
        else:
            lib_path = "sys.path.append(r\"%s\")" % lib_path

        # Things to adapt when building an egg
        if "/egg" in lib_path:
            lib_path = ""
            package_base = 'os.path.abspath(os.path.join(os.path.dirname('\
                           '__file__), "..", "..", "share", "dblatex"))'
        else:
            package_base = 'r"%s"' % (package_base)

        if self._catalogs:
            catalogs = self.CATALOGS % (self._catalogs, self._catalogs)
        else:
            catalogs = ""

        if self._style:
            style_set = "sys.argv.insert(1, '-T%s')" % self._style
        else:
            style_set = ""

        script_args = { 'env_executable': env_exec,
                        'env_args': env_exec and (' %s' % env_args) or '',
                        'py_executable': py_exec,
                        'lib_path': lib_path,
                        'style_set': style_set,
                        'package': "dblatex",
                        'package_path': "dbtexmf.dblatex",
                        'catalogs': catalogs,
                        'package_base': package_base }

        script = self.SHELL_SCRIPT % script_args
        script_name = os.path.basename(script_name)
        outfile = os.path.join(self.build_dir, script_name)
        fd = os.open(outfile, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0o755)
        os.write(fd, script.encode('ascii'))
        os.close(fd)


class Build(build):
    """
    Build the documentation if missing or required to rebuild
    """
    user_options = build.user_options + \
                 [('docbook-xsl=', None,
                   'DocBook Project Stylesheet base directory (build_doc)')]

    def initialize_options(self):
        build.initialize_options(self)
        self.docbook_xsl = None

    def run(self):
        # Do the default tasks
        build.run(self)
        # And build the doc
        self.build_doc()

    def build_doc(self):
        log.info("running build_doc")
        htmldir = os.path.join("docs", "xhtml")
        pdfdocs = glob.glob(os.path.join("docs", "[mr]*.pdf"))
        manpage = os.path.join("docs", "manpage", "dblatex.1.gz")

        # Lazy check to avoid a rebuild for nothing
        if (not(self.force) and os.path.exists(htmldir) and len(pdfdocs) >= 2
            and os.path.exists(manpage)):
            return

        # Assumes that make is the GNU make
        cmd = ["make", "-C", "docs", "VERSION=%s" % (get_version())]
        if self.docbook_xsl:
            cmd.append("XSLDBK=%s" % os.path.abspath(self.docbook_xsl))

        subprocess.call(cmd)


def find_programs(utils):
    import shutil
    util_paths = {}
    missed = []
    for util in utils:
        path = shutil.which(util)
        if path:
            util_paths[util] = path
        else:
            missed.append(util)
    return (util_paths, missed)

def kpsewhich(tex_file):
    if os.name == "nt":
        close_fds = False
    else:
        close_fds = True
    p = Popen("kpsewhich %s" % tex_file, shell=True,
              stdin=PIPE, stdout=PIPE, close_fds=close_fds)
    data = p.communicate()[0]
    if isinstance(data, bytes):
        data = data.decode(sys.getdefaultencoding())
    return data.strip()


class Sdist(sdist):
    """
    Make the source package, and remove the .pyc files
    """
    def prune_file_list(self):
        sdist.prune_file_list(self)
        self.filelist.exclude_pattern(r'.*.pyc', is_regex=1)


class Install(install):

    user_options = install.user_options + \
                   [('catalogs=', None, 'default SGML catalogs'),
                    ('nodeps', None, 'don\'t check the dependencies'),
                    ('style=', None, 'default style to use'),
                    ('use-python-path', None, 'don\'t use env to locate python')]

    def initialize_options(self):
        install.initialize_options(self)
        self.catalogs = None
        self.nodeps = None
        self.style = None
        self.use_python_path = None
        # Prevents from undefined 'install_layout' attribute
        if not(getattr(self, "install_layout", None)):
            self.install_layout = None

    def check_util_dependencies(self):
        # First, check non critical graphic tools
        found, missed = find_programs(("epstopdf", "convert", "fig2dev"))
        for util in found:
            print("+checking %s... yes" % util)
        for util in missed:
            print("+checking %s... no" % util)
        if missed:
            print(("warning: not found: %s" % ", ".join(missed)))

        # Now, be serious
        found, missed = find_programs(("latex", "makeindex",
                                       "pdflatex", "kpsewhich"))
        for util in found:
            print("+checking %s... yes" % util)
        for util in missed:
            print("+checking %s... no" % util)
        if missed:
            raise OSError("not found: %s" % ", ".join(missed))

    def check_xslt_dependencies(self):
        sys.path.insert(0, "lib")
        from dbtexmf.xslt import xslt
        sys.path.remove("lib")

        # At least one XSLT must be available
        deplists = xslt.get_deplists()
        if not(deplists):
            raise OSError("no XSLT available")

        # For each XSLT check the programs they depend on
        xslt_found = []
        xslt_missed = []
        for (mod, deplist) in deplists:
            if not(deplist):
                xslt_found.append(mod)
                print("+checking XSLT %s... yes" % mod)
                continue
            found, missed = find_programs(deplist)
            if missed:
                xslt_missed.append(mod)
                print("+checking XSLT %s... no (missing %s)" % \
                      (mod, ", ".join(missed)))
            else:
                xslt_found.append(mod)
                print("+checking XSLT %s... yes" % mod)

        if not(xslt_found):
            raise OSError("XSLT not installed: %s" % ", ".join(xslt_missed))
        elif xslt_missed:
            print("warning: XSLT not found: %s" % ", ".join(xslt_missed))

    def check_latex_dependencies(self):
        # Find the Latex files from the package
        stys = []
        for root, dirs, files in os.walk('latex/'):
            stys += glob.glob(os.path.join(root, "*.sty"))
        if stys:
            own_stys = [os.path.basename(s)[:-4] for s in stys]
        else:
            own_stys = []

        # Find the used packages
        used_stys = []
        re_sty = re.compile(r"\\usepackage\s*\[?.*\]?{(\w+)}")
        for sty in stys:
            f = open(sty, "rt", encoding="latin-1")
            for line in f:
                line = line.split("%")[0]
                m = re_sty.search(line)
                if m:
                    p = m.group(1)
                    try:
                        used_stys.index(p)
                    except:
                        used_stys.append(p)
            f.close()

        # Now look if they are found
        found_stys = []
        mis_stys = []
        used_stys.sort()

        # Dirty...
        for f in ("truncate", "elfonts", "CJKutf8", "pinyin", "ifxetex"):
            try:
                used_stys.remove(f)
            except:
                pass

        for sty in used_stys:
            if sty in found_stys:
                continue
            status = "+checking %s... " % sty
            if sty in own_stys:
                status += "found in package"
                found_stys.append(sty)
                print(status)
                continue
            stypath = kpsewhich("%s.sty" % sty)
            if stypath:
                status += "yes"
                found_stys.append(sty)
            else:
                status += "no"
                mis_stys.append(sty)
            print(status)
            
        if mis_stys:
            raise OSError("not found: %s" % ", ".join(mis_stys))

    def run(self):
#        if self.install_layout == "deb":
#            db = DebianInstaller(self)
#        else:
        db = None

        if not(db) and not(self.nodeps):
            try:
                self.check_xslt_dependencies()
                self.check_util_dependencies()
                self.check_latex_dependencies()
            except Exception as e:
                print("Error: %s" % e, file=sys.stderr)
                sys.exit(1)

        if db: db.adapt_paths()

        # If no build is required, at least build the script
        if self.skip_build:
            self.run_command('build_scripts')

        install.run(self)

        if db: db.finalize()


class InstallData(install_data):

    def run(self):
        ignore_pattern = os.path.sep + r"(CVS|RCS)" + os.path.sep
        # literal backslash must be doubled in regular expressions
        ignore_pattern = ignore_pattern.replace('\\', r'\\')

        # Walk through sub-dirs, specified in data_files and build the
        # full data files list accordingly
        full_data_files = []
        for install_base, paths in self.data_files:
            base_files = []
            for path in paths:
                if os.path.isdir(path):
                    pref = os.path.dirname(path)
                    for root, dirs, files in os.walk(path):
                        if re.search(ignore_pattern, root + os.sep):
                            continue
                        # Only the last directory is copied, not the full path
                        if not(pref):
                            iroot = root
                        else:
                            iroot = root.split(pref + os.path.sep, 1)[1]
                        idir = os.path.join(install_base, iroot)
                        files = [os.path.join(root, i) for i in files]
                        if files:
                            full_data_files += [(idir, files)]
                else:
                    base_files.append(path)

            if base_files:
                full_data_files += [(install_base, base_files)]

        # Replace synthetic data_files by the full one, and do the actual job
        self.data_files = full_data_files
        rc = install_data.run(self)

        if self.distribution.get_command_obj("install").install_layout != "deb":
            self.adapt_installed_data()
        return rc

    def adapt_installed_data(self):
        installed = self.get_outputs()
        for data_file in installed:
            if os.path.basename(data_file) == "param.xsl":
                self._set_texlive_version(data_file)
                break

    def _set_texlive_version(self, param_file):
        """Detect the installed Texlive version from hyperref.sty version, and
        override the texlive.version param accordingly."""
        hyper_sty = kpsewhich("hyperref.sty")
        if not(hyper_sty):
            # Cannot do anything, give up
            return

        # Grab the value from package version
        d = open(hyper_sty, "rt", encoding="latin-1").read()
        m = re.search(r"\\ProvidesPackage{hyperref}\s+\[(\d+)", d, re.M)
        if not(m):
            return
        year = m.group(1)

        # Patch the parameter with the found value
        p = open(param_file, "rt", encoding="latin-1").read()
        p2 = re.sub('name="texlive.version">.*<',
                    'name="texlive.version">%s<' % year, p)
        f = open(param_file, "wt", encoding="latin-1")
        f.write(p2)
        f.close()


def get_version():
    sys.path.insert(0, "lib")
    from dbtexmf.dblatex import dblatex
    d = dblatex.DbLatex(base=os.getcwd())
    sys.path.remove("lib")
    return d.get_version()


if __name__ == "__main__":
    pdfdocs = glob.glob(os.path.join("docs", "[mr]*.pdf"))
    htmldoc = [os.path.join("docs", "xhtml")]
    classifiers = [
       "Operating System :: OS Independent",
       "Topic :: Text Processing :: Markup :: XML",
       "License :: OSI Approved :: GNU General Public License (GPL)"
    ]

    description = """
       dblatex is a program that transforms your SGML/XML DocBook documents to
       DVI, PostScript or PDF by translating them into pure LaTeX as a first
       process.  MathML 2.0 markups are supported, too. It started as a clone
       of DB2LaTeX.
       """

    setup(name="dblatex",
        version=get_version(),
        description='DocBook to LaTeX/ConTeXt Publishing',
        author='Benoit Guillon',
        author_email='marsgui@users.sourceforge.net',
        url='http://dblatex.sf.net',
        license='GPL Version 2 or later',
        long_description=description,
        classifiers=classifiers,
        packages=['dbtexmf',
                  'dbtexmf.core',
                  'dbtexmf.xslt',
                  'dbtexmf.dblatex',
                  'dbtexmf.dblatex.xetex',
                  'dbtexmf.dblatex.grubber'],
        package_dir={'dbtexmf':'lib/dbtexmf'},
        package_data={'dbtexmf.core':['sgmlent.txt'],
                      'dbtexmf.dblatex.grubber':['xindylang.xml']},
        data_files=[('share/dblatex', ['xsl', 'latex', 'etc/schema']),
                    ('share/doc/dblatex', pdfdocs),
                    ('share/doc/dblatex', htmldoc),
                    ('share/man/man1', ['docs/manpage/dblatex.1.gz'])],
        scripts=['scripts/dblatex'],
        cmdclass={'build': Build,
                  'build_scripts': BuildScripts,
                  'install': Install,
                  'install_data': InstallData,
                  'sdist': Sdist}
        )