Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -471,21 +471,28 @@ Command-line execution

One can use `docxtpl` module directly on command line to generate a docx from a template and a json file as a context::

usage: python -m docxtpl [-h] [-o] [-q] template_path json_path output_filename
usage: python -m docxtpl [-h] [-o] [-q] [--validate] [--report FILE] template_path [json_path] [output_filename]

Make docx file from existing template docx and json data.

positional arguments:
template_path The path to the template docx file.
json_path The path to the json file with the data.
output_filename The filename to save the generated docx.
json_path The path to the json file with the data (not required with --validate).
output_filename The filename to save the generated docx (not required with --validate).

optional arguments:
-h, --help show this help message and exit
-o, --overwrite If output file already exists, overwrites without asking
for confirmation
-q, --quiet Do not display unnecessary messages
--validate Check if the template Jinja2 syntax is valid
--report FILE Write validation result as JSON to FILE (requires --validate)

To validate a template without generating a document::

python -m docxtpl template.docx --validate

If the syntax is invalid, the error is printed to the console. With ``--report FILE``, a JSON report is written, for example ``{"valid": false, "error": "..."}`` or ``{"valid": true}``.

See tests/module_execute.py for an example.

Expand Down
90 changes: 86 additions & 4 deletions docxtpl/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import argparse
import json
import os
import sys

from .template import DocxTemplate, TemplateError

Expand All @@ -9,11 +10,13 @@
OUTPUT_ARG = "output_filename"
OVERWRITE_ARG = "overwrite"
QUIET_ARG = "quiet"
VALIDATE_ARG = "validate"
REPORT_ARG = "report"


def make_arg_parser():
parser = argparse.ArgumentParser(
usage="python -m docxtpl [-h] [-o] [-q] {} {} {}".format(
usage="python -m docxtpl [-h] [-o] [-q] [--validate] [--report FILE] {} [{}] [{}]".format(
TEMPLATE_ARG, JSON_ARG, OUTPUT_ARG
),
description="Make docx file from existing template docx and json data.",
Expand All @@ -22,10 +25,30 @@ def make_arg_parser():
TEMPLATE_ARG, type=str, help="The path to the template docx file."
)
parser.add_argument(
JSON_ARG, type=str, help="The path to the json file with the data."
JSON_ARG,
type=str,
nargs="?",
default=None,
help="The path to the json file with the data.",
)
parser.add_argument(
OUTPUT_ARG, type=str, help="The filename to save the generated docx."
OUTPUT_ARG,
type=str,
nargs="?",
default=None,
help="The filename to save the generated docx.",
)
parser.add_argument(
"--" + VALIDATE_ARG,
action="store_true",
help="Check if the template Jinja2 syntax is valid.",
)
parser.add_argument(
"--" + REPORT_ARG,
type=str,
metavar="FILE",
default=None,
help="Write validation result as JSON to FILE (requires --validate).",
)
parser.add_argument(
"-" + OVERWRITE_ARG[0],
Expand Down Expand Up @@ -67,8 +90,10 @@ def is_argument_valid(arg_name, arg_value, overwrite):
return arg_value.endswith(".docx") and check_exists_ask_overwrite(
arg_value, overwrite
)
elif arg_name in [OVERWRITE_ARG, QUIET_ARG]:
elif arg_name in [OVERWRITE_ARG, QUIET_ARG, VALIDATE_ARG]:
return arg_value in [True, False]
elif arg_name == REPORT_ARG:
return arg_value is None or isinstance(arg_value, str)


def check_exists_ask_overwrite(arg_value, overwrite):
Expand All @@ -94,10 +119,30 @@ def check_exists_ask_overwrite(arg_value, overwrite):


def validate_all_args(parsed_args):
if parsed_args.get(REPORT_ARG) and not parsed_args.get(VALIDATE_ARG):
raise RuntimeError("--report requires --validate.")

if parsed_args[VALIDATE_ARG]:
template_path = parsed_args[TEMPLATE_ARG]
if not is_argument_valid(TEMPLATE_ARG, template_path, False):
raise RuntimeError(
'The specified {arg_name} "{arg_value}" is not valid.'.format(
arg_name=TEMPLATE_ARG, arg_value=template_path
)
)
return

if not parsed_args[JSON_ARG] or not parsed_args[OUTPUT_ARG]:
raise RuntimeError(
"json_path and output_filename are required in render mode."
)

overwrite = parsed_args[OVERWRITE_ARG]
# Raises AssertionError if any of the arguments is not validated
try:
for arg_name, arg_value in parsed_args.items():
if arg_name in (VALIDATE_ARG, REPORT_ARG):
continue
if not is_argument_valid(arg_name, arg_value, overwrite):
raise AssertionError
except AssertionError:
Expand All @@ -108,6 +153,41 @@ def validate_all_args(parsed_args):
)


def validate_template_syntax(template_path):
try:
doc = DocxTemplate(template_path)
doc.get_undeclared_template_variables()
return None
except TemplateError as e:
return str(e)
except Exception as e:
return str(e)


def write_validation_report(report_path, valid, error=None):
payload = {"valid": valid}
if error is not None:
payload["error"] = error
with open(report_path, "w") as f:
json.dump(payload, f)


def run_validation(parsed_args):
template_path = os.path.abspath(parsed_args[TEMPLATE_ARG])
error = validate_template_syntax(template_path)
valid = error is None

if not valid:
print(error)
elif not parsed_args[QUIET_ARG]:
print("Template syntax is valid.")

if parsed_args.get(REPORT_ARG):
write_validation_report(parsed_args[REPORT_ARG], valid, error)

return 0 if valid else 1


def get_json_data(json_path):
with open(json_path) as file:
try:
Expand Down Expand Up @@ -159,6 +239,8 @@ def main():
parsed_args = get_args(parser)
try:
validate_all_args(parsed_args)
if parsed_args[VALIDATE_ARG]:
sys.exit(run_validation(parsed_args))
json_data = get_json_data(os.path.abspath(parsed_args[JSON_ARG]))
doc = make_docxtemplate(os.path.abspath(parsed_args[TEMPLATE_ARG]))
doc = render_docx(doc, json_data)
Expand Down