Skip to content

Add copy rule action (#136)#167

Open
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1784154491-copy-rule-action
Open

Add copy rule action (#136)#167
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1784154491-copy-rule-action

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Summary

Implements issue #136: a new copy rule action that duplicates the value of one tag (the source) into the tag(s) being acted on (the destination), with an optional sed transform applied to the copied value.

Syntax:

  • copy:{source} — e.g. edition/copy:catalognumber sets edition to the value of catalognumber.
  • copy:{source}:sed:{pattern}:{replacement} — e.g. edition/copy:releasetitle:sed:Deluxe:Standard copies releasetitle into edition, applying the sed substitution first. The sed portion reuses the existing SedAction grammar/escaping/round-trip.

Key design decisions

Source resolution happens at the execute_metadata_actions level. The existing execute_single_action / execute_multi_value_action only receive the destination field's value, with no access to other fields. copy needs a different field's value, so the source is resolved once per track (via a new _resolve_copy_source_value) and threaded into those helpers as a copy_source argument:

copy_source: str | list[str] | None = None
if isinstance(act.behavior, CopyAction):
    if act.behavior.source in ("new", "favorite", "rating"):
        datafile = datafile or open_datafile(tags.path)
    copy_source = _resolve_copy_source_value(act.behavior.source, tags, datafile)

Single-value tags resolve to a str; multi-value tags resolve to a list[str]. Datafile fields (new/favorite/rating) are supported as sources too.

Overwrite semantics. Unlike the other actions (which transform the destination's existing value), copy overwrites the destination wholesale with the source value. This means it also works when the destination is empty (e.g. copying genre into an empty descriptor).

Multi-value behavior:

  • single → single: copies the string.
  • multi → multi: copies the list.
  • single → multi: becomes a single-element list.
  • multi → single: rejected at parse time with a clear InvalidRuleError (Cannot copy multi-valued source tag genre into single-valued tag(s) edition) rather than silently corrupting data.

Pattern filters the source. Because the destination is overwritten wholesale, the action's pattern filters which source values are copied (not destination values). E.g. descriptor:^Pop$/copy:genre copies only the genre values equal to Pop. The nested sed follows standalone-sed conventions (;-splitting, empty-value removal, dedup for multi-value destinations).

Valid source tags. Any concrete Tag may be a source, including non-modifiable ones like tracktotal, since the source is only ever read.

Changes

  • rule_parser.py: CopyAction dataclass; added to the Action.behavior union everywhere; __str__ serialization (round-trips: str(Action.parse(x)) == x); "copy" added to valid_actions; argument parsing incl. nested sed; COPYABLE_TAGS; multi→single shape validation.
  • rules.py: _resolve_copy_source_value; copy_source threaded through execute_single_action / execute_multi_value_action; copy branches in both.
  • __init__.py: export CopyAction.
  • docs/METADATA_TOOLS.md: documents the action, semantics, multi-value rules, and grammar.
  • Tests: parsing + __str__ round-trip (incl. sed + escaping) and error cases in rule_parser_test.py; execution (single→single, single→multi, multi→multi, into-empty, copy+sed, pattern filtering, datafile source) in rules_test.py.

Verification

mypy, ruff format --check, ruff check, and prettier --check pass on the changed files; the rule_parser and rules test suites pass (69 tests). Pre-existing failures in unrelated suites (cache/releases/virtualfs/cli) are environmental (SQLite/FUSE) and reproduce on master without these changes.

Link to Devin session: https://app.devin.ai/sessions/5b13e52d65c0400487a716d901805117
Requested by: @azuline

@azuline azuline self-assigned this Jul 15, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment thread docs/METADATA_TOOLS.md

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Documentation still says "five" action kinds instead of "six" after adding the copy action

The count of supported action kinds is left as "five" (docs/METADATA_TOOLS.md:474) even though the same document was updated to say "six" earlier (docs/METADATA_TOOLS.md:390), so users reading the grammar section see a stale count.

Impact: Users may be confused by the inconsistent documentation about how many action kinds exist.

Stale reference in the grammar section

The PR correctly updated line 390 from "five" to "six" but missed the second occurrence at line 474 which still reads "one of the five supported action kinds".

(Refers to line 474)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@azuline
azuline marked this pull request as draft July 16, 2026 04:13
@azuline
azuline marked this pull request as ready for review July 16, 2026 04:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant