Blob Blame History Raw
# -*- python -*-
#
# gtk-doc - GTK DocBook documentation generator.
# Copyright (C) 1998  Damon Chaplin
#               2007  David Necas (Yeti)
#               2007-2016  Stefan Sauer
#
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#

"""
The rebase tool rewrites URI references in installed HTML documentation.
"""

from __future__ import print_function
from six import iteritems, iterkeys

import logging
import os
import re
import subprocess

from . import common

# Maps.
# These two point to the last seen URI of given type for a package:
# OnlineMap: package => on-line URI
# LocalMap: package => local URI
# This maps all seen URIs of a package to fix broken links in the process:
# RevMap: URI => package
OnlineMap = {}
LocalMap = {}
RevMap = {}
# Remember what mangling we did.
Mapped = {}


def log(options, *msg):
    if options.verbose:
        print(*msg)


def run(options):
    other_dirs = []

    # We scan the directory containing GLib and any directories in GNOME2_PATH
    # first, but these will be overriden by any later scans.
    if "GNOME2_PATH" in os.environ:
        for dir in os.environ["GNOME2_PATH"].split(':'):
            dir = os.path.join(dir, "share/gtk-doc/html")
            if os.path.isdir(dir):
                log(options, "Prepending GNOME2_PATH directory:", dir)
                other_dirs = [dir] + other_dirs

    glib_dir = common.GetModuleDocDir('glib-2.0')
    if glib_dir:
        log(options, "Prepending GLib directory", glib_dir)
        other_dirs = [glib_dir] + other_dirs

    # Check all other dirs, but skip already scanned dirs ord subdirs of those

    for dir in other_dirs:
        ScanDirectory(dir, options)

    if options.relative:
        RelativizeLocalMap(options.html_dir, options)

    RebaseReferences(options.html_dir, options)
    PrintWhatWeHaveDone()


def ScanDirectory(scan_dir, options):
    log(options, "Scanning documentation directory %s", scan_dir)

    if scan_dir == options.html_dir:
        log(options, "Excluding self")
        return

    if not os.path.isdir(scan_dir):
        logging.info('Cannot open dir "%s"', scan_dir)
        return

    subdirs = []
    onlinedir = None
    have_index = False
    for entry in sorted(os.listdir(scan_dir)):
        full_entry = os.path.join(scan_dir, entry)
        if os.path.isdir(full_entry):
            subdirs.append(full_entry)
            continue

        if entry.endswith('.devhelp2'):
            log(options, "Reading index from " + entry)
            o = ReadDevhelp(scan_dir, entry)
            # Prefer this location over possibly stale index.sgml
            if o is not None:
                onlinedir = o
            have_index = True

        if onlinedir and entry == "index.sgml":
            log(options, "Reading index from index.sgml")
            onlinedir = ReadIndex(dir, entry)
            have_index = True
        elif entry == "index.sgml.gz" and not os.path.exists(os.path.join(scan_dir, 'index.sgml')):
            # debian/ubuntu started to compress this as index.sgml.gz :/
            print(''' Please fix https://bugs.launchpad.net/ubuntu/+source/gtk-doc/+bug/77138 . For now run:
gunzip %s/%s
''' % (scan_dir, entry))
        elif entry.endswith('.devhelp2.gz') and not os.path.exists(full_entry[:-3]):
            # debian/ubuntu started to compress this as *devhelp2.gz :/
            print('''Please fix https://bugs.launchpad.net/ubuntu/+source/gtk-doc/+bug/1466210 . For now run:
gunzip %s/%s
''' % (scan_dir, entry))
        # we could consider supporting: gzip module

    if have_index:
        AddMap(scan_dir, onlinedir, options)

    # Now recursively scan the subdirectories.
    for subdir in subdirs:
        ScanDirectory(subdir, options)


def ReadDevhelp(dir, file):
    onlinedir = None

    for line in common.open_text(os.path.join(dir, file)):
        # online must come before chapter/functions
        if '<chapters' in line or '<functions' in line:
            break
        match = re.search(r' online="([^"]*)"', line)
        if match:
            # Remove trailing non-directory component.
            onlinedir = re.sub(r'(.*/).*', r'\1', match.group(1))
    return onlinedir


def ReadIndex(dir, file):
    onlinedir = None

    for line in common.open_text(os.path.join(dir, file)):
        # ONLINE must come before any ANCHORs
        if '<ANCHOR' in line:
            break
        match = re.match(r'''^<ONLINE\s+href\s*=\s*"([^"]+)"\s*>''', line)
        if match:
            # Remove trailing non-directory component.
            onlinedir = re.sub(r'''(.*/).*''', r'\1', match.groups(1))
    return onlinedir


def AddMap(dir, onlinedir, options):
    package = None

    package = os.path.split(dir)[1]
    if options.dest_dir != '' and dir.startswith(options.dest_dir):
        dir = dir[len(options.dest_dir) - 1:]

    if onlinedir:
        log(options, "On-line location of %s." % onlinedir)
        OnlineMap[package] = onlinedir
        RevMap[onlinedir] = package
    else:
        log(options, "No On-line location for %s found" % package)

    log(options, "Local location of $package: " + dir)
    LocalMap[package] = dir
    RevMap[dir] = package


def RelativizeLocalMap(dirname, options):
    prefix = None
    dir = None

    dirname = os.path.realpath(dirname)
    prefix = os.path.split(dirname)
    for package, dir in LocalMap.items():
        if dir.startswith(prefix):
            dir = os.path.join("..", dir[len(prefix):])
            LocalMap[package] = dir
            log(options, "Relativizing local location of $package to " + dir)


def RebaseReferences(dirname, options):
    for ifile in sorted(os.listdir(dirname)):
        if ifile.endswith('.html'):
            RebaseFile(os.path.join(dirname, ifile), options)


def RebaseFile(filename, options):
    log(options, "Fixing file: " + filename)
    regex = re.compile(r'''(<a(?:\s+\w+=(?:"[^"]*"|'[^']*'))*\s+href=")([^"]*)(")''',
                       flags=re.MULTILINE)

    def repl_func(match):
        return match.group(1) + RebaseLink(match.group(2), options) + match.group(3)

    contents = common.open_text(filename).read()
    processed = re.sub(regex, repl_func, contents)
    newfilename = filename + '.new'
    with common.open_text(newfilename, 'w') as h:
        h.write(processed)
    os.unlink(filename)
    os.rename(newfilename, filename)


def RebaseLink(href, options):
    match = re.match(r'^(.*/)([^/]*)$', href)
    package = None
    origdir = 'INVALID'

    if match:
        dir = origdir = match.group(1)
        file = match.group(2)
        if dir in RevMap:
            package = RevMap[dir]
        else:
            match = re.match(r'\.\./([^/]+)', href)
            if match is not None:
                package = match.groups(1)
            elif options.aggressive:
                match = re.search(r'''([^/]+)/$''', href)
                package = match.groups(1)

        if package:
            if options.online and package in OnlineMap:
                dir = OnlineMap[package]
            elif package in LocalMap:
                dir = LocalMap[package]
            href = os.path.join(dir, file)
        else:
            log(options, "Can't determine package for '%s'" % href)

        if dir != origdir:
            if origdir in Mapped:
                Mapped[origdir][1] += 1
            else:
                Mapped[origdir] = [dir, 1]
    return href


def PrintWhatWeHaveDone():
    for origdir in sorted(iterkeys(Mapped)):
        info = Mapped[origdir]
        print(origdir, "->", info[0], "(%s)" % info[1])