Skip to content

Render Swift collections as AppleScript lists and records - #6

Merged
fredsimard merged 3 commits into
mainfrom
feature/swift-collections-to-applescript
Aug 19, 2026
Merged

fredsimard merged 3 commits into
mainfrom
feature/swift-collections-to-applescript

Conversation

@fredsimard

Copy link
Copy Markdown
Owner

What this changes

Swift collections are rendered as their AppleScript equivalents on substitution: an Array becomes a list, a Dictionary becomes a record, and the two nest, so an [[String: Any]] arrives as a list of records. Elements go back through substitution(for:), so escaping and literal rendering stay in one place. Fixes #1.

Tuples were left out of scope: they need Mirror-based reflection to iterate, which would be the only reflection in the package, and arrays and dictionaries cover the real call sites. Worth a separate issue if anyone asks for them.

Why

substitution(for:) handled String, Int, Double, Bool and AppleScriptRawValue; anything else fell through to String(describing:), so a Swift array reached the script as the text ["a", "b"]. Passing a list meant hand-building AppleScript source, wrapping it in AppleScriptRawValue, and taking on escaping every element yourself — the one place the package handed back a job it should have been doing.

Notes for the reviewer

Text quoting depends on position. A placeholder is written inside the script's own quotes ("$key"), so a substituted string must not add a second pair; an element inside {…} has no quotes around it and must supply its own. Hence the quotingText parameter on substitution(for:), set only by list(for:) and record(for:).

Record keys. AppleScript record keys are identifiers, not strings. A key that reads as a plain identifier is left bare, so a term the target application defines keeps its meaning — {name:"Roger"} sets the app's name property, while {|name|:"Roger"} would define an unrelated one. Keys with spaces or punctuation, and AppleScript's reserved words, are vertical-bar quoted, with \ and | escaped inside. Both escapes were verified against osascript; reserved words were verified to be rejected bare.

Behaviour change, no API change. Nothing stops compiling on the Swift side, but a script written against the old String(describing:) text has to be updated. One sharp edge, called out in the changelog: a collection in a quoted placeholder now renders "{"a", "b"}", an AppleScript syntax error rather than silently wrong text — loud, and at compile time. Set and Data still take the old describe-and-escape path, so nothing beyond arrays and dictionaries shifted.

Ordering. Dictionary keys are sorted on the key as written, so quoting doesn't reorder anything and the same dictionary always renders the same source.

How it was tested

swift build and swift test pass — 30 tests, six new unit tests for lists, records, nesting, empty collections, key quoting and bridged dictionaries. One of the new tests runs a generated list of records through osascript and reads name and |first name| back out of it, so the rendering is proven to compile rather than just to match a string. Examples/AppleScripts.swift was built as a consumer package the way CI does it.

SwiftLint could not run locally (sourcekitdInProc fails to load on this machine), so the strict lint job is unverified here.

  • macOS version: 26.6.1
  • Tested against application: none — the new code is pure substitution; execution was exercised through osascript only.

Checklist

  • swift build and swift test pass.
  • New or changed public API has a /// doc comment describing purpose, parameters and return value.
  • Docs updated if behaviour, parameters or return values changed.
  • Tests added for anything deterministic (escaping, substitution, parsing).
  • No breaking change to public API — or it's called out here, for a major release.
  • Package messages stay in English.

🤖 Generated with Claude Code

fredsimard and others added 2 commits August 19, 2026 00:00
An Array or Dictionary passed as a variable used to fall through to String(describing:) and reach the script as text such as ["a", "b"], which left callers hand-building AppleScript source and wrapping it in AppleScriptRawValue — taking on escaping every element themselves.

Arrays now render as lists and dictionaries as records, with elements going back through substitution(for:) so escaping and literal rendering stay in one place and nesting works. Text supplies its own quotes inside a collection, since the script provides none there, unlike around a placeholder.

Record keys that are not plain AppleScript identifiers, reserved words included, are vertical-bar quoted; identifiers are left bare so a term the target application defines keeps its meaning. Keys are sorted so the same dictionary always renders the same source.

Closes #1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Also drops the README roadmap line pointing at #1, now that it ships.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@fredsimard fredsimard self-assigned this Aug 19, 2026
@fredsimard fredsimard added documentation Improvements or additions to documentation enhancement New feature or request labels Aug 19, 2026
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@fredsimard
fredsimard merged commit 48de61a into main Aug 19, 2026
3 checks passed
@fredsimard
fredsimard deleted the feature/swift-collections-to-applescript branch August 19, 2026 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert Swift collections to their AppleScript equivalents

1 participant