git » pygen » master » tree

[master] / pygen

#!/usr/bin/env python3

import sys
import os
import io
import shutil
from optparse import OptionParser


def printd(*args):
    for i in args:
        print(i, end=" ", file=sys.stderr)
    print(file=sys.stderr)


def is_newer_than(src, dst):
    """Determine if "src" is newer than "dst"."""
    if not os.path.exists(dst):
        # To simplify callers, return as if src is newer when there is
        # no dst, so that it will be copied
        return True
    # We have to use the integer part because copystat() is not accurate
    # and loses precision when copying mtimes, causing the mtimes to be
    # different and sometimes the new file has an older mtime than the old
    # one. Since we do not expect to need sub-second precision when
    # regenerating, this should be safe.
    return int(os.path.getmtime(src)) > int(os.path.getmtime(dst))


def gen_from(src, dst, environ=None, incpath=None):
    "Process src and generate dst."

    # open files if necessary
    opened_src = opened_dst = False
    if not isinstance(src, io.IOBase):
        src = open(src)
        opened_src = True
    if not isinstance(dst, io.IOBase):
        dst = open(dst, "w")
        opened_dst = True

    # set up the environment for execution
    preout = sys.stdout
    if not environ:
        sys.stdout = dst
        environ = {
            "sys": sys,
            "src_mtime": os.path.getmtime(src.name),
        }

    # include path search
    if not incpath:
        incpath = options.include
    base, reduced = os.path.split(src.name)
    incpath.append(base)

    # state variables
    in_python = 0
    in_python_code = ""

    for line in src:
        # state parsing
        if in_python and not line.startswith("#endpy"):
            in_python_code += line
            continue

        if line.startswith("#include "):
            path = line.split()[1]
            origpath = path
            if not os.path.exists(path):
                for p in incpath:
                    newpath = p + "/" + path
                    if os.path.exists(newpath):
                        path = newpath
                        break
                else:
                    printd("Unable to find file to include")
                    printd("\tsrc: %s" % src.name)
                    printd("\tfile: %s" % origpath)
                    printd("\tincpath: %s" % str(incpath))
                    os.unlink(dst.name)
                    return

            gen_from(path, dst, environ)

        elif line.startswith("#py "):
            code = line.split(" ", 1)[1]
            exec(code, environ)

        elif line.startswith("#py:"):
            in_python = 1
            in_python_code = ""

        elif line.startswith("#endpy"):
            exec(in_python_code, environ)
            in_python = 0
            in_python_code = ""

        elif line.startswith("#print"):
            s = line.split(" ", 1)[1]
            s = "print(" + s + ")"
            exec(s, environ)

        else:
            dst.write(line)

    # restore environment
    sys.stdout = preout

    if opened_src:
        src.close()
    if opened_dst:
        dst.close()

    # preserve mtime
    shutil.copystat(src.name, dst.name)


def autogen(src, dst):
    "Generate automatically by walking src and storing results in dst."

    srcroot = os.path.abspath(src)
    base, reduced = os.path.split(srcroot)
    tree = os.walk(srcroot)

    if not os.path.isdir(dst):
        print("mkdir", dst)
        os.mkdir(dst)

    # directories' stats must be updated after we do all the generation,
    # so we append them to the list as we go
    to_update_stat = []

    for path, dirs, files in tree:
        for d in dirs:
            fulld = path + "/" + d
            diff = fulld[len(srcroot) :]

            if [x for x in options.exclude if diff[1:].startswith(x)]:
                continue

            p = os.path.normpath(dst + "/" + diff)
            if os.path.islink(fulld):
                files.append(d)
                continue
            if not os.path.isdir(p):
                print("mkdir", p)
                os.mkdir(p)
                to_update_stat.append((fulld, p))

        for f in files:
            fullf = path + "/" + f
            diff = fullf[len(srcroot) :]

            if [x for x in options.exclude if diff[1:].startswith(x)]:
                continue

            if diff.endswith(".pgh"):
                # header files are ignored
                print("ignored", diff)
                continue
            elif diff.endswith(".pg"):
                t = os.path.splitext(diff)[0]
                t = os.path.normpath(dst + "/" + t)

                if not is_newer_than(fullf, t):
                    print("skipped", diff)
                    continue
                print(diff, "->", t)
                gen_from(fullf, t)
            else:
                t = os.path.normpath(dst + "/" + diff)
                if not is_newer_than(fullf, t):
                    print("skipped", diff)
                    continue
                if os.path.islink(fullf):
                    dlink = os.readlink(fullf)
                    print("symlink", diff, "->", dlink)
                    if os.path.exists(t):
                        os.unlink(t)
                    os.symlink(dlink, t)
                elif os.path.isfile(fullf):
                    print("copy", diff)
                    shutil.copy2(fullf, t)

    # second pass to preserve directories' modification time
    for s, d in to_update_stat:
        print("copystat", d)
        shutil.copystat(s, d)


if __name__ == "__main__":
    usage = "usage: %prog [options] {gen|autogen} [action_parameters]"

    parser = OptionParser(usage)
    parser.add_option(
        "-I",
        "--include",
        action="append",
        default=[],
        help="Include directory for .pgh files",
    )
    parser.add_option(
        "-x",
        "--exclude",
        action="append",
        default=[],
        help="Paths to exclude from the generation",
    )
    # parser.add_option("-v", "--verbose", action = "store_true",
    # 		help = "Show additional information")

    options, args = parser.parse_args()

    if len(args) < 1:
        parser.error("Incorrect number of arguments")
        sys.exit(1)

    action = args[0]

    if action == "gen":
        if len(args) < 2:
            parser.error("Incorrect number of arguments")
            sys.exit(1)
        for src in args[1:]:
            dst = os.path.splitext(src)[0]
            print(src, "->", dst)
            gen_from(src, dst)

    elif action == "autogen":
        if len(args) < 3:
            parser.error("Incorrect number of arguments")
            sys.exit(1)
        srcroot, dstroot = args[1:3]
        autogen(srcroot, dstroot)

    else:
        parser.error("Unknown action")
        sys.exit(1)