AST
This commit is contained in:
219
cmdlets/delete_tag.py
Normal file
219
cmdlets/delete_tag.py
Normal file
@@ -0,0 +1,219 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Sequence
|
||||
import json
|
||||
|
||||
from . import register
|
||||
import models
|
||||
import pipeline as ctx
|
||||
from helper import hydrus as hydrus_wrapper
|
||||
from ._shared import Cmdlet, CmdletArg, normalize_hash, parse_tag_arguments
|
||||
from helper.logger import log
|
||||
|
||||
|
||||
CMDLET = Cmdlet(
|
||||
name="delete-tags",
|
||||
summary="Remove tags from a Hydrus file.",
|
||||
usage="del-tags [-hash <sha256>] <tag>[,<tag>...]",
|
||||
aliases=["del-tag", "del-tags", "delete-tag"],
|
||||
args=[
|
||||
CmdletArg("-hash", description="Override the Hydrus file hash (SHA256) to target instead of the selected result."),
|
||||
CmdletArg("<tag>[,<tag>...]", required=True, description="One or more tags to remove. Comma- or space-separated."),
|
||||
],
|
||||
details=[
|
||||
"- Requires a Hydrus file (hash present) or explicit -hash override.",
|
||||
"- Multiple tags can be comma-separated or space-separated.",
|
||||
],
|
||||
)
|
||||
|
||||
@register(["del-tag", "del-tags", "delete-tag", "delete-tags"]) # Still needed for backward compatibility
|
||||
def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
# Help
|
||||
try:
|
||||
if any(str(a).lower() in {"-?", "/?", "--help", "-h", "help", "--cmdlet"} for a in args):
|
||||
log(json.dumps(CMDLET, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Check if we have a piped TagItem with no args (i.e., from @1 | delete-tag)
|
||||
has_piped_tag = (result and hasattr(result, '__class__') and
|
||||
result.__class__.__name__ == 'TagItem' and
|
||||
hasattr(result, 'tag_name'))
|
||||
|
||||
# Check if we have a piped list of TagItems (from @N selection)
|
||||
has_piped_tag_list = (isinstance(result, list) and result and
|
||||
hasattr(result[0], '__class__') and
|
||||
result[0].__class__.__name__ == 'TagItem')
|
||||
|
||||
if not args and not has_piped_tag and not has_piped_tag_list:
|
||||
log("Requires at least one tag argument")
|
||||
return 1
|
||||
|
||||
# Parse -hash override and collect tags from remaining args
|
||||
override_hash: str | None = None
|
||||
rest: list[str] = []
|
||||
i = 0
|
||||
while i < len(args):
|
||||
a = args[i]
|
||||
low = str(a).lower()
|
||||
if low in {"-hash", "--hash", "hash"} and i + 1 < len(args):
|
||||
override_hash = str(args[i + 1]).strip()
|
||||
i += 2
|
||||
continue
|
||||
rest.append(a)
|
||||
i += 1
|
||||
|
||||
# Check if first argument is @ syntax (result table selection)
|
||||
# @5 or @{2,5,8} to delete tags from ResultTable by index
|
||||
tags_from_at_syntax = []
|
||||
hash_from_at_syntax = None
|
||||
|
||||
if rest and str(rest[0]).startswith("@"):
|
||||
selector_arg = str(rest[0])
|
||||
pipe_selector = selector_arg[1:].strip()
|
||||
# Parse @N or @{N,M,K} syntax
|
||||
if pipe_selector.startswith("{") and pipe_selector.endswith("}"):
|
||||
# @{2,5,8}
|
||||
pipe_selector = pipe_selector[1:-1]
|
||||
try:
|
||||
indices = [int(tok.strip()) for tok in pipe_selector.split(',') if tok.strip()]
|
||||
except ValueError:
|
||||
log("Invalid selection syntax. Use @2 or @{2,5,8}")
|
||||
return 1
|
||||
|
||||
# Get the last ResultTable from pipeline context
|
||||
try:
|
||||
last_table = ctx._LAST_RESULT_TABLE
|
||||
if last_table:
|
||||
# Extract tags from selected rows
|
||||
for idx in indices:
|
||||
if 1 <= idx <= len(last_table.rows):
|
||||
# Look for a TagItem in _LAST_RESULT_ITEMS by index
|
||||
if idx - 1 < len(ctx._LAST_RESULT_ITEMS):
|
||||
item = ctx._LAST_RESULT_ITEMS[idx - 1]
|
||||
if hasattr(item, '__class__') and item.__class__.__name__ == 'TagItem':
|
||||
tag_name = getattr(item, 'tag_name', None)
|
||||
if tag_name:
|
||||
log(f"[delete_tag] Extracted tag from @{idx}: {tag_name}")
|
||||
tags_from_at_syntax.append(tag_name)
|
||||
# Also get hash from first item for consistency
|
||||
if not hash_from_at_syntax:
|
||||
hash_from_at_syntax = getattr(item, 'hash_hex', None)
|
||||
|
||||
if not tags_from_at_syntax:
|
||||
log(f"No tags found at indices: {indices}")
|
||||
return 1
|
||||
else:
|
||||
log("No ResultTable in pipeline (use @ after running get-tag)")
|
||||
return 1
|
||||
except Exception as exc:
|
||||
log(f"Error processing @ selection: {exc}", file=__import__('sys').stderr)
|
||||
return 1
|
||||
|
||||
# Handle @N selection which creates a list - extract the first item
|
||||
if isinstance(result, list) and len(result) > 0:
|
||||
# If we have a list of TagItems, we want to process ALL of them if no args provided
|
||||
# This handles: delete-tag @1 (where @1 expands to a list containing one TagItem)
|
||||
if not args and hasattr(result[0], '__class__') and result[0].__class__.__name__ == 'TagItem':
|
||||
# We will extract tags from the list later
|
||||
pass
|
||||
else:
|
||||
result = result[0]
|
||||
|
||||
# Determine tags and hash to use
|
||||
tags: list[str] = []
|
||||
hash_hex = None
|
||||
|
||||
if tags_from_at_syntax:
|
||||
# Use tags extracted from @ syntax
|
||||
tags = tags_from_at_syntax
|
||||
hash_hex = normalize_hash(override_hash) if override_hash else normalize_hash(hash_from_at_syntax)
|
||||
log(f"[delete_tag] Using @ syntax extraction: {len(tags)} tag(s) to delete: {tags}")
|
||||
elif isinstance(result, list) and result and hasattr(result[0], '__class__') and result[0].__class__.__name__ == 'TagItem':
|
||||
# Got a list of TagItems (e.g. from delete-tag @1)
|
||||
tags = [getattr(item, 'tag_name') for item in result if getattr(item, 'tag_name', None)]
|
||||
# Use hash from first item
|
||||
hash_hex = normalize_hash(override_hash) if override_hash else normalize_hash(getattr(result[0], "hash_hex", None))
|
||||
elif result and hasattr(result, '__class__') and result.__class__.__name__ == 'TagItem':
|
||||
# Got a piped TagItem - delete this specific tag
|
||||
tag_name = getattr(result, 'tag_name', None)
|
||||
if tag_name:
|
||||
tags = [tag_name]
|
||||
hash_hex = normalize_hash(override_hash) if override_hash else normalize_hash(getattr(result, "hash_hex", None))
|
||||
else:
|
||||
# Traditional mode - parse tag arguments
|
||||
tags = parse_tag_arguments(rest)
|
||||
hash_hex = normalize_hash(override_hash) if override_hash else normalize_hash(getattr(result, "hash_hex", None))
|
||||
|
||||
if not tags:
|
||||
log("No valid tags were provided")
|
||||
return 1
|
||||
|
||||
if not hash_hex:
|
||||
log("Selected result does not include a hash")
|
||||
return 1
|
||||
|
||||
try:
|
||||
service_name = hydrus_wrapper.get_tag_service_name(config)
|
||||
except Exception as exc:
|
||||
log(f"Failed to resolve tag service: {exc}")
|
||||
return 1
|
||||
|
||||
try:
|
||||
client = hydrus_wrapper.get_client(config)
|
||||
except Exception as exc:
|
||||
log(f"Hydrus client unavailable: {exc}")
|
||||
return 1
|
||||
|
||||
if client is None:
|
||||
log("Hydrus client unavailable")
|
||||
return 1
|
||||
|
||||
log(f"[delete_tag] Sending deletion request: hash={hash_hex}, tags={tags}, service={service_name}")
|
||||
try:
|
||||
result = client.delete_tags(hash_hex, tags, service_name)
|
||||
log(f"[delete_tag] Hydrus response: {result}")
|
||||
except Exception as exc:
|
||||
log(f"Hydrus del-tag failed: {exc}")
|
||||
return 1
|
||||
|
||||
preview = hash_hex[:12] + ('…' if len(hash_hex) > 12 else '')
|
||||
log(f"Removed {len(tags)} tag(s) from {preview} via '{service_name}'.")
|
||||
|
||||
# Re-fetch and emit updated tags after deletion
|
||||
try:
|
||||
payload = client.fetch_file_metadata(hashes=[str(hash_hex)], include_service_keys_to_tags=True, include_file_urls=False)
|
||||
items = payload.get("metadata") if isinstance(payload, dict) else None
|
||||
if isinstance(items, list) and items:
|
||||
meta = items[0] if isinstance(items[0], dict) else None
|
||||
if isinstance(meta, dict):
|
||||
# Extract tags from updated metadata
|
||||
from cmdlets.get_tag import _extract_my_tags_from_hydrus_meta, TagItem
|
||||
service_key = hydrus_wrapper.get_tag_service_key(client, service_name)
|
||||
updated_tags = _extract_my_tags_from_hydrus_meta(meta, service_key, service_name)
|
||||
|
||||
# Emit updated tags as TagItem objects
|
||||
from result_table import ResultTable
|
||||
table = ResultTable("Tags", max_columns=2)
|
||||
tag_items = []
|
||||
for idx, tag_name in enumerate(updated_tags, start=1):
|
||||
tag_item = TagItem(
|
||||
tag_name=tag_name,
|
||||
tag_index=idx,
|
||||
hash_hex=hash_hex,
|
||||
source="hydrus",
|
||||
service_name=service_name,
|
||||
)
|
||||
tag_items.append(tag_item)
|
||||
table.add_result(tag_item)
|
||||
ctx.emit(tag_item)
|
||||
|
||||
# Store items for @ selection in next command (CLI will handle table management)
|
||||
# Don't call set_last_result_table so we don't pollute history or table context
|
||||
except Exception as exc:
|
||||
log(f"Warning: Could not fetch updated tags after deletion: {exc}", file=__import__('sys').stderr)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user