Skip to content

Commit 105a2d6

Browse files
committed
Refactor flag processing into shared utilities
Introduce `consumeFlagArg`, `matchFlagCategory`, and `consumeFusedFlag` in `handler.go` to eliminate repetitive flag-parsing logic across all command handlers. This significantly reduces boilerplate and ensures consistent handling of subshell preservation and `--flag=value` syntax.
1 parent b3b965a commit 105a2d6

116 files changed

Lines changed: 609 additions & 2802 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"allow": [
44
"Read(//usr/local/go/bin/**)",
55
"Read(//opt/homebrew/bin/**)",
6-
"mcp__fff__find_files"
6+
"mcp__fff__find_files",
7+
"mcp__fff__grep",
8+
"mcp__fff__multi_grep"
79
]
810
}
911
}

.claude/skills/add-handler/SKILL.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ Key rules:
126126
- Use `isFlagToken(tok)` to distinguish flags from positionals
127127
- Use `classifyToken(tok)` for positionals that should get generic classification (usually file paths)
128128
- Walk tokens with an index variable (`i`), not `range`, when flags consume next args
129+
- Use the shared utilities from `handler.go` for flag processing (read the file to see what's available):
130+
- `consumeFlagArg(tok, args, i, result, "<placeholder>")` to consume a flag and its next token (handles subshell preservation automatically)
131+
- `flagCategory` + `matchFlagCategory` to replace repetitive if/else chains when you have 3+ flag categories
132+
- `consumeFusedFlag(tok, categories)` to handle `--flag=value` syntax
133+
- When a handler has 3+ flag categories (e.g. pathFlags, valFlags, numericFlags), define a `categories` slice and use `matchFlagCategory` in the loop instead of sequential if blocks
129134

130135
Reference `handler_grep.go` for a handler with flag-consuming arguments, or `handler_echo.go` for a simple positional-collapsing handler.
131136

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ jobs:
99
check:
1010
runs-on: ubuntu-latest
1111
steps:
12-
- uses: actions/checkout@v4
12+
- uses: actions/checkout@v5
1313

14-
- uses: actions/setup-go@v5
14+
- uses: actions/setup-go@v6
1515
with:
1616
go-version-file: go.mod
1717
cache: false

handler.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,61 @@
11
package shellshape
22

3+
import "strings"
4+
35
// handlerFunc processes tokens after the executable name.
46
// subcommand is the subcommand token (empty if none).
57
type handlerFunc func(subcommand string, tokens []string) []string
68

9+
// flagCategory pairs a set of flag names with the placeholder to use for their values.
10+
type flagCategory struct {
11+
flags map[string]bool
12+
placeholder string
13+
}
14+
15+
// consumeFlagArg appends a flag and its next-token value (collapsed to placeholder)
16+
// to result. Subshell tokens are preserved verbatim.
17+
func consumeFlagArg(tok string, args []string, i int, result []string, placeholder string) ([]string, int) {
18+
result = append(result, tok)
19+
i++
20+
if i < len(args) {
21+
if isSubshellToken(args[i]) {
22+
result = append(result, args[i])
23+
} else {
24+
result = append(result, placeholder)
25+
}
26+
i++
27+
}
28+
return result, i
29+
}
30+
31+
// matchFlagCategory checks tok against a list of flag categories and returns
32+
// the matching placeholder. Returns ("", false) if no category matches.
33+
func matchFlagCategory(tok string, categories []flagCategory) (string, bool) {
34+
for _, cat := range categories {
35+
if cat.flags[tok] {
36+
return cat.placeholder, true
37+
}
38+
}
39+
return "", false
40+
}
41+
42+
// consumeFusedFlag handles --flag=value syntax against a list of flag categories.
43+
// Returns the normalized "flag=<placeholder>" string and true if matched,
44+
// or ("", false) if the token doesn't contain '=' or doesn't match any category.
45+
func consumeFusedFlag(tok string, categories []flagCategory) (string, bool) {
46+
eqIdx := strings.IndexByte(tok, '=')
47+
if eqIdx < 0 {
48+
return "", false
49+
}
50+
key := tok[:eqIdx]
51+
for _, cat := range categories {
52+
if cat.flags[key] {
53+
return key + "=" + cat.placeholder, true
54+
}
55+
}
56+
return "", false
57+
}
58+
759
var redirectConsumeNext = map[string]bool{
860
">": true, ">>": true, "<": true,
961
"&>": true, "&>>": true,

handler_apt.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,7 @@ func handleApt(subcommand string, tokens []string) []string {
4848
}
4949

5050
if valFlags[tok] {
51-
result = append(result, tok)
52-
i++
53-
if i < len(args) {
54-
if isSubshellToken(args[i]) {
55-
result = append(result, args[i])
56-
} else {
57-
result = append(result, "<val>")
58-
}
59-
i++
60-
}
51+
result, i = consumeFlagArg(tok, args, i, result, "<val>")
6152
continue
6253
}
6354

handler_at.go

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ func handleAt(subcommand string, tokens []string) []string {
2020
timeArgFlags := map[string]bool{"-t": true}
2121
jobFlags := map[string]bool{"-c": true}
2222

23+
categories := []flagCategory{
24+
{fileFlags, "<path>"},
25+
{queueFlags, "<queue>"},
26+
{timeArgFlags, "<time>"},
27+
{jobFlags, "<job-id>"},
28+
}
29+
2330
deleteMode := false
2431

2532
var result []string
@@ -36,43 +43,8 @@ func handleAt(subcommand string, tokens []string) []string {
3643
continue
3744
}
3845

39-
if fileFlags[tok] {
40-
result = append(result, tok)
41-
i++
42-
if i < len(args) {
43-
result = append(result, "<path>")
44-
i++
45-
}
46-
continue
47-
}
48-
49-
if queueFlags[tok] {
50-
result = append(result, tok)
51-
i++
52-
if i < len(args) {
53-
result = append(result, "<queue>")
54-
i++
55-
}
56-
continue
57-
}
58-
59-
if timeArgFlags[tok] {
60-
result = append(result, tok)
61-
i++
62-
if i < len(args) {
63-
result = append(result, "<time>")
64-
i++
65-
}
66-
continue
67-
}
68-
69-
if jobFlags[tok] {
70-
result = append(result, tok)
71-
i++
72-
if i < len(args) {
73-
result = append(result, "<job-id>")
74-
i++
75-
}
46+
if placeholder, ok := matchFlagCategory(tok, categories); ok {
47+
result, i = consumeFlagArg(tok, args, i, result, placeholder)
7648
continue
7749
}
7850

@@ -119,12 +91,7 @@ func handleAtq(subcommand string, tokens []string) []string {
11991
tok := args[i]
12092

12193
if tok == "-q" {
122-
result = append(result, tok)
123-
i++
124-
if i < len(args) {
125-
result = append(result, "<queue>")
126-
i++
127-
}
94+
result, i = consumeFlagArg(tok, args, i, result, "<queue>")
12895
continue
12996
}
13097

handler_awk.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,12 @@ func handleAwk(subcommand string, tokens []string) []string {
4343
}
4444

4545
if fsFlags[tok] {
46-
result = append(result, tok)
47-
i++
48-
if i < len(args) {
49-
result = append(result, "<val>")
50-
i++
51-
}
46+
result, i = consumeFlagArg(tok, args, i, result, "<val>")
5247
continue
5348
}
5449

5550
if varFlags[tok] {
56-
result = append(result, tok)
57-
i++
58-
if i < len(args) {
59-
result = append(result, "<val>")
60-
i++
61-
}
51+
result, i = consumeFlagArg(tok, args, i, result, "<val>")
6252
continue
6353
}
6454

handler_aws.go

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ func handleAws(subcommand string, tokens []string) []string {
5757
"--ca-bundle": true,
5858
}
5959

60+
categories := []flagCategory{
61+
{queryFlags, "<query>"},
62+
{numericFlags, "N"},
63+
{pathFlags, "<path>"},
64+
}
65+
6066
var result []string
6167
subcmdAssigned := false
6268

@@ -85,58 +91,14 @@ func handleAws(subcommand string, tokens []string) []string {
8591
result = append(result, tok)
8692
i++
8793
if i < len(args) {
88-
if isSubshellToken(args[i]) {
89-
result = append(result, args[i])
90-
} else {
91-
result = append(result, args[i])
92-
}
93-
i++
94-
}
95-
continue
96-
}
97-
98-
// Query flags: collapse value to <query>.
99-
if queryFlags[tok] {
100-
result = append(result, tok)
101-
i++
102-
if i < len(args) {
103-
if isSubshellToken(args[i]) {
104-
result = append(result, args[i])
105-
} else {
106-
result = append(result, "<query>")
107-
}
94+
result = append(result, args[i])
10895
i++
10996
}
11097
continue
11198
}
11299

113-
// Numeric flags: collapse value to N.
114-
if numericFlags[tok] {
115-
result = append(result, tok)
116-
i++
117-
if i < len(args) {
118-
if isSubshellToken(args[i]) {
119-
result = append(result, args[i])
120-
} else {
121-
result = append(result, "N")
122-
}
123-
i++
124-
}
125-
continue
126-
}
127-
128-
// Path flags: collapse value to <path>.
129-
if pathFlags[tok] {
130-
result = append(result, tok)
131-
i++
132-
if i < len(args) {
133-
if isSubshellToken(args[i]) {
134-
result = append(result, args[i])
135-
} else {
136-
result = append(result, "<path>")
137-
}
138-
i++
139-
}
100+
if placeholder, ok := matchFlagCategory(tok, categories); ok {
101+
result, i = consumeFlagArg(tok, args, i, result, placeholder)
140102
continue
141103
}
142104

handler_base64.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,12 @@ func handleBase64(subcommand string, tokens []string) []string {
3535
}
3636

3737
if pathFlags[tok] {
38-
result = append(result, tok)
39-
i++
40-
if i < len(args) {
41-
result = append(result, "<path>")
42-
i++
43-
}
38+
result, i = consumeFlagArg(tok, args, i, result, "<path>")
4439
continue
4540
}
4641

4742
if numericFlags[tok] {
48-
result = append(result, tok)
49-
i++
50-
if i < len(args) {
51-
result = append(result, "N")
52-
i++
53-
}
43+
result, i = consumeFlagArg(tok, args, i, result, "N")
5444
continue
5545
}
5646

handler_biome.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,7 @@ func handleBiome(subcommand string, tokens []string) []string {
3333
}
3434

3535
if valueFlags[tok] {
36-
result = append(result, tok)
37-
i++
38-
if i < len(args) {
39-
result = append(result, "<val>")
40-
i++
41-
}
36+
result, i = consumeFlagArg(tok, args, i, result, "<val>")
4237
continue
4338
}
4439

0 commit comments

Comments
 (0)