Add copy rule action (#136)#167
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
🟡 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)
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Implements issue #136: a new
copyrule action that duplicates the value of one tag (thesource) into the tag(s) being acted on (the destination), with an optionalsedtransform applied to the copied value.Syntax:
copy:{source}— e.g.edition/copy:catalognumbersetseditionto the value ofcatalognumber.copy:{source}:sed:{pattern}:{replacement}— e.g.edition/copy:releasetitle:sed:Deluxe:Standardcopiesreleasetitleintoedition, applying the sed substitution first. The sed portion reuses the existingSedActiongrammar/escaping/round-trip.Key design decisions
Source resolution happens at the
execute_metadata_actionslevel. The existingexecute_single_action/execute_multi_value_actiononly receive the destination field's value, with no access to other fields.copyneeds 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 acopy_sourceargument:Single-value tags resolve to a
str; multi-value tags resolve to alist[str]. Datafile fields (new/favorite/rating) are supported as sources too.Overwrite semantics. Unlike the other actions (which transform the destination's existing value),
copyoverwrites the destination wholesale with the source value. This means it also works when the destination is empty (e.g. copyinggenreinto an emptydescriptor).Multi-value behavior:
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
patternfilters which source values are copied (not destination values). E.g.descriptor:^Pop$/copy:genrecopies only thegenrevalues equal toPop. The nestedsedfollows standalone-sedconventions (;-splitting, empty-value removal, dedup for multi-value destinations).Valid source tags. Any concrete
Tagmay be a source, including non-modifiable ones liketracktotal, since the source is only ever read.Changes
rule_parser.py:CopyActiondataclass; added to theAction.behaviorunion everywhere;__str__serialization (round-trips:str(Action.parse(x)) == x);"copy"added tovalid_actions; argument parsing incl. nested sed;COPYABLE_TAGS; multi→single shape validation.rules.py:_resolve_copy_source_value;copy_sourcethreaded throughexecute_single_action/execute_multi_value_action; copy branches in both.__init__.py: exportCopyAction.docs/METADATA_TOOLS.md: documents the action, semantics, multi-value rules, and grammar.__str__round-trip (incl. sed + escaping) and error cases inrule_parser_test.py; execution (single→single, single→multi, multi→multi, into-empty, copy+sed, pattern filtering, datafile source) inrules_test.py.Verification
mypy,ruff format --check,ruff check, andprettier --checkpass on the changed files; therule_parserandrulestest suites pass (69 tests). Pre-existing failures in unrelated suites (cache/releases/virtualfs/cli) are environmental (SQLite/FUSE) and reproduce onmasterwithout these changes.Link to Devin session: https://app.devin.ai/sessions/5b13e52d65c0400487a716d901805117
Requested by: @azuline