#!/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)