summaryrefslogtreecommitdiff
path: root/patternutils/commands/tmv.py
diff options
context:
space:
mode:
Diffstat (limited to 'patternutils/commands/tmv.py')
-rw-r--r--patternutils/commands/tmv.py109
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()