Skip to content

Commit dacc321

Browse files
committed
✨ Allow jsonpath when print json or yaml
1 parent 4292aeb commit dacc321

File tree

6 files changed

+137
-37
lines changed

6 files changed

+137
-37
lines changed

MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use_repo(
1919
go_deps,
2020
"com_github_jedib0t_go_pretty_v6",
2121
"com_github_mark3labs_mcp_go",
22+
"com_github_ohler55_ojg",
2223
"com_github_savioxavier_termlink",
2324
"com_github_spf13_cobra",
2425
"com_github_spf13_pflag",

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/eat-pray-ai/yutu
33
go 1.24.3
44

55
require (
6-
github.com/mark3labs/mcp-go v0.32.0
6+
github.com/mark3labs/mcp-go v0.32.1-0.20250628092910-1eddde7bd69b
77
github.com/savioxavier/termlink v1.4.3
88
github.com/spf13/cobra v1.9.1
99
github.com/spf13/viper v1.20.1
@@ -40,6 +40,7 @@ require (
4040
github.com/fsnotify/fsnotify v1.9.0 // indirect
4141
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4242
github.com/jedib0t/go-pretty/v6 v6.6.7
43+
github.com/ohler55/ojg v1.26.8
4344
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
4445
github.com/sagikazarmark/locafero v0.9.0 // indirect
4546
github.com/sourcegraph/conc v0.3.0 // indirect

go.sum

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
1818
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
1919
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
2020
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
21-
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
22-
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
2321
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
2422
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
2523
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -42,10 +40,12 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
4240
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
4341
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
4442
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
45-
github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8=
46-
github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
43+
github.com/mark3labs/mcp-go v0.32.1-0.20250628092910-1eddde7bd69b h1:nxi80AOhzZBM8ZiP9PAP26MJOT6WbEpkut8fmcOFPuo=
44+
github.com/mark3labs/mcp-go v0.32.1-0.20250628092910-1eddde7bd69b/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
4745
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
4846
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
47+
github.com/ohler55/ojg v1.26.8 h1:njM65m+ej8sLHiFZIhJK9UkwOmDPsUikjGbTgcwu8CU=
48+
github.com/ohler55/ojg v1.26.8/go.mod h1:/Y5dGWkekv9ocnUixuETqiL58f+5pAsUfg5P8e7Pa2o=
4949
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
5050
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
5151
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -58,8 +58,6 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
5858
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
5959
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
6060
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
61-
github.com/savioxavier/termlink v1.4.2 h1:PRlvcStluuSKA87KCIqzODknYeQ3XEcgJP6DvAAVl1c=
62-
github.com/savioxavier/termlink v1.4.2/go.mod h1:5T5ePUlWbxCHIwyF8/Ez1qufOoGM89RCg9NvG+3G3gc=
6361
github.com/savioxavier/termlink v1.4.3 h1:Gh6vrG7jSn21cRiYdQqFXYcdXfM+Fg14aG487JTfKpA=
6462
github.com/savioxavier/termlink v1.4.3/go.mod h1:5T5ePUlWbxCHIwyF8/Ez1qufOoGM89RCg9NvG+3G3gc=
6563
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
@@ -82,26 +80,16 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI
8280
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
8381
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
8482
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
85-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
86-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
8783
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
8884
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
89-
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
90-
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
9185
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
9286
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
93-
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
94-
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
9587
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
9688
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
97-
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
98-
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
9989
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
100-
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
101-
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
90+
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
10291
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
103-
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
104-
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
92+
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
10593
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
10694
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
10795
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -118,8 +106,6 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
118106
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
119107
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
120108
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
121-
google.golang.org/api v0.238.0 h1:+EldkglWIg/pWjkq97sd+XxH7PxakNYoe/rkSTbnvOs=
122-
google.golang.org/api v0.238.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
123109
google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo=
124110
google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
125111
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=

pkg/utils/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ go_library(
66
importpath = "github.com/eat-pray-ai/yutu/pkg/utils",
77
visibility = ["//visibility:public"],
88
deps = [
9+
"@com_github_ohler55_ojg//jp",
910
"@com_github_spf13_pflag//:pflag",
1011
"@in_gopkg_yaml_v3//:yaml_v3",
1112
],

pkg/utils/utils.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,35 @@ import (
55
"encoding/base64"
66
"encoding/json"
77
"fmt"
8+
"github.com/ohler55/ojg/jp"
89
"github.com/spf13/pflag"
910
"io"
10-
"os"
1111
"os/exec"
1212
"path/filepath"
1313
"runtime"
1414

1515
"gopkg.in/yaml.v3"
1616
)
1717

18-
func PrintJSON(data interface{}, writer io.Writer) {
19-
if writer == nil { // todo: remove fallback to stdout
20-
writer = os.Stdout
18+
func PrintJSON(data interface{}, jpath string, writer io.Writer) {
19+
j, err := jp.ParseString(jpath)
20+
if err != nil && jpath != "" {
21+
_, _ = fmt.Fprintln(writer, "Invalid JSONPath:", jpath)
22+
return
23+
} else if jpath != "" {
24+
data = j.Get(data)
2125
}
2226
marshalled, _ := json.MarshalIndent(data, "", " ")
2327
_, _ = fmt.Fprintln(writer, string(marshalled))
2428
}
2529

26-
func PrintYAML(data interface{}, writer io.Writer) {
27-
if writer == nil { // todo: remove fallback to stdout
28-
writer = os.Stdout
30+
func PrintYAML(data interface{}, jpath string, writer io.Writer) {
31+
j, err := jp.ParseString(jpath)
32+
if err != nil && jpath != "" {
33+
_, _ = fmt.Fprintln(writer, "Invalid JSONPath:", jpath)
34+
return
35+
} else if jpath != "" {
36+
data = j.Get(data)
2937
}
3038
marshalled, _ := yaml.Marshal(data)
3139
_, _ = fmt.Fprintln(writer, string(marshalled))

pkg/utils/utils_test.go

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package utils
22

33
import (
44
"bytes"
5-
"fmt"
65
"github.com/spf13/cobra"
76
"github.com/spf13/pflag"
87
"reflect"
@@ -115,24 +114,76 @@ func TestIsJson(t *testing.T) {
115114

116115
func TestPrintJSON(t *testing.T) {
117116
type args struct {
118-
data interface{}
117+
data interface{}
118+
jpath string
119119
}
120120
tests := []struct {
121121
name string
122122
args args
123123
wantWriter string
124124
}{
125+
{
126+
name: "empty jsonpath",
127+
args: args{data: map[string]string{"key": "value"}, jpath: ""},
128+
wantWriter: "{\n \"key\": \"value\"\n}\n",
129+
},
125130
{
126131
name: "simple json",
127-
args: args{data: map[string]string{"key": "value"}},
128-
wantWriter: fmt.Sprint("{\n \"key\": \"value\"\n}\n"),
132+
args: args{data: map[string]string{"key": "value"}, jpath: "$"},
133+
wantWriter: "[\n {\n \"key\": \"value\"\n }\n]\n",
134+
},
135+
{
136+
name: "invalid jsonpath",
137+
args: args{
138+
data: map[string]string{"key": "value"}, jpath: "//",
139+
},
140+
wantWriter: "Invalid JSONPath: //\n",
141+
},
142+
{
143+
name: "extract specific field",
144+
args: args{
145+
data: map[string]interface{}{
146+
"key": "value",
147+
"another": "field",
148+
}, jpath: "$.key",
149+
},
150+
wantWriter: "[\n \"value\"\n]\n",
151+
},
152+
{
153+
name: "nested jsonpath",
154+
args: args{
155+
data: map[string]interface{}{
156+
"item1": map[string]string{"key1": "value1"},
157+
"item2": map[string]string{"key2": "value2"},
158+
"count": 2,
159+
},
160+
jpath: "$.*.key1",
161+
},
162+
wantWriter: "[\n \"value1\"\n]\n",
163+
},
164+
{
165+
name: "array jsonpath",
166+
args: args{
167+
data: []map[string]string{
168+
{"key1": "value1"},
169+
{"key2": "value2"},
170+
{"key1": "value3"},
171+
},
172+
jpath: "$[*].key1",
173+
},
174+
wantWriter: "[\n \"value1\",\n \"value3\"\n]\n",
175+
},
176+
{
177+
name: "nil data",
178+
args: args{data: nil, jpath: "$"},
179+
wantWriter: "[\n null\n]\n",
129180
},
130181
}
131182
for _, tt := range tests {
132183
t.Run(
133184
tt.name, func(t *testing.T) {
134185
writer := &bytes.Buffer{}
135-
PrintJSON(tt.args.data, writer)
186+
PrintJSON(tt.args.data, tt.args.jpath, writer)
136187
if gotWriter := writer.String(); gotWriter != tt.wantWriter {
137188
t.Errorf("PrintJSON() = %v, want %v", gotWriter, tt.wantWriter)
138189
}
@@ -143,24 +194,76 @@ func TestPrintJSON(t *testing.T) {
143194

144195
func TestPrintYAML(t *testing.T) {
145196
type args struct {
146-
data interface{}
197+
data interface{}
198+
jpath string
147199
}
148200
tests := []struct {
149201
name string
150202
args args
151203
wantWriter string
152204
}{
153205
{
154-
name: "simple yaml",
155-
args: args{data: map[string]string{"key": "value"}},
206+
name: "empty jsonpath",
207+
args: args{data: map[string]string{"key": "value"}, jpath: ""},
156208
wantWriter: "key: value\n\n",
157209
},
210+
{
211+
name: "simple yaml",
212+
args: args{data: map[string]string{"key": "value"}, jpath: "$"},
213+
wantWriter: "- key: value\n\n",
214+
},
215+
{
216+
name: "invalid jsonpath",
217+
args: args{
218+
data: map[string]string{"key": "value"}, jpath: "//",
219+
},
220+
wantWriter: "Invalid JSONPath: //\n",
221+
},
222+
{
223+
name: "extract specific field",
224+
args: args{
225+
data: map[string]interface{}{
226+
"key": "value",
227+
"another": "field",
228+
}, jpath: "$.key",
229+
},
230+
wantWriter: "- value\n\n",
231+
},
232+
{
233+
name: "nested jsonpath",
234+
args: args{
235+
data: map[string]interface{}{
236+
"item1": map[string]string{"key1": "value1"},
237+
"item2": map[string]string{"key2": "value2"},
238+
"count": 2,
239+
},
240+
jpath: "$.*.key1",
241+
},
242+
wantWriter: "- value1\n\n",
243+
},
244+
{
245+
name: "array jsonpath",
246+
args: args{
247+
data: []map[string]string{
248+
{"key1": "value1"},
249+
{"key2": "value2"},
250+
{"key1": "value3"},
251+
},
252+
jpath: "$[*].key1",
253+
},
254+
wantWriter: "- value1\n- value3\n\n",
255+
},
256+
{
257+
name: "nil data",
258+
args: args{data: nil, jpath: "$"},
259+
wantWriter: "- null\n\n",
260+
},
158261
}
159262
for _, tt := range tests {
160263
t.Run(
161264
tt.name, func(t *testing.T) {
162265
writer := &bytes.Buffer{}
163-
PrintYAML(tt.args.data, writer)
266+
PrintYAML(tt.args.data, tt.args.jpath, writer)
164267
if gotWriter := writer.String(); gotWriter != tt.wantWriter {
165268
t.Errorf("PrintYAML() = %v, want %v", gotWriter, tt.wantWriter)
166269
}

0 commit comments

Comments
 (0)