#####################################################################################
#
# Copyright (c) typedef int GmbH
# SPDX-License-Identifier: EUPL-1.2
#
#####################################################################################
import os
import shutil
import sys
from importlib.resources import files
import jinja2
from txaio import make_logger
__all__ = ("Templates",)
log = make_logger()
[docs]
class Templates:
"""
Crossbar.io application templates.
"""
[docs]
SKIP_FILES = (".pyc", ".pyo", ".exe")
"""
File extensions of files to skip when instantiating an application template.
"""
[docs]
TEMPLATES = {
"default": {
"name": "default",
"help": "A WAMP router speaking WebSocket plus a static Web server.",
"basedir": "node/templates/default",
"params": {},
"skip_jinja": ["autobahn.js", "autobahn.min.js", "autobahn.min.jgz"],
}
}
"""
Application template definitions.
"""
@staticmethod
[docs]
def help():
"""
Print CLI help.
"""
print("\nAvailable Crossbar.io node templates:\n")
for name, template in Templates.TEMPLATES.values():
print(" {} {}".format(name.ljust(16, " "), template["help"]))
print("")
@staticmethod
[docs]
def init(appdir, template="default", params=None, dryrun=False, skip_existing=True):
"""
Initialize an application directory from a template by template name.
:param appdir: The path of the directory to instantiate the application template in.
:type appdir: str
:param template: The name of the application template to instantiate.
:type template: str
:param dryrun: If `True`, only perform a dry run (don't actually do anything, only prepare).
:type dryrun: bool
"""
IS_WIN = sys.platform.startswith("win")
template = Templates.TEMPLATES.get(template, None)
if not template:
raise Exception('no such application directory template: "{}"'.format(template))
basedir = str(files("crossbar") / template["basedir"])
if IS_WIN:
basedir = basedir.replace("\\", "/") # Jinja need forward slashes even on Windows
log.info("Using template from '{dir}'", dir=basedir)
appdir = os.path.abspath(appdir)
if "jinja" in template:
kwargs = template["jinja"]
else:
kwargs = {}
jinja_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(basedir), keep_trailing_newline=True, autoescape=True, **kwargs
)
_params = template["params"].copy()
if params:
_params.update(params)
created = []
try:
for root, dirs, filenames in os.walk(basedir):
for d in dirs:
reldir = os.path.relpath(os.path.join(root, d), basedir)
if "appname" in _params:
reldir = reldir.replace("appname", _params["appname"])
create_dir_path = os.path.join(appdir, reldir)
if os.path.isdir(create_dir_path):
msg = "Directory {} already exists".format(create_dir_path)
if not skip_existing:
log.info(msg)
raise Exception(msg)
else:
log.warn("{msg} - SKIPPING", msg=msg)
else:
log.info("Creating directory {dir}", dir=create_dir_path)
if not dryrun:
os.mkdir(create_dir_path)
created.append(("dir", create_dir_path))
for f in filenames:
if not f.endswith(Templates.SKIP_FILES):
src_file = os.path.abspath(os.path.join(root, f))
src_file_rel_path = os.path.relpath(src_file, basedir)
reldir = os.path.relpath(root, basedir)
if "appname" in _params:
reldir = reldir.replace("appname", _params["appname"])
f = f.replace("appname", _params["appname"])
dst_dir_path = os.path.join(appdir, reldir)
dst_file = os.path.abspath(os.path.join(dst_dir_path, f))
if os.path.isfile(dst_file):
msg = "File {} already exists".format(dst_file)
if not skip_existing:
log.info(msg)
raise Exception(msg)
else:
log.warn("{msg} - SKIPPING", msg=msg)
else:
log.info("Creating file {name}", name=dst_file)
if not dryrun:
if f in template.get("skip_jinja", []):
shutil.copy(src_file, dst_file)
else:
with open(dst_file, "wb") as dst_file_fd:
if IS_WIN:
# Jinja need forward slashes even on Windows
src_file_rel_path = src_file_rel_path.replace("\\", "/")
page = jinja_env.get_template(src_file_rel_path)
contents = page.render(**_params).encode("utf8")
dst_file_fd.write(contents)
created.append(("file", dst_file))
# force exception to test rollback
# a = 1/0
return template.get("get_started_hint", None)
except Exception:
log.failure("Something went wrong while instantiating app template - rolling back changes ..")
for ptype, path in reversed(created):
if ptype == "file":
try:
log.info("Removing file {path}", path=path)
if not dryrun:
os.remove(path)
except:
log.warn("Warning: could not remove file {path}", path=path)
elif ptype == "dir":
try:
log.info("Removing directory {path}", path=path)
if not dryrun:
os.rmdir(path)
except:
log.warn("Warning: could not remove directory {path}", path=path)
else:
raise Exception("logic error")
raise