1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
"""Command-line interface to Kajiki to render a single template."""
import argparse
import json
import os
import pathlib
import site
import sys
import kajiki.loader
try:
import tomllib
except ImportError:
_TOML_AVAILABLE = False
else:
_TOML_AVAILABLE = True
def _kv_pair(pair):
"""Convert a KEY=VALUE string to a 2-tuple of (KEY, VALUE).
This is intended for usage with the type= argument to argparse.
"""
key, sep, value = pair.partition("=")
if not sep:
msg = f"Expected a KEY=VALUE pair, got {pair}"
raise argparse.ArgumentTypeError(msg)
return key, value
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"-m",
"--mode",
dest="force_mode",
choices=["text", "xml", "html", "html5"],
help="Force a specific templating mode instead of auto-detecting based on extension.",
)
parser.add_argument(
"-i",
"--path",
action="append",
dest="paths",
default=[],
metavar="path",
help=(
"Add to the file loader's include paths. For the package "
"loader, this will add the path to Python's site directories."
),
)
parser.add_argument(
"-v",
"--var",
action="append",
dest="template_variables",
default=[],
type=_kv_pair,
metavar="KEY=VALUE",
help="Template variables, passed as KEY=VALUE pairs.",
)
parser.add_argument(
"--json",
action="append",
default=[],
type=pathlib.Path,
help="Load template variables from a JSON file.",
)
if _TOML_AVAILABLE:
parser.add_argument(
"--toml",
action="append",
default=[],
type=pathlib.Path,
help="Load template variables from a TOML file.",
)
parser.add_argument(
"-p",
"--package",
dest="loader_type",
action="store_const",
const=kajiki.loader.PackageLoader,
default=kajiki.loader.FileLoader,
help="Load based on package name instead of file path.",
)
parser.add_argument(
"file_or_package",
help="Filename or package to load.",
)
parser.add_argument(
"output_file",
type=argparse.FileType("w"),
default=sys.stdout,
nargs="?",
help="Output file. If unspecified, use stdout.",
)
opts = parser.parse_args(argv)
loader_kwargs = {}
if opts.loader_type is kajiki.loader.PackageLoader:
for path in opts.paths:
site.addsitedir(path)
else:
opts.paths.append(os.path.dirname(opts.file_or_package) or ".")
loader_kwargs["path"] = opts.paths
template_variables = {}
for json_file in opts.json:
with json_file.open("r", encoding="utf-8") as f:
template_variables.update(json.load(f))
if _TOML_AVAILABLE:
for toml_file in opts.toml:
with toml_file.open("rb") as f:
template_variables.update(tomllib.load(f))
template_variables.update(dict(opts.template_variables))
loader = opts.loader_type(force_mode=opts.force_mode, **loader_kwargs)
template = loader.import_(opts.file_or_package)
result = template(template_variables).render()
opts.output_file.write(result)
# Close the output file to avoid a ResourceWarning during unit
# tests on Python 3.4+. But don't close stdout, just flush it
# instead.
if opts.output_file is sys.stdout:
opts.output_file.flush()
else:
opts.output_file.close()
if __name__ == "__main__":
main(sys.argv[1:]) # pragma: no cover
|