Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature post command options 1142 #1145

Merged
merged 5 commits into from
Aug 5, 2023
Merged
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
63 changes: 43 additions & 20 deletions osxphotos/cli/export.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""export command for osxphotos CLI"""

from __future__ import annotations

import atexit
import inspect
import os
Expand All @@ -9,7 +11,7 @@
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

Expand Down Expand Up @@ -647,7 +649,7 @@
"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",
Expand Down Expand Up @@ -683,11 +685,22 @@
"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",
Expand Down Expand Up @@ -910,6 +923,7 @@ def export(
place,
portrait,
post_command,
post_command_error,
post_function,
preview,
preview_if_missing,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1575,7 +1590,7 @@ def cleanup_lock_files():
export_dir=dest,
dry_run=dry_run,
exiftool_path=exiftool_path,
export_db=export_db,
on_error=post_command_error,
verbose=verbose,
)

Expand Down Expand Up @@ -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")
Expand All @@ -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 = []
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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:
Expand Down
57 changes: 55 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 'Error running command "foobar' in result.output
assert result.output.count("Error running command") == 1


def test_export_post_command_bad_option_1():
Expand Down