#
# SConscript file for building SCons documentation.
#

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

import os.path
import re
import string

Import('build_dir', 'env', 'whereis')

env = env.Clone()

build = os.path.join(build_dir, 'doc')

#
#
#
dist_doc_tar_gz = '$DISTDIR/scons-doc-${VERSION}.tar.gz'

#
# We'll only try to build text files (for some documents)
# if lynx is available to do the dump.
#
fig2dev = whereis('fig2dev')
epydoc = whereis('epydoc')
groff = whereis('groff')
lynx = whereis('lynx')
man2html = whereis('man2html')
jade_original = whereis('jade')
jade = whereis('openjade') or jade_original
jadetex = whereis('jadetex')
pdfjadetex = whereis('pdfjadetex')
jw = whereis('jw')
tidy = whereis('tidy')

tar_deps = []
tar_list = []

entity_re = re.compile(r'<!entity\s+(?:%\s+)?(?:\S+)\s+SYSTEM\s+"([^"]*)">', re.I)
format_re = re.compile(r'<(?:graphic|imagedata)\s+fileref="([^"]*)"(?:\s+format="([^"]*)")?')

#
# Find internal dependencies in .xml files:
#
#   <!entity bground SYSTEM "bground.xml">
#   <graphic fileref="file.jpg">
#   <imagedata fileref="file.jpg">
#
# This only finds one per line, and assumes that anything
# defined as a SYSTEM entity is, in fact, a file included
# somewhere in the document.
#
def scanxml(node, env, target):
    includes = []

    contents = node.get_contents()

    includes.extend(entity_re.findall(contents))

    matches = format_re.findall(contents)
    for m in matches:
        file, format = m
        if format and file[-len(format):] != format:
            file = file + '.' + format
        if not os.path.isabs(file):
            a = []
            f = file
            while f:
                f, tail = os.path.split(f)
                if tail == 'doc':
                    break
                a = [tail] + a
            file = apply(os.path.join, a, {})
        includes.append(file)

    return includes

s = Scanner(name = 'xml', function = scanxml, skeys = ['.xml', '.mod'])

orig_env = env
env = orig_env.Clone(SCANNERS = [s],
                     SCONS_PROC_PY = File('#bin/scons-proc.py').rfile(),
                     SCONSOUTPUT_PY = File('#bin/sconsoutput.py').rfile())

# Fetch the list of files in the build engine that contain
# SCons documentation XML for processing.
def chop(s): return s[:-1]

# If we ever read doc from __scons_doc__ strings in *.py files again,
# here's how it's done:
#manifest_in = File('#src/engine/MANIFEST.in').rstr()
#manifest_xml_in = File('#src/engine/MANIFEST-xml.in').rstr()
#scons_doc_files = map(chop, open(manifest_in).readlines() +\
#                            open(manifest_xml_in).readlines())
#scons_doc_files = map(lambda x: '#src/engine/'+x, scons_doc_files)
#manifest_in = File('#src/engine/MANIFEST.in').rstr()

manifest_xml_in = File('#src/engine/MANIFEST-xml.in').rstr()
scons_doc_files = map(chop, open(manifest_xml_in).readlines())
scons_doc_files = map(lambda x: File('#src/engine/'+x).rstr(), scons_doc_files)

if not jw:
    print "jw not found, skipping building User Guide."
