Skip to content

Commit a0c2ddf

Browse files
authored
[fix] Tokenize open shortcuts with shlex and return typed exit error (#169) (#188)
- Replace strings.Fields with shlex.Split in resolveShortcut so quoted args like 'sh -c "echo hi"' tokenize correctly instead of splitting on whitespace only - Replace os.Exit in RunE with return &output.SilentError so cobra lifecycle (deferred cleanup, post-hooks, test harness) is not bypassed - Add unit tests for quoted-arg and invalid-quoting cases - Add e2e test TestOpenQuotedShortcut verifying end-to-end behaviour
1 parent 1ac74c8 commit a0c2ddf

5 files changed

Lines changed: 39 additions & 2 deletions

File tree

cmd/open.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88
"sort"
99
"strings"
1010

11+
"github.com/google/shlex"
1112
"github.com/lugassawan/rimba/internal/config"
13+
"github.com/lugassawan/rimba/internal/output"
1214
"github.com/spf13/cobra"
1315
)
1416

@@ -74,7 +76,7 @@ Shortcuts are configured in .rimba/settings.toml:
7476
if err := sub.Run(); err != nil {
7577
var exitErr *exec.ExitError
7678
if errors.As(err, &exitErr) {
77-
os.Exit(exitErr.ExitCode())
79+
return &output.SilentError{ExitCode: exitErr.ExitCode()}
7880
}
7981
return fmt.Errorf("failed to run %q: %w", cmdArgs[0], err)
8082
}
@@ -138,7 +140,10 @@ func resolveShortcut(cmd *cobra.Command, name string) ([]string, error) {
138140
return nil, fmt.Errorf("shortcut %q not found in [open] config; available: %s", name, availableShortcuts(cfg.Open))
139141
}
140142

141-
parts := strings.Fields(value)
143+
parts, err := shlex.Split(value)
144+
if err != nil {
145+
return nil, fmt.Errorf("invalid shortcut %q in [open] config: %w\nTo fix: check quoting in .rimba/settings.toml", name, err)
146+
}
142147
if len(parts) == 0 {
143148
return nil, fmt.Errorf("shortcut %q has empty value in [open] config", name)
144149
}

cmd/open_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ func TestResolveOpenCommand(t *testing.T) {
8888
}
8989
cfgNoOpen := &config.Config{WorktreeDir: "../wt", DefaultSource: "main"}
9090
cfgEmpty := &config.Config{WorktreeDir: "../wt", DefaultSource: "main", Open: map[string]string{"empty": ""}}
91+
cfgQuoted := &config.Config{
92+
WorktreeDir: "../wt",
93+
DefaultSource: "main",
94+
Open: map[string]string{
95+
"wrap": `sh -c "echo hi"`,
96+
"bad": `echo "unterminated`,
97+
},
98+
}
9199

92100
tests := []struct {
93101
name string
@@ -134,6 +142,14 @@ func TestResolveOpenCommand(t *testing.T) {
134142
name: "inline args passed through",
135143
inlineArgs: []string{"pwd"}, wantArgs: []string{"pwd"},
136144
},
145+
{
146+
name: "quoted shortcut args tokenized correctly", cfg: cfgQuoted,
147+
withVal: "wrap", wantArgs: []string{"sh", "-c", "echo hi"},
148+
},
149+
{
150+
name: "invalid quoting in shortcut returns error", cfg: cfgQuoted,
151+
withVal: "bad", wantErr: "invalid shortcut",
152+
},
137153
}
138154

139155
for _, tt := range tests {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/lugassawan/rimba
33
go 1.25.7
44

55
require (
6+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
67
github.com/mark3labs/mcp-go v0.47.1
78
github.com/pelletier/go-toml/v2 v2.3.0
89
github.com/spf13/cobra v1.10.2

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
77
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
88
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
99
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
10+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
11+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
1012
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
1113
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1214
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=

tests/e2e/open_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,16 @@ func TestOpenShortcutExitCode(t *testing.T) {
193193
t.Error("expected non-zero exit code from shortcut running 'false'")
194194
}
195195
}
196+
197+
func TestOpenQuotedShortcut(t *testing.T) {
198+
if testing.Short() {
199+
t.Skip(skipE2E)
200+
}
201+
202+
repo := setupInitializedRepo(t)
203+
setupOpenShortcuts(t, repo, map[string]string{"wrap": `sh -c "echo hello world"`})
204+
rimbaSuccess(t, repo, "add", "open-quoted")
205+
206+
r := rimbaSuccess(t, repo, "open", "open-quoted", "-w", "wrap")
207+
assertContains(t, r.Stdout, "hello world")
208+
}

0 commit comments

Comments
 (0)