#!/usr/bin/env python

# Usage: run "python makeweb.py src web" to update files
# under 'web'. See "python makeweb.py --help" for more options.

import os, sys, shutil
from stat import *
from optparse import OptionParser


# how to process files that match various filename patterns
FILE_ACTION_TBL = [

    # selector_func(infilepath) -> True|False
    # output_filepath_func(outfilepath) -> outfilepath
    # cmd_line_func(opts, infilepath, outfilepath) -> cmdline|None

    ( lambda f: f.endswith('.py'),
      lambda o: o.replace('.py', '.html'),
      lambda opts, i, o: "source-highlight --input='%s' --output='%s'" \
        % (i, o) ),

    ( lambda f: f.endswith('userguide.txt'),
      lambda o: o.replace('.txt', '.html'),
      lambda opts, i, o: "asciidoc --unsafe --conf-file='%s/layout.conf' -a icons -a toc -o '%s' '%s'" \
        % (opts.srcdir, o, i) ),

    ( lambda f: f.endswith('.txt'),
      lambda o: o.replace('.txt', '.html'),
      lambda opts, i, o: "asciidoc --unsafe --conf-file='%s/layout.conf' -a icons -o '%s' '%s'" \
        % (opts.srcdir, o, i) ),

    ( lambda f: f.endswith('layout.conf') or f.endswith('.svn') or f.endswith('.inc'),
      lambda o: o,
      lambda opts, i, o: None )
]


# list of directories to skip
DIR_SKIP_TBL = [
    '.svn'
]


def _is_older(inp, outp):
    older = True
    if os.path.exists(outp):
        modin  = os.stat(inp)[ST_MTIME]
        modout = os.stat(outp)[ST_MTIME]
        older = (modin > modout)
    return older


def _rmtree_warn(fn, path, excinfo):
    print("** WARNING **: error while doing %s on %s" % (fn, path))


def _can_skip(opts, infile, outfile):
    return not _is_older(infile, outfile) and not opts.force


def process_file(opts, infile, outfile):

    for matchfn, outfilefn, cmdfn in FILE_ACTION_TBL:
        if matchfn(infile):
            outfile_actual = outfilefn(outfile)
            cmd = cmdfn(opts, infile, outfile_actual)
            if _can_skip(opts, infile, outfile_actual):
                if opts.verbose >= 2:
                    print("up to date %s -> %s" % (infile, outfile_actual))
                return
            if cmd:
                # do cmd
                if opts.verbose:
                    print("process %s -> %s" % (infile, outfile_actual))
                if opts.verbose >= 3:
                    print("command is [%s]" % cmd)
                if not opts.dryrun:
                    os.system(cmd)
            # else if cmd is None, do nothing
            return

    # nothing matched, default action is to copy
    if not _can_skip(opts, infile, outfile):
        if opts.verbose:
            print("copying %s -> %s" % (infile, outfile))
        if not opts.dryrun:
            shutil.copy(infile, outfile)


def make(opts, indir, outdir):

    # does outdir exists?
    odexists = os.path.exists(outdir)

    # if it exists, it must be a dir!
    if odexists and not os.path.isdir(outdir):
        print("** WARNING **: output dir '%s' exists but is " \
            "not a dir, skipping" % outdir)
        return

    # make outdir if not existing
    if not odexists:
        if opts.verbose:
            print("creating %s" % outdir)
        if not opts.dryrun:
            os.mkdir(outdir)

    # process indir/* -> outdir/*
    for elem in os.listdir(indir):
        inp = os.path.join(indir, elem)
        outp = os.path.join(outdir, elem)

        # process files
        if os.path.isfile(inp):
            if os.path.exists(outp) and not os.path.isfile(outp):
                print("** WARNING **: output '%s' corresponding to " \
                    "input '%s' is not a file, skipping" % (outp, inp))
            else:
                process_file(opts, inp, outp)

        # process directories
        elif os.path.isdir(inp):
            # if dir is in skip list, silently ignore
            if elem in DIR_SKIP_TBL:
                if opts.verbose >= 3:
                    print("skipping %s" % inp)
                continue
            # just recurse
            make(opts, inp, outp)

        # neither a file nor a dir
        else:
            print("** WARNING **: input '%s' is neither file nor " \
                "dir, skipping" % inp)


def get_opts():
    usage = "usage: %prog [options] srcdir destdir"

    p = OptionParser(usage=usage)
    p.add_option("-f", "--force", action="store_true", dest="force", default=False, 
        help="ignore timestamps and force build of all files")
    p.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
        help="be chatty (use more v's to be more friendly)")
    p.add_option("-c", "--clean-first", action="store_true", dest="clean", default=False,
        help="remove outdir and everything under it before building")
    p.add_option("-n", "--dry-run", action="store_true", dest="dryrun", default=False,
        help="just print what will happen rather than doing it")

    (opts, args) = p.parse_args()
    if len(args) != 2:
        p.error("incorrect number of arguments")
    srcdir, destdir = args

    if not os.path.exists(srcdir):
        p.error("srcdir (%s) does not exist" % srcdir)
    if not os.path.isdir(args[0]):
        p.error("srcdir (%s) is not a directory" % srcdir)
    if os.path.exists(destdir) and not os.path.isdir(destdir):
        p.error("destdir (%s) is not a directory" % destdir)

    if opts.verbose == None:
        opts.verbose = 0

    # add srcdir and destdir also to opts
    opts.srcdir = srcdir
    opts.destdir = destdir

    return (opts, srcdir, destdir)


def main():
    opts, src, dest = get_opts()
    if opts.verbose >= 3:
        print(("running with options:\nsrc = [%s]\ndest = [%s]\nverbose = [%d]\n" +\
            "dry-run = [%d]\nclean-first = [%d]\nforce = [%d]") %\
            (src, dest, opts.verbose, opts.dryrun, opts.clean, opts.force))
    if opts.dryrun and opts.verbose == 0:
        opts.verbose = 1
    if opts.clean:
        if opts.dryrun or opts.verbose:
            print("removing tree %s" % dest)
        if not opts.dryrun:
            os.rmtree(dest, True, _rmtree_warn)
    make(opts, src, dest)


main()