else:
    #
    # Always create a version.xml file containing the version information
    # for this run.  Ignore it for dependency purposes so we don't
    # rebuild all the docs every time just because the date changes.
    #
    date, ver, rev = env.Dictionary('DATE', 'VERSION', 'REVISION')
    version_xml = File(os.path.join(build, "version.xml"))
    #version_xml = File("version.xml")
    verfile = str(version_xml)
    try:
        os.unlink(verfile)
    except OSError:
        pass    # okay if the file didn't exist
    dir, f = os.path.split(verfile)
    try:
        os.makedirs(dir)
    except OSError:
        pass	# okay if the directory already exists
    open(verfile, "w").write("""<!--
THIS IS AN AUTOMATICALLY-GENERATED FILE.  DO NOT EDIT.
-->
<!ENTITY builddate "%s">
<!ENTITY buildversion "%s">
<!ENTITY buildrevision "%s">
""" % (date, ver, rev))

    builders_gen = os.path.join(build, 'user', 'builders.gen')
    builders_mod = os.path.join(build, 'user', 'builders.mod')
    tools_gen = os.path.join(build, 'user', 'tools.gen')
    tools_mod = os.path.join(build, 'user', 'tools.mod')
    variables_gen = os.path.join(build, 'user', 'variables.gen')
    variables_mod = os.path.join(build, 'user', 'variables.mod')

    # We put $( - $) around $SOURCES in the command line below because
    # the path names will change when a given input file is found in
    # a repository one run and locally the next, and we don't want
    # to rebuild documentation just because it's found in one location
    # vs. the other.  The *.gen and *.mod targets will still be dependent
    # on the list of the files themselves.
    doc_output_files = [builders_gen, builders_mod,
                        tools_gen, tools_mod,
                        variables_gen, variables_mod]
    b = env.Command(doc_output_files,
                    scons_doc_files,
                    "$PYTHON $SCONS_PROC_PY --xml -b ${TARGETS[0]},${TARGETS[1]} -t ${TARGETS[2]},${TARGETS[3]} -v ${TARGETS[4]},${TARGETS[5]} $( $SOURCES $)")
    env.Depends(b, "$SCONS_PROC_PY")

    env.Local(b)

    #
    # Each document will live in its own subdirectory.  List them here
    # as hash keys, with a hash of the info to control its build.
    #
    docs = {
        'design' : {
                'htmlindex' : 'book1.html',
                'ps'        : 1,
                'pdf'       : 1,
                'text'      : 0,
        },
        # This doesn't build on all systems, and the document is old
        # enough that there's reallyno need to build it every time any
        # more, so just comment it out for now.
        #'python10' : {
        #        'htmlindex' : 't1.html',
        #        'html'      : 1,
        #        'ps'        : 1,
        #        'pdf'       : 0,
        #        'text'      : 0,
        #        'graphics'  : [
        #                        'arch.fig',
        #                        'builder.fig',
        #                        'job-task.fig',
        #                        'node.fig',
        #                        'scanner.fig',
        #                        'sig.fig'
        #                      ],
        #},
        'reference' : {
                'htmlindex' : 'book1.html',
                'html'      : 1,
                'ps'        : 1,
                'pdf'       : 1,
                'text'      : 0,
        },
        # For whenever (if ever?) we start putting developer guide
        # information in a printable document instead of the wiki.
        #'developer' : {
        #        'htmlindex' : 'book1.html',
        #        'html'      : 1,
        #        'ps'        : 1,
        #        'pdf'       : 1,
        #        'text'      : 0,
        #},
        'user' : {
                'htmlindex' : 'book1.html',
                'html'      : 1,
                'ps'        : 1,
                'pdf'       : 1,
                'text'      : 1,
                'graphics'  : [
                                'SCons-win32-install-1.jpg',
                                'SCons-win32-install-2.jpg',
                                'SCons-win32-install-3.jpg',
                                'SCons-win32-install-4.jpg',
                              ],
                'sconsoutput' : 1,
        },
    }

    #
    # We have to tell SCons to scan the top-level XML files which
    # get included by the document XML files in the subdirectories.
    #
    manifest = File('MANIFEST').rstr()
    src_files = map(lambda x: x[:-1], open(manifest).readlines())
    for s in src_files:
        base, ext = os.path.splitext(s)
        if ext in ['.fig', '.jpg']:
            orig_env.Install(build, s)
        else:
            orig_env.SCons_revision(os.path.join(build, s), s)
        Local(os.path.join(build, s))

    #
    # For each document, build the document itself in HTML, Postscript,
    # and PDF formats.
    #
    for doc in docs.keys():
        manifest = File(os.path.join(doc, 'MANIFEST')).rstr()
        src_files = map(lambda x: x[:-1],
                        open(manifest).readlines())
        build_doc = docs[doc].get('sconsoutput') and int(ARGUMENTS.get('BUILDDOC', 0))
        for s in src_files:
            doc_s = os.path.join(doc, s)
            build_s = os.path.join(build, doc, s)
            base, ext = os.path.splitext(doc_s)
            if ext in ['.fig', '.jpg']:
                orig_env.InstallAs(build_s, doc_s)
            else:
                if build_doc and ext == '.xml':
                    env.Command(doc_s,
                                base + '.in',
                                "$PYTHON $SCONSOUTPUT_PY $SOURCE > $TARGET")
                orig_env.SCons_revision(build_s, doc_s)
            Local(build_s)

        main = os.path.join(build, doc, 'main.xml')
        out = 'main.out'

        # Hard-coding the scons-src path is a bit of a hack.  This can
        # be reworked when a better solution presents itself.
        scons_src_main = os.path.join(build_dir, 'scons-src', 'doc', main)
        env.Ignore(scons_src_main, version_xml)

        htmldir = os.path.join(build, 'HTML', 'scons-%s' % doc)
        htmlindex = os.path.join(htmldir, docs[doc]['htmlindex'])
        html = os.path.join(build, 'HTML', 'scons-%s.html' % doc)
        ps = os.path.join(build, 'PS', 'scons-%s.ps' % doc)
        pdf = os.path.join(build, 'PDF', 'scons-%s.pdf' % doc)
        text = os.path.join(build, 'TEXT', 'scons-%s.txt' % doc)

        if docs[doc].get('html') and jade:
            def copy_index_html(target, source, env):
                # Older versions of DocBook|jw|jade|whatever would
                # create a book1.html file, while newer versions create
                # an index.html file (logically enough).  The scons.org
                # web site links expect book1.html, so we're going to
                # leave the target as is, and run this post-processing
                # action function to check that the target really did
                # get created, and if it didn't, copy it from index.html.
                t = str(target[0])
                if not os.path.exists(t):
                    i = os.path.join(os.path.split(t)[0], 'index.html')
                    open(t, 'w').write(open(i, 'r').read())
                return None

            cmds = [
                Delete("${TARGET.dir}/*.html"),
                "jw -b html -o ${TARGET.dir} $SOURCES",
            ]
            if tidy:
                cmds.append("tidy -m -q $TARGET || true")
            cmds.append(Action(copy_index_html))
            env.Command(htmlindex, File(main), cmds)
            Local(htmlindex)

            cmds = [
                Delete("${TARGET.dir}/main.html"),
                "jw -u -b html -o ${TARGET.dir} $SOURCES",
                Move("$TARGET", "${TARGET.dir}/main.html"),
            ]
            if tidy:
                cmds.append("tidy -m -q $TARGET || true")
            env.Command(html, File(main), cmds)
            Local(html)

            env.Ignore([html, htmlindex], version_xml)

            tar_deps.extend([html, htmlindex])
            tar_list.extend([html, htmldir])

            for g in docs[doc].get('graphics', []):
                base, ext = os.path.splitext(g)
                if ext == '.fig':
                    jpg = base + '.jpg'
                    htmldir_jpg = os.path.join(htmldir, jpg)
                    if fig2dev:
                        fig = os.path.join(build, doc, g)
                        env.Command(htmldir_jpg, fig,
                                    "%s -L jpeg -q 100 $SOURCES $TARGET" % fig2dev)
                    else:
                        env.InstallAs(htmldir_jpg, jpg)
                    env.Depends(html, htmldir_jpg)
                    Local(htmldir_jpg)
                else:
                    src = os.path.join(build, doc, g)
                    Local(env.Install(htmldir, src))

        if docs[doc].get('ps') and jadetex and jade_original:
            env.Command(ps, main, [
                Delete("${TARGET.dir}/%s" % out),
                "jw -b ps -o ${TARGET.dir} -p %s $SOURCES" % jade_original,
                "mv ${TARGET.dir}/main.ps $TARGET",
                Delete("${TARGET.dir}/%s" % out),
            ])
            Local(ps)

            env.Ignore(ps, version_xml)

            tar_deps.append(ps)
            tar_list.append(ps)

            for g in docs[doc].get('graphics', []):
                base, ext = os.path.splitext(g)
                if ext == '.fig':
                    eps = base + '.eps'
                    build_eps = os.path.join(build, 'PS', eps)
                    if fig2dev:
                        fig = os.path.join(build, doc, g)
                        env.Command(build_eps, fig, "%s -L eps $SOURCES $TARGET" % fig2dev)
                    else:
                        env.InstallAs(build_eps, eps)
                    env.Depends(ps, build_eps)
                    Local(build_eps)
                else:
                    src = os.path.join(build, doc, g)
                    Local(env.Install(htmldir, src))

        if docs[doc].get('pdf') and pdfjadetex and jade_original:
            env.Command(pdf, main, [
                Delete("${TARGET.dir}/%s" % out),
                "jw -b pdf -o ${TARGET.dir} -p %s $SOURCES" % jade_original,
                "mv ${TARGET.dir}/main.pdf $TARGET",
                Delete("${TARGET.dir}/out"),
            ])
            Local(pdf)

            env.Ignore(pdf, version_xml)

            tar_deps.append(pdf)
            tar_list.append(pdf)

        if docs[doc].get('text') and jade and lynx:
            env.Command(text, html, "lynx -dump ${SOURCE.abspath} > $TARGET")
            Local(text)

            env.Ignore(text, version_xml)

            tar_deps.append(text)
            tar_list.append(text)

