From a45cc806d8960dd6c8fceddd8830e40e085417f2 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 28 Sep 2021 15:12:29 -0300 Subject: [PATCH] Added basic functionality to make the module executable --- docxtpl/__main__.py | 116 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 docxtpl/__main__.py diff --git a/docxtpl/__main__.py b/docxtpl/__main__.py new file mode 100644 index 0000000..796f2d7 --- /dev/null +++ b/docxtpl/__main__.py @@ -0,0 +1,116 @@ +from .template import DocxTemplate,TemplateError +import argparse, json +from pathlib import Path +from argparse import ArgumentError + + +def make_arg_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + usage='docxtpl template_path json_path output_filename', + description='Make docx file from existing template docx and json data.', + add_help=True) + parser.add_argument('Template', + metavar='template_path', + type=str, + help='The path to the template docx file.') + parser.add_argument('Json', + metavar='json_path', + type=str, + help='The path to the json file with the data.') + parser.add_argument('Output', + metavar='output_filename', + type=str, + help='The filename to save the generated docx.') + return parser + + +def get_args(parser: argparse.ArgumentParser) -> dict: + try: + parsed_args = vars(parser.parse_args()) + return parsed_args + # There is a bug that prevents argparser from catching ArgumentError + # manually. For more info: https://bugs.python.org/issue41255 + # I know bare exceptions are wrong, could not find another way to catch + # wrong arguments. + except: + raise RuntimeError(f'Correct usage is:\n{parser.usage}') + + +def is_argument_valid(arg_name: str, arg_value: str) -> bool: + if arg_name == 'Template': + return Path(arg_value).is_file() and arg_value.endswith('.docx') + elif arg_name == 'Json': + return Path(arg_value).is_file() and arg_value.endswith('.json') + elif arg_name == 'Output': + return arg_value.endswith('.docx') and check_exists_ask_overwrite( + arg_value) + + +def check_exists_ask_overwrite(arg_value) -> bool: + if Path(arg_value).exists(): + try: + if input(f'File {arg_value} already exists, would you like to overwrite the existing file? (y/n)').lower() == 'y': + return True + else: + raise FileExistsError + except FileExistsError as e: + raise RuntimeError(f'File {arg_value} already exists, please choose a different name.') from e + else: + return True + + +def validate_all_args(parsed_args) -> None: + try: + for arg_name, arg_value in parsed_args.items(): + if not is_argument_valid(arg_name, arg_value): + raise AttributeError + except ArgumentError as e: + raise RuntimeError( + f'The specified {arg_name} "{arg_value}" is not valid.') + + +def get_json_data(json_path: Path) -> dict: + with open(json_path) as file: + try: + json_data = json.load(file) + return json_data + except json.JSONDecodeError as e: + print( + f'There was an error on line {e.lineno}, column {e.colno} while trying to parse file {json_path}') + raise RuntimeError('Failed to get json data.') + + +def make_docxtemplate(template_path: Path) -> DocxTemplate: + try: + return DocxTemplate(template_path) + except TemplateError as e: + raise RuntimeError('Could not create docx template.') from e + + +def save_file(doc: DocxTemplate, output_path: Path) -> None: + try: + doc.save(output_path) + print(f'Document successfully generated and saved at {output_path}') + except PermissionError as e: + print(f'{e.strerror}. Could not save file {e.filename}.') + raise RuntimeError('Failed to save file.') from e + + +def main() -> None: + parser = make_arg_parser() + try: + parsed_args = get_args(parser) + validate_all_args(parsed_args) + json_data = get_json_data(Path(parsed_args['Json']).resolve()) + doc = make_docxtemplate(Path(parsed_args['Template']).resolve()) + doc.render(json_data) + save_file(doc, Path(parsed_args['Output']).resolve()) + except RuntimeError as e: + print(e) + return + finally: + print('Exiting program!') + + +if __name__ == '__main__': + main()