huge refactor of plugin system
This commit is contained in:
+34
-3
@@ -11,6 +11,9 @@ CmdletArg = sh.CmdletArg
|
||||
SharedArgs = sh.SharedArgs
|
||||
normalize_hash = sh.normalize_hash
|
||||
parse_tag_arguments = sh.parse_tag_arguments
|
||||
render_tag_value_templates = sh.render_tag_value_templates
|
||||
merge_sequences = sh.merge_sequences
|
||||
extract_tag_from_result = sh.extract_tag_from_result
|
||||
should_show_help = sh.should_show_help
|
||||
get_field = sh.get_field
|
||||
from SYS.logger import debug, log
|
||||
@@ -133,6 +136,11 @@ CMDLET = Cmdlet(
|
||||
detail=[
|
||||
"- Requires a Hydrus file (hash present) or explicit -query override.",
|
||||
"- Multiple tags can be comma-separated or space-separated.",
|
||||
"- Use #(namespace) inside a tag value to remove a derived tag, e.g. delete-tag \"title:#(track) - #(series)\".",
|
||||
"- Angle-bracket transforms match add-tag syntax, e.g. delete-tag \"code:e<padding(00,#(episode))>\".",
|
||||
"- Current documented transforms include padding, default, replace, and increment.",
|
||||
"- Template examples assume lowercase tag text; case transforms are intentionally not part of the documented syntax.",
|
||||
"- See docs/tag_template_syntax.md for recipe-style examples and the current shared template syntax.",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -225,7 +233,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
store_name = override_store or get_field(result, "store")
|
||||
path = get_field(result, "path") or get_field(result, "target")
|
||||
tags = [str(t) for t in grouped_tags if t]
|
||||
return 0 if _process_deletion(tags, file_hash, path, store_name, config) else 1
|
||||
return 0 if _process_deletion(tags, file_hash, path, store_name, config, result=result) else 1
|
||||
|
||||
if not tags_arg and not has_piped_tag and not has_piped_tag_list:
|
||||
log("Requires at least one tag argument")
|
||||
@@ -316,7 +324,8 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
item_hash,
|
||||
item_path,
|
||||
item_store,
|
||||
config):
|
||||
config,
|
||||
result=item):
|
||||
success_count += 1
|
||||
|
||||
if success_count > 0:
|
||||
@@ -331,6 +340,7 @@ def _process_deletion(
|
||||
store_name: str | None,
|
||||
config: Dict[str,
|
||||
Any],
|
||||
result: Any = None,
|
||||
) -> bool:
|
||||
"""Helper to execute the deletion logic for a single target."""
|
||||
|
||||
@@ -367,12 +377,33 @@ def _process_deletion(
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
existing_tag_list = merge_sequences(
|
||||
extract_tag_from_result(result),
|
||||
_fetch_existing_tags(),
|
||||
case_sensitive=True,
|
||||
)
|
||||
|
||||
resolved_tags, unresolved_templates = render_tag_value_templates(
|
||||
tags,
|
||||
existing_tags=existing_tag_list,
|
||||
result=result,
|
||||
)
|
||||
if unresolved_templates:
|
||||
log(
|
||||
f"[delete_tag] skipped {len(unresolved_templates)} tag template(s) with unresolved #(namespace) placeholders",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
tags = list(resolved_tags)
|
||||
if not tags:
|
||||
return False
|
||||
|
||||
# Safety: only block if this deletion would remove the final title tag
|
||||
title_tags = [
|
||||
t for t in tags if isinstance(t, str) and t.lower().startswith("title:")
|
||||
]
|
||||
if title_tags:
|
||||
existing_tags = _fetch_existing_tags()
|
||||
existing_tags = existing_tag_list
|
||||
current_titles = [
|
||||
t for t in existing_tags
|
||||
if isinstance(t, str) and t.lower().startswith("title:")
|
||||
|
||||
Reference in New Issue
Block a user