#
# Man page(s), in good ol' troff format.
#
man_page_list = ['scons.1', 'sconsign.1', 'scons-time.1']

for m in man_page_list:
    x = orig_env.SCons_revision(os.path.join(build, 'man', m),
                            os.path.join('man', m))

man_i_files = ['builders.man', 'tools.man', 'variables.man']

man_intermediate_files = map(lambda x: os.path.join(build, 'man', x),
                             man_i_files)

cmd = "$PYTHON $SCONS_PROC_PY --man -b ${TARGETS[0]} -t ${TARGETS[1]} -v ${TARGETS[2]} $( $SOURCES $)"
man_intermediate_files = env.Command(man_intermediate_files,
                                     scons_doc_files,
                                     cmd)
env.Depends(man_intermediate_files, "$SCONS_PROC_PY")
Local(man_intermediate_files)

for man_1 in man_page_list:
    man, _1 = os.path.splitext(man_1)

    man_1 = os.path.join(build, 'man', man_1)

    if groff:
        ps = os.path.join(build, 'PS', '%s-man.ps' % man)
        text = os.path.join(build, 'TEXT', '%s-man.txt' % man)

        b = env.Command(ps, man_1, "( cd ${SOURCES.dir} && groff -man -Tps ${SOURCES.file} ) > $TARGET")
        Local(ps)
        env.Depends(b, man_intermediate_files)

        b = env.Command(text, man_1, "( cd ${SOURCES.dir} && groff -man -Tascii ${SOURCES.file} ) > $TARGET")
        Local(text)
        env.Depends(b, man_intermediate_files)

        tar_deps.extend([ps, text])
        tar_list.extend([ps, text])

    if man2html:
        html = os.path.join(build, 'HTML' , '%s-man.html' % man)

        def strip_to_first_html_tag(target, source, env):
            t = str(target[0])
            contents = open(t).read()
            contents = contents[string.find(contents, '<HTML>'):]
            open(t, 'w').write(contents)
            return 0

        cmds = [
            "( cd %s/man && cp %s .. )" % (build, string.join(man_i_files)),
            "( cd ${SOURCE.dir} && man2html ${SOURCE.file} ) > $TARGET",
            Action(strip_to_first_html_tag),
        ]
        if tidy:
            cmds.append("tidy -m -q $TARGET || true")
        b = env.Command(html, man_1, cmds)
        Local(html)
        env.Depends(b, man_intermediate_files)

        tar_deps.append(html)
        tar_list.append(html)

