1
1
"""export command for osxphotos CLI"""
2
2
3
+ from __future__ import annotations
4
+
3
5
import atexit
4
6
import inspect
5
7
import os
9
11
import subprocess
10
12
import sys
11
13
import time
12
- from typing import Iterable , List , Optional , Tuple
14
+ from typing import Any , Callable , Iterable , List , Literal , Optional , Tuple
13
15
14
16
import click
15
17
647
649
"If present, this file will be read after the export is completed and any rules found in the file "
648
650
"will be added to the list of rules to keep. "
649
651
"This file uses the same format as a .gitignore file and should contain one rule per line; "
650
- "lines starting with a `#` will be ignored. "
652
+ "lines starting with a `#` will be ignored. " ,
651
653
)
652
654
@click .option (
653
655
"--add-exported-to-album" ,
683
685
"COMMAND is an osxphotos template string, for example: '--post-command exported \" echo {filepath|shell_quote} >> {export_dir}/exported.txt\" ', "
684
686
"which appends the full path of all exported files to the file 'exported.txt'. "
685
687
"You can run more than one command by repeating the '--post-command' option with different arguments. "
688
+ "See also --post-command-error and --post-function."
686
689
"See Post Command below." ,
687
690
type = click .Tuple (
688
691
[click .Choice (POST_COMMAND_CATEGORIES , case_sensitive = False ), TemplateString ()]
689
692
),
690
693
)
694
+ @click .option (
695
+ "--post-command-error" ,
696
+ metavar = "ACTION" ,
697
+ help = "Specify either `continue` or `break` for ACTION to control behavior when a post-command fails. "
698
+ "If `continue`, osxphotos will log the error and continue processing. "
699
+ "If `break`, osxphotos will stop processing any additional --post-command commands for the current photo "
700
+ "but will continue with the export. "
701
+ "Without --post-command-error, osxphotos will abort the export if a post-command encounters an error. " ,
702
+ type = click .Choice (["continue" , "break" ], case_sensitive = False ),
703
+ )
691
704
@click .option (
692
705
"--post-function" ,
693
706
metavar = "filename.py::function" ,
@@ -910,6 +923,7 @@ def export(
910
923
place ,
911
924
portrait ,
912
925
post_command ,
926
+ post_command_error ,
913
927
post_function ,
914
928
preview ,
915
929
preview_if_missing ,
@@ -1138,6 +1152,7 @@ def export(
1138
1152
place = cfg .place
1139
1153
portrait = cfg .portrait
1140
1154
post_command = cfg .post_command
1155
+ post_command_error = cfg .post_command_error
1141
1156
post_function = cfg .post_function
1142
1157
preview = cfg .preview
1143
1158
preview_if_missing = cfg .preview_if_missing
@@ -1575,7 +1590,7 @@ def cleanup_lock_files():
1575
1590
export_dir = dest ,
1576
1591
dry_run = dry_run ,
1577
1592
exiftool_path = exiftool_path ,
1578
- export_db = export_db ,
1593
+ on_error = post_command_error ,
1579
1594
verbose = verbose ,
1580
1595
)
1581
1596
@@ -2590,7 +2605,7 @@ def collect_files_to_keep(
2590
2605
KEEP_RULEs = []
2591
2606
2592
2607
# parse .osxphotos_keep file if it exists
2593
- keep_file : pathlib .Path = export_dir / ".osxphotos_keep"
2608
+ keep_file : pathlib .Path = export_dir / ".osxphotos_keep"
2594
2609
if keep_file .is_file ():
2595
2610
for line in keep_file .read_text ().splitlines ():
2596
2611
line = line .rstrip ("\r \n " )
@@ -2604,10 +2619,10 @@ def collect_files_to_keep(
2604
2619
KEEP_RULEs .append (k .replace (export_dir_str , "" ))
2605
2620
else :
2606
2621
KEEP_RULEs .append (k )
2607
-
2622
+
2608
2623
if not KEEP_RULEs :
2609
2624
return [], []
2610
-
2625
+
2611
2626
# have some rules to apply
2612
2627
matcher = osxphotos .gitignorefile .parse_pattern_list (KEEP_RULEs , export_dir )
2613
2628
keepers = []
@@ -2841,16 +2856,18 @@ def write_extended_attributes(
2841
2856
2842
2857
2843
2858
def run_post_command (
2844
- photo ,
2845
- post_command ,
2846
- export_results ,
2847
- export_dir ,
2848
- dry_run ,
2849
- exiftool_path ,
2850
- export_db ,
2851
- verbose ,
2859
+ photo : osxphotos . PhotoInfo ,
2860
+ post_command : tuple [ tuple [ str , str ]] ,
2861
+ export_results : ExportResults ,
2862
+ export_dir : str | pathlib . Path ,
2863
+ dry_run : bool ,
2864
+ exiftool_path : str ,
2865
+ on_error : Literal [ "break" , "continue" ] | None ,
2866
+ verbose : Callable [[ Any ], None ] ,
2852
2867
):
2868
+ """Run --post-command commands"""
2853
2869
# todo: pass in RenderOptions from export? (e.g. so it contains strip, etc?)
2870
+
2854
2871
for category , command_template in post_command :
2855
2872
files = getattr (export_results , category )
2856
2873
for f in files :
@@ -2864,7 +2881,6 @@ def run_post_command(
2864
2881
if command :
2865
2882
verbose (f'Running command: "{ command } "' )
2866
2883
if not dry_run :
2867
- args = shlex .split (command )
2868
2884
cwd = pathlib .Path (f ).parent
2869
2885
run_error = None
2870
2886
run_results = None
@@ -2873,11 +2889,18 @@ def run_post_command(
2873
2889
except Exception as e :
2874
2890
run_error = e
2875
2891
finally :
2876
- run_error = run_error or run_results .returncode
2877
- if run_error :
2878
- rich_echo_error (
2879
- f'[error]Error running command "{ command } ": { run_error } '
2880
- )
2892
+ returncode = run_results .returncode if run_results else None
2893
+ if run_error or returncode :
2894
+ # there was an error running the command
2895
+ error_str = f'Error running command "{ command } ": return code: { returncode } , exception: { run_error } '
2896
+ rich_echo_error (f"[error]{ error_str } [/]" )
2897
+ if not on_error :
2898
+ # no error handling specified, raise exception
2899
+ raise RuntimeError (error_str )
2900
+ if on_error == "break" :
2901
+ # break out of loop and return
2902
+ return
2903
+ # else on_error must be continue
2881
2904
2882
2905
2883
2906
def render_and_validate_report (report : str , exiftool_path : str , export_dir : str ) -> str :
0 commit comments