df
Some checks failed
smoke-mm / Install & smoke test mm --help (push) Has been cancelled
Some checks failed
smoke-mm / Install & smoke test mm --help (push) Has been cancelled
This commit is contained in:
@@ -20,7 +20,9 @@ from SYS.logger import debug, log
|
||||
from Store import Store
|
||||
|
||||
|
||||
def _refresh_tag_view_if_current(file_hash: str | None, store_name: str | None, path: str | None, config: Dict[str, Any]) -> None:
|
||||
def _refresh_tag_view_if_current(
|
||||
file_hash: str | None, store_name: str | None, path: str | None, config: Dict[str, Any]
|
||||
) -> None:
|
||||
"""If the current subject matches the target, refresh tags via get-tag."""
|
||||
try:
|
||||
from cmdlet import get as get_cmdlet # type: ignore
|
||||
@@ -53,7 +55,9 @@ def _refresh_tag_view_if_current(file_hash: str | None, store_name: str | None,
|
||||
subj_paths = [norm(v) for v in [subject.get("path"), subject.get("target")] if v]
|
||||
else:
|
||||
subj_hashes = [norm(get_field(subject, f)) for f in ("hash",) if get_field(subject, f)]
|
||||
subj_paths = [norm(get_field(subject, f)) for f in ("path", "target") if get_field(subject, f)]
|
||||
subj_paths = [
|
||||
norm(get_field(subject, f)) for f in ("path", "target") if get_field(subject, f)
|
||||
]
|
||||
|
||||
is_match = False
|
||||
if target_hash and target_hash in subj_hashes:
|
||||
@@ -76,11 +80,15 @@ def _refresh_tag_view_if_current(file_hash: str | None, store_name: str | None,
|
||||
CMDLET = Cmdlet(
|
||||
name="delete-tag",
|
||||
summary="Remove tags from a file in a store.",
|
||||
usage="delete-tag -store <store> [-query \"hash:<sha256>\"] <tag>[,<tag>...]",
|
||||
usage='delete-tag -store <store> [-query "hash:<sha256>"] <tag>[,<tag>...]',
|
||||
arg=[
|
||||
SharedArgs.QUERY,
|
||||
SharedArgs.STORE,
|
||||
CmdletArg("<tag>[,<tag>...]", required=True, description="One or more tags to remove. Comma- or space-separated."),
|
||||
CmdletArg(
|
||||
"<tag>[,<tag>...]",
|
||||
required=True,
|
||||
description="One or more tags to remove. Comma- or space-separated.",
|
||||
),
|
||||
],
|
||||
detail=[
|
||||
"- Requires a Hydrus file (hash present) or explicit -query override.",
|
||||
@@ -88,28 +96,35 @@ CMDLET = Cmdlet(
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
# Help
|
||||
if should_show_help(args):
|
||||
log(f"Cmdlet: {CMDLET.name}\nSummary: {CMDLET.summary}\nUsage: {CMDLET.usage}")
|
||||
return 0
|
||||
|
||||
|
||||
def _looks_like_tag_row(obj: Any) -> bool:
|
||||
if obj is None:
|
||||
return False
|
||||
# TagItem (direct) or PipeObject/dict emitted from get-tag table rows.
|
||||
try:
|
||||
if hasattr(obj, '__class__') and obj.__class__.__name__ == 'TagItem' and hasattr(obj, 'tag_name'):
|
||||
if (
|
||||
hasattr(obj, "__class__")
|
||||
and obj.__class__.__name__ == "TagItem"
|
||||
and hasattr(obj, "tag_name")
|
||||
):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
return bool(get_field(obj, 'tag_name'))
|
||||
return bool(get_field(obj, "tag_name"))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
has_piped_tag = _looks_like_tag_row(result)
|
||||
has_piped_tag_list = isinstance(result, list) and bool(result) and _looks_like_tag_row(result[0])
|
||||
has_piped_tag_list = (
|
||||
isinstance(result, list) and bool(result) and _looks_like_tag_row(result[0])
|
||||
)
|
||||
|
||||
# Parse -query/-store overrides and collect remaining args.
|
||||
override_query: str | None = None
|
||||
@@ -151,8 +166,17 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
grouped_table = ""
|
||||
grouped_tags = get_field(result, "tag") if result is not None else None
|
||||
tags_arg = parse_tag_arguments(rest)
|
||||
if grouped_table == "tag.selection" and isinstance(grouped_tags, list) and grouped_tags and not tags_arg:
|
||||
file_hash = normalize_hash(override_hash) if override_hash else normalize_hash(get_field(result, "hash"))
|
||||
if (
|
||||
grouped_table == "tag.selection"
|
||||
and isinstance(grouped_tags, list)
|
||||
and grouped_tags
|
||||
and not tags_arg
|
||||
):
|
||||
file_hash = (
|
||||
normalize_hash(override_hash)
|
||||
if override_hash
|
||||
else normalize_hash(get_field(result, "hash"))
|
||||
)
|
||||
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]
|
||||
@@ -161,7 +185,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
if not tags_arg and not has_piped_tag and not has_piped_tag_list:
|
||||
log("Requires at least one tag argument")
|
||||
return 1
|
||||
|
||||
|
||||
# Normalize result to a list for processing
|
||||
items_to_process = []
|
||||
if isinstance(result, list):
|
||||
@@ -171,13 +195,13 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
|
||||
# Process each item
|
||||
success_count = 0
|
||||
|
||||
|
||||
# If we have TagItems and no args, we are deleting the tags themselves
|
||||
# If we have Files (or other objects) and args, we are deleting tags FROM those files
|
||||
|
||||
|
||||
# Check if we are in "delete selected tags" mode (tag rows)
|
||||
is_tag_item_mode = bool(items_to_process) and _looks_like_tag_row(items_to_process[0])
|
||||
|
||||
|
||||
if is_tag_item_mode:
|
||||
# Collect all tags to delete from the TagItems and batch per file.
|
||||
# This keeps delete-tag efficient (one backend call per file).
|
||||
@@ -186,7 +210,11 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
tag_name = get_field(item, "tag_name")
|
||||
if not tag_name:
|
||||
continue
|
||||
item_hash = normalize_hash(override_hash) if override_hash else normalize_hash(get_field(item, "hash"))
|
||||
item_hash = (
|
||||
normalize_hash(override_hash)
|
||||
if override_hash
|
||||
else normalize_hash(get_field(item, "hash"))
|
||||
)
|
||||
item_store = override_store or get_field(item, "store")
|
||||
item_path = get_field(item, "path") or get_field(item, "target")
|
||||
key = (str(item_hash or ""), str(item_store or ""), str(item_path or ""))
|
||||
@@ -202,32 +230,33 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
# "Delete tags from files" mode
|
||||
# We need args (tags to delete)
|
||||
if not tags_arg:
|
||||
log("Requires at least one tag argument when deleting from files")
|
||||
return 1
|
||||
|
||||
log("Requires at least one tag argument when deleting from files")
|
||||
return 1
|
||||
|
||||
# Process each item
|
||||
|
||||
|
||||
# If we have tags from @ syntax (e.g. delete-tag @{1,2}), we ignore the piped result for tag selection
|
||||
# but we might need the piped result for the file context if @ selection was from a Tag table
|
||||
# Actually, the @ selection logic above already extracted tags.
|
||||
|
||||
|
||||
# Process items from pipe (or single result)
|
||||
# If args are provided, they are the tags to delete from EACH item
|
||||
# If items are TagItems and no args, the tag to delete is the item itself
|
||||
for item in items_to_process:
|
||||
tags_to_delete: list[str] = []
|
||||
item_hash = normalize_hash(override_hash) if override_hash else normalize_hash(get_field(item, "hash"))
|
||||
item_path = (
|
||||
get_field(item, "path")
|
||||
or get_field(item, "target")
|
||||
item_hash = (
|
||||
normalize_hash(override_hash)
|
||||
if override_hash
|
||||
else normalize_hash(get_field(item, "hash"))
|
||||
)
|
||||
item_path = get_field(item, "path") or get_field(item, "target")
|
||||
item_store = override_store or get_field(item, "store")
|
||||
|
||||
if _looks_like_tag_row(item):
|
||||
if tags_arg:
|
||||
tags_to_delete = tags_arg
|
||||
else:
|
||||
tag_name = get_field(item, 'tag_name')
|
||||
tag_name = get_field(item, "tag_name")
|
||||
if tag_name:
|
||||
tags_to_delete = [str(tag_name)]
|
||||
else:
|
||||
@@ -244,12 +273,19 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def _process_deletion(tags: list[str], file_hash: str | None, path: str | None, store_name: str | None, config: Dict[str, Any]) -> bool:
|
||||
|
||||
def _process_deletion(
|
||||
tags: list[str],
|
||||
file_hash: str | None,
|
||||
path: str | None,
|
||||
store_name: str | None,
|
||||
config: Dict[str, Any],
|
||||
) -> bool:
|
||||
"""Helper to execute the deletion logic for a single target."""
|
||||
|
||||
|
||||
if not tags:
|
||||
return False
|
||||
|
||||
|
||||
if not store_name:
|
||||
log("Store is required (use -store or pipe a result with store)", file=sys.stderr)
|
||||
return False
|
||||
@@ -258,12 +294,16 @@ def _process_deletion(tags: list[str], file_hash: str | None, path: str | None,
|
||||
if not resolved_hash and path:
|
||||
try:
|
||||
from SYS.utils import sha256_file
|
||||
|
||||
resolved_hash = sha256_file(Path(path))
|
||||
except Exception:
|
||||
resolved_hash = None
|
||||
|
||||
if not resolved_hash:
|
||||
log("Item does not include a usable hash (and hash could not be derived from path)", file=sys.stderr)
|
||||
log(
|
||||
"Item does not include a usable hash (and hash could not be derived from path)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return False
|
||||
|
||||
def _fetch_existing_tags() -> list[str]:
|
||||
@@ -278,18 +318,23 @@ def _process_deletion(tags: list[str], file_hash: str | None, path: str | None,
|
||||
title_tags = [t for t in tags if isinstance(t, str) and t.lower().startswith("title:")]
|
||||
if title_tags:
|
||||
existing_tags = _fetch_existing_tags()
|
||||
current_titles = [t for t in existing_tags if isinstance(t, str) and t.lower().startswith("title:")]
|
||||
current_titles = [
|
||||
t for t in existing_tags if isinstance(t, str) and t.lower().startswith("title:")
|
||||
]
|
||||
del_title_set = {t.lower() for t in title_tags}
|
||||
remaining_titles = [t for t in current_titles if t.lower() not in del_title_set]
|
||||
if current_titles and not remaining_titles:
|
||||
log("Cannot delete the last title: tag. Add a replacement title first (add-tags \"title:new title\").", file=sys.stderr)
|
||||
log(
|
||||
'Cannot delete the last title: tag. Add a replacement title first (add-tags "title:new title").',
|
||||
file=sys.stderr,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
backend = Store(config)[store_name]
|
||||
ok = backend.delete_tag(resolved_hash, list(tags), config=config)
|
||||
if ok:
|
||||
preview = resolved_hash[:12] + ('…' if len(resolved_hash) > 12 else '')
|
||||
preview = resolved_hash[:12] + ("…" if len(resolved_hash) > 12 else "")
|
||||
debug(f"Removed {len(tags)} tag(s) from {preview} via store '{store_name}'.")
|
||||
_refresh_tag_view_if_current(resolved_hash, store_name, path, config)
|
||||
return True
|
||||
@@ -302,6 +347,3 @@ def _process_deletion(tags: list[str], file_hash: str | None, path: str | None,
|
||||
# Register cmdlet (no legacy decorator)
|
||||
CMDLET.exec = _run
|
||||
CMDLET.register()
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user