diff --git a/.env b/.env new file mode 100644 index 0000000..09ca0d3 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +PYTHONPATH=./src + diff --git a/.gitignore b/.gitignore index 140aac7..f8d2a0b 100644 --- a/.gitignore +++ b/.gitignore @@ -83,7 +83,7 @@ celerybeat-schedule *.sage.py # dotenv -.env +# .env # virtualenv .venv diff --git a/.vscode/.browse.VC.db b/.vscode/.browse.VC.db new file mode 100644 index 0000000..73d6995 Binary files /dev/null and b/.vscode/.browse.VC.db differ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..29b3da6 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,35 @@ +{ + "configurations": [ + { + "name": "Default", + "includePath": [ + "${default}", + "packages/libvsc/src/include" + ], + "defines": [ + "${default}" + ], + "macFrameworkPath": [ + "${default}" + ], + "forcedInclude": [ + "${default}", + "additional/paths" + ], + "compileCommands": "${default}", + "browse": { + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "${default}", + "path": [ + "${default}" + ] + }, + "intelliSenseMode": "${default}", + "cStandard": "${default}", + "cppStandard": "${default}", + "compilerPath": "${default}", + "C_Cpp.enhancedColorization": "Enabled" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..db8dc0c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "python.defaultInterpreterPath": "${workspaceFolder}/packages/python/bin/python3", + "python.testing.unittestArgs": [ + "-v", + "-s", + "./ve/unit", + "-p", + "test_**.py" + ], + "python.envFile": "${workspaceFolder}/.env", + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true, + + "search.useIgnoreFiles": true, + "search.useGlobalIgnoreFiles": true +} + diff --git a/ivpm.yaml b/ivpm.yaml index e4b20f4..04180e6 100644 --- a/ivpm.yaml +++ b/ivpm.yaml @@ -6,10 +6,14 @@ package: - name: jinja2 type: python src: pypi + - name: pyyaml + src: pypi dev-deps: - name: jinja2 type: python src: pypi + - name: pyyaml + src: pypi - name: Sphinx src: pypi - name: sphinx-rtd-theme diff --git a/setup.py b/setup.py index 2c9ec9a..ce76db1 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,8 @@ 'setuptools_scm', ], install_requires=[ - 'jinja2>=2.10' + 'jinja2>=2.10', + 'pyyaml' ], ) diff --git a/src/vte/__main__.py b/src/vte/__main__.py index 8221d00..0654cfa 100644 --- a/src/vte/__main__.py +++ b/src/vte/__main__.py @@ -6,8 +6,12 @@ import jinja2 import os import stat +import sys from stat import * from . import template_rgy +from .cmds.generate import CmdGenerate +from .cmds.preprocess import CmdPreprocess +from .cmds.list import CmdList def substitute(vars, str): i=0 @@ -32,217 +36,61 @@ def substitute(vars, str): return str -def list_templates(args, rgy): - template_ids = sorted(rgy.template_map.keys()) - - max_len = 0 - for id in template_ids: - if len(id) > max_len: - max_len = len(id) - - fmt_str = "%-" + str(max_len) + "s - %s" - for id in template_ids: - print(fmt_str % (id, rgy.template_map[id].desc)) - -#******************************************************************** -#* generate() -#* -#******************************************************************** -def generate(args, parameters, rgy): - if args.template not in rgy.template_map.keys(): - print("Error: template does not exist") - return - - template = rgy.template_map[args.template] - - env = jinja2.Environment(loader=template) - -# jinja2.Environment.globals['name'] = "foo"; - global_vars = { - "name" : args.name - } - - # First, check all template parameters - # and set the default or take user value - for param_k in template.parameters.keys(): - param = template.parameters[param_k] - - if param_k in parameters.keys(): - global_vars[param_k] = parameters[param_k] - elif param.has_default == True: - global_vars[param_k] = param.default - else: - print("Error: parameter \"" + param_k + "\" is not specified") - return - - for param_k in parameters.keys(): - if param_k not in template.parameters.keys(): - print("Warning: user-specified parameter \"" + param_k + "\" has no effect") - - # If 'force' is not set, check for files that might be overwritten - existing_files = False - - if args.force == False: - for tmpl in env.list_templates(): - tmpl_e = env.get_template(tmpl) - - filename = substitute(global_vars, tmpl_e.name) - file_dir = os.path.dirname(filename) - try: - filename_u = tmpl_e.module.filename - - if filename_u.find("/") != -1: - # User has specified a relative path, so we assume - # this is relative to the output directory - filename = filename_u - else: - # Just a simple filename, so we assume it is relative - # to the template file - filename = file_dir + "/" + filename_u - except: - pass - - filename = substitute(global_vars, filename) - - if os.path.exists(filename): - print("Error: output file \"" + filename + "\" already exists") - existing_files = True - - if existing_files == True: - exit(1) - - for tmpl in env.list_templates(): - tmpl_e = env.get_template(tmpl) - - filename = substitute(global_vars, tmpl_e.name) - file_dir = os.path.dirname(filename) - try: - filename = tmpl_e.module.filename; - - filename_u = tmpl_e.module.filename - - if filename_u.find("/") != -1 or file_dir == "": - # User has specified a relative path, so we assume - # this is relative to the output directory - filename = filename_u - else: - # Just a simple filename, so we assume it is relative - # to the template file - filename = file_dir + "/" + filename_u - except: - pass - - filename = substitute(global_vars, filename) - - print("Note: processing template " + tmpl_e.name) - result = tmpl_e.render(global_vars) -# print("Result: " + result) - - dir = os.path.dirname(filename) - - if dir != "": - if os.path.isdir(dir) == False: - os.makedirs(dir) - - fh = open(filename, "w"); - fh.write(result) - fh.close() - - chmod = "" - try: - chmod = tmpl_e.module.chmod - except: - pass - - chmod = chmod.strip() - - if chmod != "": - mode = os.stat(filename).st_mode - - i=0 - is_add = True - while i < len(chmod): - if chmod[i] == "+": - is_add = True - elif chmod[i] == "-": - is_add = False - elif chmod[i] == "x": - if is_add: - mode = mode | stat.S_IEXEC - else: - mode = mode & ~stat.S_IEXEC - elif chmod[i] == "r": - if is_add: - mode = mode | stat.S_IREAD - else: - mode = mode & ~stat.S_IREAD - elif chmod[i] == "w": - if is_add: - mode = mode | stat.S_IWRITE - else: - mode = mode & ~stat.S_IWRITE - else: - print("Error: unknown chmod character \"" + chmod[i] + "\" in chmod string \"" + chmod + "\"") - - i=i+1 - os.chmod(filename, mode) - - -def main(): +def get_parser(): parser = argparse.ArgumentParser(prog="vte") subparser = parser.add_subparsers(dest="subcmd") + subparser.required = True + generate_cmd = subparser.add_parser("generate", help="generate source files") - generate_cmd.add_argument("-force", action="store_true", help="force overwrite of existing files") generate_cmd.add_argument("template", help="ID of template") generate_cmd.add_argument("name", help="Name to use in the template") - generate_cmd.add_argument("parameters", - metavar="KEY=VALUE", - nargs="*", - help="Specify other template variables") + # generate_cmd.add_argument("parameters", + # metavar="KEY=VALUE", + # nargs="*", + # help="Specify other template variables") + generate_cmd.set_defaults(func=CmdGenerate()) + + preprocess_cmd = subparser.add_parser("preprocess", + help="Pre-process a file") + preprocess_cmd.add_argument("-o", "--output", help="Specify output") + preprocess_cmd.add_argument("file", help="Input file") + preprocess_cmd.set_defaults(func=CmdPreprocess()) list_cmd = subparser.add_parser("list", help="list available templates") - - args = parser.parse_args() + list_cmd.set_defaults(func=CmdList()) + return parser + +def main(): + parser = get_parser() + + parameters = {} + + # Filter out parameters first + parse_args = [] + for arg in sys.argv: + if arg.startswith("-D"): + key_val = arg[2:] + eq = key_val.find('=') + if eq != -1: + parameters[key_val[0:eq]] = key_val[eq+1:] + else: + parameters[key_val] = "" + else: + parse_args.append(arg) + + args = parser.parse_args(args=parse_args[1:]) + args.parameters = parameters - template_path = [] - # TODO: query extension points - # Bring in the template path elements from - if os.getenv("VTE_TEMPLATE_PATH") != None: - for elem in os.getenv("VTE_TEMPLATE_PATH").split(":"): - template_path.append(elem) - - rgy = template_rgy.TemplateRgy(template_path) - - if args.subcmd == "generate": - if args.name.find("=") != -1: - print("Error: name contains '='") - exit(1) - - # Check that parameters are properly-specified - parameters = {} - for param in args.parameters: - idx = param.find("=") - if idx == -1: - print("Error: parameter specification \"" + param + "\" doesn't contain '='"); - exit(1) - parameters[param[:idx]] = param[idx+1:] + args.func(args) - generate(args, parameters, rgy) - elif args.subcmd == "list": - list_templates(args, rgy) - else: - print("Error: no subcommand specified") - parser.print_help() - exit(1); - if __name__ == "__main__": main() diff --git a/src/vte/cmds/__init__.py b/src/vte/cmds/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/vte/cmds/generate.py b/src/vte/cmds/generate.py new file mode 100644 index 0000000..2451483 --- /dev/null +++ b/src/vte/cmds/generate.py @@ -0,0 +1,170 @@ + +import os +import jinja2 +import stat +from vte.template_rgy import TemplateRgy + +class CmdGenerate(object): + + def __call__(self, args): + parameters = args.parameters + rgy = TemplateRgy.inst() + + rgy.find_templates() + + if args.template not in rgy.template_map.keys(): + raise Exception("template %s does not exist" % args.template) + + template = rgy.template_map[args.template] + + env = jinja2.Environment(loader=template) + + global_vars = { + "name" : args.name + } + + # First, check all template parameters + # and set the default or take user value + for param_k in template.parameters.keys(): + param = template.parameters[param_k] + + if param_k in parameters.keys(): + global_vars[param_k] = parameters[param_k] + elif param.has_default == True: + global_vars[param_k] = param.default + else: + raise Exception("Parameter \"" + param_k + "\" is not specified") + + for param_k in parameters.keys(): + if param_k not in template.parameters.keys(): + print("Warning: user-specified parameter \"" + param_k + "\" has no effect") + + # If 'force' is not set, check for files that might be overwritten + existing_files = False + + if args.force == False: + for tmpl in env.list_templates(): + tmpl_e = env.get_template(tmpl) + + filename = self.substitute(global_vars, tmpl_e.name) + file_dir = os.path.dirname(filename) + try: + filename_u = tmpl_e.module.filename + + if filename_u.find("/") != -1: + # User has specified a relative path, so we assume + # this is relative to the output directory + filename = filename_u + else: + # Just a simple filename, so we assume it is relative + # to the template file + filename = file_dir + "/" + filename_u + except: + pass + + filename = self.substitute(global_vars, filename) + + if os.path.exists(filename): + print("Error: output file \"" + filename + "\" already exists") + existing_files = True + + if existing_files == True: + raise Exception("Existing files") + + for tmpl in env.list_templates(): + tmpl_e = env.get_template(tmpl) + + filename = self.substitute(global_vars, tmpl_e.name) + file_dir = os.path.dirname(filename) + try: + filename = tmpl_e.module.filename; + + filename_u = tmpl_e.module.filename + + if filename_u.find("/") != -1 or file_dir == "": + # User has specified a relative path, so we assume + # this is relative to the output directory + filename = filename_u + else: + # Just a simple filename, so we assume it is relative + # to the template file + filename = file_dir + "/" + filename_u + except: + pass + + filename = self.substitute(global_vars, filename) + + print("Note: processing template " + tmpl_e.name) + result = tmpl_e.render(global_vars) + + dir = os.path.dirname(filename) + + if dir != "": + if os.path.isdir(dir) == False: + os.makedirs(dir) + + fh = open(filename, "w"); + fh.write(result) + fh.close() + + chmod = "" + try: + chmod = tmpl_e.module.chmod + except: + pass + + chmod = chmod.strip() + + if chmod != "": + mode = os.stat(filename).st_mode + + i=0 + is_add = True + while i < len(chmod): + if chmod[i] == "+": + is_add = True + elif chmod[i] == "-": + is_add = False + elif chmod[i] == "x": + if is_add: + mode = mode | stat.S_IEXEC + else: + mode = mode & ~stat.S_IEXEC + elif chmod[i] == "r": + if is_add: + mode = mode | stat.S_IREAD + else: + mode = mode & ~stat.S_IREAD + elif chmod[i] == "w": + if is_add: + mode = mode | stat.S_IWRITE + else: + mode = mode & ~stat.S_IWRITE + else: + print("Error: unknown chmod character \"" + chmod[i] + "\" in chmod string \"" + chmod + "\"") + + i=i+1 + os.chmod(filename, mode) + + def substitute(self, vars, str): + i=0 + while i < len(str): + idx = str.find("{{", i) + + if idx == -1: + break + + end = str.find("}}", idx) + + if end == -1: + print("Error: unterminated reference") + break + + key = str[idx+2:end] + + if key in vars.keys(): + str = str[:idx] + vars[key] + str[end+2:] + + i=idx+2 + + return str diff --git a/src/vte/cmds/list.py b/src/vte/cmds/list.py new file mode 100644 index 0000000..2996d48 --- /dev/null +++ b/src/vte/cmds/list.py @@ -0,0 +1,19 @@ + +from vte.template_rgy import TemplateRgy + +class CmdList(object): + + def __call__(self, args): + rgy = TemplateRgy.inst() + rgy.find_templates() + + template_ids = sorted(rgy.template_map.keys()) + + max_len = 0 + for id in template_ids: + if len(id) > max_len: + max_len = len(id) + + fmt_str = "%-" + str(max_len) + "s - %s" + for id in template_ids: + print(fmt_str % (id, rgy.template_map[id].desc)) diff --git a/src/vte/cmds/preprocess.py b/src/vte/cmds/preprocess.py new file mode 100644 index 0000000..7c563df --- /dev/null +++ b/src/vte/cmds/preprocess.py @@ -0,0 +1,38 @@ + +import sys +import jinja2 + +class CmdPreprocess(object): + + class StringTemplateLoader(jinja2.BaseLoader): + + def __init__(self, content): + self.content = content + + def list_templates(self): + return ["default"] + + def get_source(self, environment, template): + return self.content, "", lambda: True + + def __call__(self, args): + if args.file == "-": + content = sys.stdin.read() + else: + with open(args.file, "r") as fp: + content = fp.read() + + loader = CmdPreprocess.StringTemplateLoader(content) + env = jinja2.Environment(loader=loader) + + global_vars = args.parameters + + tmpl_e = env.get_template(env.list_templates()[0]) + + if args.output is None or args.output == "-": + sys.stdout.write(tmpl_e.render(global_vars)) + else: + with open(args.output, "w") as fp: + fp.write(tmpl_e.render(global_vars)) + + pass diff --git a/src/vte/template_rgy.py b/src/vte/template_rgy.py index bf10424..ca60703 100644 --- a/src/vte/template_rgy.py +++ b/src/vte/template_rgy.py @@ -9,7 +9,25 @@ class TemplateRgy: - def find_templates(self, dir, template_id): + _inst = None + + def __init__(self): + self.template_path = [] + self.templates = [] + self.template_map = {} + + if os.getenv("VTE_TEMPLATE_PATH") != None: + for elem in os.getenv("VTE_TEMPLATE_PATH").split(":"): + self.template_path.append(elem) + + def find_templates(self): + self.template_map.clear() + self.templates.clear() + + for d in self.template_path: + self.find_templates(d, []) + + def _find_templates(self, dir, template_id): if os.path.isfile(os.path.join(dir, ".vte")): if len(template_id) == 0: @@ -27,10 +45,9 @@ def find_templates(self, dir, template_id): template_id) template_id.pop() - def __init__(self, template_path): - self.templates = [] - self.template_map = {} - - for d in template_path: - self.find_templates(d, []) + @classmethod + def inst(cls): + if cls._inst is None: + cls._inst = cls() + return cls._inst