if not epydoc:
    print "epydoc not found, skipping building API documentation."
else:
    # XXX Should be in common with reading the same thing in
    # the SConstruct file.
    e = os.path.join('#src', 'engine')
    manifest_in = File(os.path.join(e, 'MANIFEST.in')).rstr()
    sources = map(lambda x: x[:-1], open(manifest_in).readlines())
    sources = filter(lambda x: string.find(x, 'Optik') == -1, sources)
    sources = filter(lambda x: string.find(x, 'Platform') == -1, sources)
    sources = filter(lambda x: string.find(x, 'Tool') == -1, sources)
    # XXX
    sources = filter(lambda x: string.find(x, 'Options') == -1, sources)

    e = os.path.join(build, '..', 'scons', 'engine')
    sources = map(lambda x, e=e: os.path.join(e, x), sources)

    epydoc_commands = [
        Delete('$OUTDIR'),
        '$EPYDOC $EPYDOCFLAGS --debug --output $OUTDIR --docformat=restructuredText --name SCons --url http://www.scons.org/ $SOURCES',
        Touch('$TARGET'),
    ]

    htmldir = os.path.join(build, 'HTML', 'scons-api')
    env.Command('${OUTDIR}/index.html', sources, epydoc_commands,
                EPYDOC=epydoc, EPYDOCFLAGS='--html', OUTDIR=htmldir)
    tar_deps.append(htmldir)
    tar_list.append(htmldir)

    # PDF and PostScript and TeX are built from the
    # same invocation.
    api_dir = os.path.join(build, 'scons-api')
    api_pdf = os.path.join(api_dir, 'api.pdf')
    api_ps = os.path.join(api_dir, 'api.ps')
    api_tex = os.path.join(api_dir, 'api.tex')
    api_targets = [api_pdf, api_ps, api_tex]
    env.Command(api_targets, sources, epydoc_commands,
                EPYDOC=epydoc, EPYDOCFLAGS='--pdf', OUTDIR=api_dir)
    Local(api_targets)

    pdf_install = os.path.join(build, 'PDF', 'scons-api.pdf')
    env.InstallAs(pdf_install, api_pdf)
    tar_deps.append(pdf_install)
    tar_list.append(pdf_install)
    Local(pdf_install)

    ps_install = os.path.join(build, 'PS', 'scons-api.ps')
    env.InstallAs(ps_install, api_ps)
    tar_deps.append(ps_install)
    tar_list.append(ps_install)
    Local(ps_install)

#
# Now actually create the tar file of the documentation,
# for easy distribution to the web site.
#
if tar_deps:
    tar_list = string.join(map(lambda x, b=build+'/': string.replace(x, b, ''),
                           tar_list))
    t = env.Command(dist_doc_tar_gz, tar_deps,
                "tar cf${TAR_HFLAG} - -C %s %s | gzip > $TARGET" % (build, tar_list))
    AddPostAction(dist_doc_tar_gz, Chmod(dist_doc_tar_gz, 0644))
    Local(t)
    Alias('doc', t)
else:
    Alias('doc', os.path.join(build_dir, 'doc'))