Feature post command options 1142 (#1145)

* Added --post-command-break/catch

* Added --post-command-break/catch

* Added --post-command-error and tests

* Fixed help text for --post-command-error
This commit is contained in:
Rhet Turnbull 2023-08-05 11:11:47 -04:00 committed by GitHub
parent 8fb47d9c40
commit 2875b45d6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 98 additions and 22 deletions

View File

@ -1,5 +1,7 @@
"""export command for osxphotos CLI"""
from __future__ import annotations
import atexit
import inspect
import os
@ -9,7 +11,7 @@ import shlex
import subprocess
import sys
import time
from typing import Iterable, List, Optional, Tuple
from typing import Any, Callable, Iterable, List, Literal, Optional, Tuple
import click
@ -647,7 +649,7 @@ from .verbose import get_verbose_console, verbose_print
"If present, this file will be read after the export is completed and any rules found in the file "
"will be added to the list of rules to keep. "
"This file uses the same format as a .gitignore file and should contain one rule per line; "
"lines starting with a `#` will be ignored. "
"lines starting with a `#` will be ignored. ",
)
@click.option(
"--add-exported-to-album",
@ -683,11 +685,22 @@ from .verbose import get_verbose_console, verbose_print
"COMMAND is an osxphotos template string, for example: '--post-command exported \"echo {filepath|shell_quote} >> {export_dir}/exported.txt\"', "
"which appends the full path of all exported files to the file 'exported.txt'. "
"You can run more than one command by repeating the '--post-command' option with different arguments. "
"See also --post-command-error and --post-function."
"See Post Command below.",
type=click.Tuple(
[click.Choice(POST_COMMAND_CATEGORIES, case_sensitive=False), TemplateString()]
),
)
@click.option(
"--post-command-error",
metavar="ACTION",
help="Specify either `continue` or `break` for ACTION to control behavior when a post-command fails. "
"If `continue`, osxphotos will log the error and continue processing. "
"If `break`, osxphotos will stop processing any additional --post-command commands for the current photo "
"but will continue with the export. "
"Without --post-command-error, osxphotos will abort the export if a post-command encounters an error. ",
type=click.Choice(["continue", "break"], case_sensitive=False),
)
@click.option(
"--post-function",
metavar="filename.py::function",
@ -910,6 +923,7 @@ def export(
place,
portrait,
post_command,
post_command_error,
post_function,
preview,
preview_if_missing,
@ -1138,6 +1152,7 @@ def export(
place = cfg.place
portrait = cfg.portrait
post_command = cfg.post_command
post_command_error = cfg.post_command_error
post_function = cfg.post_function
preview = cfg.preview
preview_if_missing = cfg.preview_if_missing
@ -1575,7 +1590,7 @@ def export(
export_dir=dest,
dry_run=dry_run,
exiftool_path=exiftool_path,
export_db=export_db,
on_error=post_command_error,
verbose=verbose,
)
@ -2590,7 +2605,7 @@ def collect_files_to_keep(
KEEP_RULEs = []
# parse .osxphotos_keep file if it exists
keep_file : pathlib.Path = export_dir / ".osxphotos_keep"
keep_file: pathlib.Path = export_dir / ".osxphotos_keep"
if keep_file.is_file():
for line in keep_file.read_text().splitlines():
line = line.rstrip("\r\n")
@ -2604,10 +2619,10 @@ def collect_files_to_keep(
KEEP_RULEs.append(k.replace(export_dir_str, ""))
else:
KEEP_RULEs.append(k)
if not KEEP_RULEs:
return [], []
# have some rules to apply
matcher = osxphotos.gitignorefile.parse_pattern_list(KEEP_RULEs, export_dir)
keepers = []
@ -2841,16 +2856,18 @@ def write_extended_attributes(
def run_post_command(
photo,
post_command,
export_results,
export_dir,
dry_run,
exiftool_path,
export_db,
verbose,
photo: osxphotos.PhotoInfo,
post_command: tuple[tuple[str, str]],
export_results: ExportResults,
export_dir: str | pathlib.Path,
dry_run: bool,
exiftool_path: str,
on_error: Literal["break", "continue"] | None,
verbose: Callable[[Any], None],
):
"""Run --post-command commands"""
# todo: pass in RenderOptions from export? (e.g. so it contains strip, etc?)
for category, command_template in post_command:
files = getattr(export_results, category)
for f in files:
@ -2864,7 +2881,6 @@ def run_post_command(
if command:
verbose(f'Running command: "{command}"')
if not dry_run:
args = shlex.split(command)
cwd = pathlib.Path(f).parent
run_error = None
run_results = None
@ -2873,11 +2889,18 @@ def run_post_command(
except Exception as e:
run_error = e
finally:
run_error = run_error or run_results.returncode
if run_error:
rich_echo_error(
f'[error]Error running command "{command}": {run_error}'
)
returncode = run_results.returncode if run_results else None
if run_error or returncode:
# there was an error running the command
error_str = f'Error running command "{command}": return code: {returncode}, exception: {run_error}'
rich_echo_error(f"[error]{error_str}[/]")
if not on_error:
# no error handling specified, raise exception
raise RuntimeError(error_str)
if on_error == "break":
# break out of loop and return
return
# else on_error must be continue
def render_and_validate_report(report: str, exiftool_path: str, export_dir: str) -> str:

View File

@ -8644,14 +8644,67 @@ def test_export_post_command_bad_command():
".",
"--post-command",
"exported",
"foobar {filepath.name|shell_quote} >> {export_dir}/exported.txt",
"false",
"--name",
"Park",
"--skip-original-if-edited",
],
)
assert result.exit_code != 0
def test_export_post_command_bad_command_continue():
"""Test --post-command with bad command with --post-command-error=continue"""
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
cli_main,
[
"export",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
".",
"--post-command",
"exported",
"false",
"--post-command-error",
"continue",
"--name",
"wedding",
],
)
assert result.exit_code == 0
assert 'Error running command "foobar' in result.output
assert result.output.count("Error running command") == 2
def test_export_post_command_bad_command_break():
"""Test --post-command with bad command with --post-command-error=break"""
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
cli_main,
[
"export",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
".",
"--post-command",
"exported",
"false",
"--post-command-error",
"break",
"--name",
"wedding",
],
)
assert result.exit_code == 0
assert result.output.count("Error running command") == 1
def test_export_post_command_bad_option_1():