diff options
Diffstat (limited to 'patternutils/commands/tmv.py')
| -rw-r--r-- | patternutils/commands/tmv.py | 109 |
1 files changed, 73 insertions, 36 deletions
diff --git a/patternutils/commands/tmv.py b/patternutils/commands/tmv.py index fa7105e..58a75e5 100644 --- a/patternutils/commands/tmv.py +++ b/patternutils/commands/tmv.py @@ -1,14 +1,16 @@ -import json import os.path import shutil +from collections.abc import Iterable +from typing import Any, TextIO import click from .. import config, template, utils +from ..types import TemplateExtensions def move_file(old_filename: str, new_filename: str) -> None: - # Create new directories if needed + """Moves a file to a new location, creating necessary directories if needed.""" dirs = os.path.dirname(new_filename) if dirs: os.makedirs(dirs, exist_ok=True) @@ -22,56 +24,80 @@ def move_file(old_filename: str, new_filename: str) -> None: "--interactive", is_flag=True, default=False, - help="prompt before renaming files", + help="Prompt before renaming files.", ) @click.option( "-e", "--editor", is_flag=True, default=False, - help="Open an editor before renaming files to allow manual changes", + help="Open an editor before renaming files to allow manual changes.", +) +@click.option( + "-f", + "--force", + is_flag=True, + default=False, + help="Force rename, even if a file exists.", ) -@click.option("-f", "--force", is_flag=True, default=False, help="force rename") @click.option( "-t", "--template-engine", default="python", type=click.Choice(["python", "jinja2"]), - help="select the template engine to use", + help="Select the template engine to use.", ) @click.option( - "-k", "--key", default="_abspath", help="select the key to use as the source file" + "-k", + "--key", + default="_abspath", + help="Select the key to use as the source file.", ) @click.option( "-s", "--stream", is_flag=True, default=False, - help="Input is a stream of JSON objects", + help="Read input as stream of JSON objects.", ) @click.option( "-p", "--use-predefined-template", is_flag=True, default=False, - help="use predefined template", + help="Use a predefined template.", +) +@click.option( + "--read-from", + default="-", + type=click.File("r"), + help="Input file (default: stdin)", ) -@click.option("--read-from", default="-", type=click.File("r")) @click.option( "--abort-on-path-exist", is_flag=True, default=False, - help="exit if a renamed path already exists", + help="Exit if a renamed path already exists.", +) +@click.option( + "--abort-on-key-not-found", + is_flag=True, + default=False, + help="Exit if the specified key (specified by the --key option) is not found in the input JSON.", ) @click.option( "-n", "--dry-run", is_flag=True, default=False, - help="perform a trial run with no changes made", + help="Perform a trial run without making changes.", ) @click.option( - "-v", "--verbose", is_flag=True, default=False, help="explain what is being done" + "-v", + "--verbose", + is_flag=True, + default=False, + help="Explain what is being done.", ) @click.version_option() @click.argument("output-pattern") @@ -80,29 +106,30 @@ def tmv( interactive: bool, editor: bool, force: bool, - template_engine: str, + template_engine: TemplateExtensions, abort_on_path_exist: bool, + abort_on_key_not_found: bool, use_predefined_template: bool, - read_from: click.File, + read_from: TextIO, key: str, stream: bool, dry_run: bool, verbose: bool, ) -> None: - """Rename files based on the OUTPUT_PATTERN. - It reads in a JSON list containing objects or a stream of objects (if the - --stream option is used). The object needs to contain a key, named as - the specified option KEY (default: _abspath), which points to the file to - be moved. The rest of the keys will be supplied to the templating engine to - generate the new filename. + Reads a JSON list containing objects or a stream of JSON objects (if + --stream is used). The object must contain a key (specified via --key, + default: _abspath) that points to the file to be renamed. The rest of the + keys will be supplied to the templating engine to generate the new + filename. """ + data: Iterable[dict[str, Any]] if stream: - data = map(utils.json_loads, read_from) # type: ignore + data = map(utils.json_loads, read_from) else: - data = utils.json_loads(read_from.read()) # type: ignore + data = utils.json_loads(read_from.read()) if use_predefined_template: try: @@ -110,19 +137,29 @@ def tmv( output_pattern, "tmv" ) except FileNotFoundError: - click.secho(f"Template {output_pattern} not found!", fg="red", err=True) - raise SystemExit + click.secho(f"Template '{output_pattern}' not found!", fg="red", err=True) + raise SystemExit(1) except RuntimeError: - click.secho("Multiple files found!", fg="red", err=True) - raise SystemExit + click.secho( + f"Multiple template files found for '{output_pattern}'!", + fg="red", + err=True, + ) + raise SystemExit(1) + render = template.get_render_function( + output_pattern, + "tmv", + engine=template_engine, + ) + new_filenames = dict() try: - render = template.get_render_function(output_pattern, engine=template_engine) - except RuntimeError: - click.secho("jinja2 not installed!", fg="red", err=True) - raise SystemExit - - new_filenames = dict((entry[key], render(**entry)) for entry in data) + for entry in data: + new_filenames[entry[key]] = render(**entry) + except KeyError: + click.secho(f"Key '{key}' not found in input data.", fg="red", err=True) + if abort_on_key_not_found: + raise SystemExit(1) if editor: try: @@ -130,19 +167,19 @@ def tmv( new_filenames = temporary_filenames except RuntimeError: click.secho("Your edit is not parseable!", fg="red", err=True) - raise SystemExit + raise SystemExit(1) has_existing = False non_existing = {} for old, new in new_filenames.items(): if os.path.exists(new): - click.secho(f"{new} does exist!", fg="red", err=True) + click.secho(f"File '{new}' already exists!", fg="red", err=True) has_existing = True else: non_existing[old] = new if has_existing and abort_on_path_exist: - raise SystemExit + raise SystemExit(1) files_to_move = new_filenames.items() if force else non_existing.items() |
