Skip to content

feat!: split godo and shell placeholder namespaces, quote substituted values - #2

Merged
MY-RV merged 1 commit into
mainfrom
feat/godo-namespace-and-quoting
Sep 19, 2026
Merged

MY-RV merged 1 commit into
mainfrom
feat/godo-namespace-and-quoting

Conversation

@MY-RV

@MY-RV MY-RV commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Summary

Two changes to how a godo.yaml reaches the shell, plus two smaller fixes found
on the way.

Namespace split. godo now claims only ${godo:…} inside a script body.
Everything else — ${HOME}, $$, any shell construct — is passed through
untouched. Captures are still declared ${NAME} in the matcher key and
written ${NAME} in @deps, because neither of those ever meets a shell; they
are consumed ${godo:argv[NAME]} in the body.

Space Where Syntax Meets a shell
godo matcher keys, @deps ${NAME} never
shell script bodies ${godo:…} always

This removes the $${…} escape and the "unknown capture" failure for bare
${…}: both existed only to rescue host environment variables from being read
as captures, and there is nothing left to rescue them from.

Quoting. Substituted values are shell-quoted, so an argument arrives as one
argument whatever it contains. Ordinary tokens (flags, paths) stay unquoted so
previews stay readable — godo test -v ./... still previews as
go test -v ./.... ${godo:…:raw} opts out per placeholder.

Also fixed

  • @deps forms a DAG: a shared dependency runs once, at its first
    (deepest-first) position, instead of once per path. Keyed on the expanded
    invocation, so the same script reached with different captures still runs once
    per capture set.
  • @deps entries resolve to tokens instead of to a line that is then re-split,
    so a capture holding a space no longer fragments the invocation.
  • Dialect.Match takes a single Script. The engine already walked the catalog
    in definition order and passed one script; the slice parameter made both
    implementations carry a loop over a one-element slice.

Before / after

Measured against the pre-change binary:

before after
godo greet 'a; touch PWNED' creates the file does not
echo ${HOME} in a body unknown capture /home/you
diamond in @deps leaf mid leaf root leaf mid root
capture with a space in @deps fragments stays one token

Breaking

${NAME} in a body must become ${godo:argv[NAME]}.

This does not fail loudly. A body left unedited expands to nothing in the
shell rather than erroring. There is no automated migration check — a load-time
lint (matcher key declares ${MODULE}, body mentions it bare) would catch it
and is not in this PR.

Scripts relying on an argument carrying a glob or a shell operator need
${godo:…:raw}.

Not covered

The Windows quoting path is untested. CI runs ubuntu-latest only and the
three execshell runner tests skip on Windows, so quoteWindows does not
execute anywhere — while release builds ship windows/amd64. This predates the
PR, but the PR adds code to that blind spot.

cmd.exe also expands %VAR% before a command sees its arguments, and no
command-line quoting suppresses it, so a value containing % is not safe on
Windows. Documented in docs/contract.md.

Follow-ups, not in this PR: QuoteFor(goos, s) to make the Windows path
testable from Linux, windows-latest in the CI matrix, and the choice between
cmd.exe and PowerShell.

Checklist

  • Behavior change? Update docs/contract.md in the same PR
  • Tests cover the change (happy path + edge)
  • ./godo ci passes locally
  • No drive-by refactors

… values

Bodies are shell space: godo claims only ${godo:…} there and passes every
other ${…} to the host shell untouched. A matcher capture stays declared
${NAME} in the key and written ${NAME} in @deps — neither ever meets a
shell — and is consumed ${godo:argv[NAME]} in the body. Collision between
catalog syntax and shell syntax is no longer resolved; it cannot occur.

Values godo substitutes are now shell-quoted: one argument in is one
argument out, whatever it contains. ${godo:…:raw} opts a single
placeholder back into verbatim interpolation for globs and deliberate
shell constructs.

Also: @deps forms a DAG, so a shared dependency runs once rather than
once per path; @deps entries resolve to tokens instead of to a line that
is re-split, so a capture holding a space no longer fragments the
invocation; Dialect.Match takes a single Script, dropping the dead loop
every implementation carried.

The Windows quoting path is untested — CI runs Linux only and the runner
tests skip on Windows. cmd.exe expands %VAR% before a command sees its
arguments, which no command-line quoting suppresses.

BREAKING CHANGE: ${NAME} in a script body must become ${godo:argv[NAME]}.
A body left unedited does not error: the shell receives the braces and
expands them to nothing. Scripts relying on an argument carrying a glob
or a shell operator need ${godo:…:raw}.
@MY-RV MY-RV self-assigned this Sep 18, 2026
@MY-RV
MY-RV merged commit 77b91e1 into main Sep 19, 2026
2 checks passed
@MY-RV
MY-RV deleted the feat/godo-namespace-and-quoting branch September 19, 2026 22:07
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