Skip to content

Commit 623c200

Browse files
refactor: Reorganize into Subpackages and Enhance Parameter Parsing (#163)
- Split codebase into logical subpackages (markdown, selectors, taskparams, taskparser, tokencount, mcp) - Add comprehensive parameter parsing with support for quoted values, escape sequences (Unicode, hex, octal), and multiple values per key - Change Params type from map[string]string to map[string][]string - Move options to separate file - Update documentation and tests
1 parent 8c5685a commit 623c200

37 files changed

+2538
-912
lines changed

docs/reference/cli.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,26 @@ file:///path/to/local/rules
135135

136136
Define a parameter for substitution in task prompts. Variables in task files using `${key}` syntax will be replaced with the specified value.
137137

138+
**Parameter Parsing Features:**
139+
140+
The `-p` flag supports flexible parameter parsing with the following features:
141+
142+
- **Basic key-value pairs**: `key=value`
143+
- **Multiple values per key**: Duplicate keys are collected into a list (e.g., `-p tag=frontend -p tag=backend` results in `tag` having both values)
144+
- **Quoted values**: Use single (`'`) or double (`"`) quotes for values containing spaces or special characters
145+
- `-p description="Application crashes on startup"`
146+
- `-p name='John Doe'`
147+
- **Escape sequences**: Supported in both quoted and unquoted values
148+
- Standard: `\n` (newline), `\t` (tab), `\r` (carriage return), `\\` (backslash)
149+
- Quotes: `\"` (double quote), `\'` (single quote)
150+
- Unicode: `\uXXXX` where XXXX are four hexadecimal digits
151+
- Hex: `\xHH` where HH are two hexadecimal digits
152+
- Octal: `\OOO` where OOO are up to three octal digits
153+
- **Case-insensitive keys**: Keys are automatically converted to lowercase
154+
- **UTF-8 support**: Full Unicode support in keys and values
155+
- **Flexible separators**: Multiple `-p` flags can be used, or a single flag can contain comma or whitespace-separated pairs
156+
- **Empty values**: Unquoted empty values (`key=`) result in empty parameter, quoted empty values (`key=""`) result in empty string
157+
138158
**Examples:**
139159
```bash
140160
# Single parameter

go.mod

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
module github.com/kitproj/coding-context-cli
22

3-
go 1.24.4
3+
go 1.24.5
44

55
require (
66
github.com/alecthomas/participle/v2 v2.1.4
77
github.com/goccy/go-yaml v1.18.0
88
github.com/hashicorp/go-getter/v2 v2.2.3
9+
github.com/stretchr/testify v1.10.0
910
)
1011

1112
require (
1213
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
14+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1315
github.com/hashicorp/errwrap v1.0.0 // indirect
1416
github.com/hashicorp/go-cleanhttp v0.5.0 // indirect
1517
github.com/hashicorp/go-multierror v1.1.0 // indirect
1618
github.com/hashicorp/go-safetemp v1.0.0 // indirect
1719
github.com/hashicorp/go-version v1.1.0 // indirect
18-
github.com/klauspost/compress v1.11.2 // indirect
20+
github.com/klauspost/compress v1.15.0 // indirect
1921
github.com/mitchellh/go-homedir v1.0.0 // indirect
2022
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
23+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
2124
github.com/ulikunitz/xz v0.5.8 // indirect
25+
gopkg.in/yaml.v3 v3.0.1 // indirect
2226
)

go.sum

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc
66
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
77
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
88
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
9+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
10+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
911
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
1012
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
1113
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
@@ -24,11 +26,19 @@ github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PF
2426
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
2527
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
2628
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
27-
github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ=
28-
github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
29+
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
30+
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
2931
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
3032
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
3133
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
3234
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
35+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
36+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
37+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
38+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
3339
github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
3440
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
41+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
42+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
43+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
44+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313

1414
yaml "github.com/goccy/go-yaml"
1515
"github.com/kitproj/coding-context-cli/pkg/codingcontext"
16+
"github.com/kitproj/coding-context-cli/pkg/codingcontext/selectors"
17+
"github.com/kitproj/coding-context-cli/pkg/codingcontext/taskparser"
1618
)
1719

1820
func main() {
@@ -25,8 +27,8 @@ func main() {
2527
var resume bool
2628
var writeRules bool
2729
var agent codingcontext.Agent
28-
params := make(codingcontext.Params)
29-
includes := make(codingcontext.Selectors)
30+
params := make(taskparser.Params)
31+
includes := make(selectors.Selectors)
3032
var searchPaths []string
3133
var manifestURL string
3234

pkg/codingcontext/README.md

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,16 @@ import (
2222
"os"
2323

2424
"github.com/kitproj/coding-context-cli/pkg/codingcontext"
25+
"github.com/kitproj/coding-context-cli/pkg/codingcontext/taskparams"
2526
)
2627

2728
func main() {
2829
// Create a new context with options
2930
ctx := codingcontext.New(
3031
codingcontext.WithSearchPaths("file://.", "file://"+os.Getenv("HOME")),
31-
codingcontext.WithParams(codingcontext.Params{
32-
"issue_number": "123",
33-
"feature": "authentication",
32+
codingcontext.WithParams(taskparams.Params{
33+
"issue_number": []string{"123"},
34+
"feature": []string{"authentication"},
3435
}),
3536
codingcontext.WithLogger(slog.New(slog.NewTextHandler(os.Stderr, nil))),
3637
)
@@ -63,24 +64,26 @@ import (
6364
"os"
6465

6566
"github.com/kitproj/coding-context-cli/pkg/codingcontext"
67+
"github.com/kitproj/coding-context-cli/pkg/codingcontext/selectors"
68+
"github.com/kitproj/coding-context-cli/pkg/codingcontext/taskparams"
6669
)
6770

6871
func main() {
6972
// Create selectors for filtering rules
70-
selectors := make(codingcontext.Selectors)
71-
selectors.SetValue("language", "go")
72-
selectors.SetValue("stage", "implementation")
73+
sel := make(selectors.Selectors)
74+
sel.SetValue("language", "go")
75+
sel.SetValue("stage", "implementation")
7376

7477
// Create context with all options
7578
ctx := codingcontext.New(
7679
codingcontext.WithSearchPaths(
7780
"file://.",
7881
"git::https://github.com/org/repo//path/to/rules",
7982
),
80-
codingcontext.WithParams(codingcontext.Params{
81-
"issue_number": "123",
83+
codingcontext.WithParams(taskparams.Params{
84+
"issue_number": []string{"123"},
8285
}),
83-
codingcontext.WithSelectors(selectors),
86+
codingcontext.WithSelectors(sel),
8487
codingcontext.WithAgent(codingcontext.AgentCursor),
8588
codingcontext.WithResume(false),
8689
codingcontext.WithUserPrompt("Additional context or instructions"),
@@ -232,11 +235,14 @@ Type representing MCP transport protocol (string type):
232235

233236
#### `Params`
234237

235-
Map of parameter key-value pairs for template substitution: `map[string]string`
238+
Map of parameter key-value pairs for template substitution: `map[string][]string`
236239

237240
**Methods:**
238241
- `String() string` - Returns string representation
239242
- `Set(value string) error` - Parses and sets key=value pair (implements flag.Value)
243+
- `Value(key string) string` - Returns the first value for the given key
244+
- `Lookup(key string) (string, bool)` - Returns the first value and whether the key exists
245+
- `Values(key string) []string` - Returns all values for the given key
240246

241247
#### `Selectors`
242248

@@ -262,7 +268,7 @@ Types for parsing task content with slash commands:
262268
- `Argument` - Slash command argument (can be positional or named key=value)
263269

264270
**Methods:**
265-
- `(*SlashCommand) Params() map[string]string` - Returns parsed parameters as map
271+
- `(*SlashCommand) Params() taskparams.Params` - Returns parsed parameters as map
266272
- `(*Text) Content() string` - Returns text content as string
267273
- Various `String()` methods for formatting each type
268274

@@ -284,8 +290,8 @@ Creates a new Context with the given options.
284290

285291
**Options:**
286292
- `WithSearchPaths(paths ...string)` - Add search paths (supports go-getter URLs)
287-
- `WithParams(params Params)` - Set parameters for substitution
288-
- `WithSelectors(selectors Selectors)` - Set selectors for filtering rules
293+
- `WithParams(params taskparams.Params)` - Set parameters for substitution (import `taskparams` package)
294+
- `WithSelectors(selectors selectors.Selectors)` - Set selectors for filtering rules (import `selectors` package)
289295
- `WithAgent(agent Agent)` - Set target agent (excludes that agent's own rules)
290296
- `WithResume(resume bool)` - Enable resume mode (skips rules)
291297
- `WithUserPrompt(userPrompt string)` - Set user prompt to append to task
@@ -304,23 +310,25 @@ Parses a markdown file into frontmatter and content. Generic function that works
304310

305311
Parses task text content into blocks of text and slash commands.
306312

307-
#### `ParseParams(s string) (Params, error)`
313+
#### `taskparams.Parse(s string) (taskparams.Params, error)`
308314

309315
Parses a string containing key=value pairs with quoted values.
310316

311317
**Examples:**
312318
```go
319+
import "github.com/kitproj/coding-context-cli/pkg/codingcontext/taskparams"
320+
313321
// Parse quoted key-value pairs
314-
params, _ := ParseParams(`key1="value1" key2="value2"`)
315-
// Result: map[string]string{"key1": "value1", "key2": "value2"}
322+
params, _ := taskparams.Parse(`key1="value1" key2="value2"`)
323+
// Result: taskparams.Params{"key1": []string{"value1"}, "key2": []string{"value2"}}
316324

317325
// Parse with spaces in values
318-
params, _ := ParseParams(`key1="value with spaces" key2="value2"`)
319-
// Result: map[string]string{"key1": "value with spaces", "key2": "value2"}
326+
params, _ := taskparams.Parse(`key1="value with spaces" key2="value2"`)
327+
// Result: taskparams.Params{"key1": []string{"value with spaces"}, "key2": []string{"value2"}}
320328

321329
// Parse with escaped quotes
322-
params, _ := ParseParams(`key1="value with \"escaped\" quotes"`)
323-
// Result: map[string]string{"key1": "value with \"escaped\" quotes"}
330+
params, _ := taskparams.Parse(`key1="value with \"escaped\" quotes"`)
331+
// Result: taskparams.Params{"key1": []string{"value with \"escaped\" quotes"}}
324332
```
325333

326334
#### `ParseAgent(s string) (Agent, error)`

pkg/codingcontext/command_frontmatter.go

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)