Skip to content

Commit cbfc16f

Browse files
mattnfiatjaf
authored andcommitted
add --jq-raw flag to print jq string results unquoted, like jq -r
1 parent 62fa610 commit cbfc16f

6 files changed

Lines changed: 62 additions & 17 deletions

File tree

cli_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,17 @@ func TestNaturalTimestamps(t *testing.T) {
229229
require.Equal(t, nostr.Timestamp(1526711839), evt.CreatedAt)
230230
require.Equal(t, "nn", evt.Content)
231231
}
232+
233+
func TestEventJQ(t *testing.T) {
234+
// default: string results come out JSON-quoted, like plain jq
235+
quoted := call(t, "nak event --ts 1699485669 -c hello --jq .content")
236+
require.Equal(t, `"hello"`, quoted)
237+
238+
// --jq-raw: string results come out unquoted, like `jq -r`
239+
raw := call(t, "nak event --ts 1699485669 -c hello --jq .content --jq-raw")
240+
require.Equal(t, "hello", raw)
241+
242+
// --jq-raw on a non-string result is still JSON-encoded, like `jq -r`
243+
num := call(t, "nak event --ts 1699485669 -k 7 --jq .kind --jq-raw")
244+
require.Equal(t, "7", num)
245+
}

event.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ example:
9595
Usage: "filter returned events with jq expression",
9696
Category: CATEGORY_EXTRAS,
9797
},
98+
&cli.BoolFlag{
99+
Name: "jq-raw",
100+
Usage: "print --jq string results without JSON quoting, like `jq -r`",
101+
Category: CATEGORY_EXTRAS,
102+
},
98103
&cli.BoolFlag{
99104
Name: "nevent",
100105
Usage: "print the nevent code (to stderr) after the event is published",
@@ -187,7 +192,7 @@ example:
187192
return err
188193
}
189194

190-
jq, err := jqPrepare(c.String("jq"))
195+
jq, err := jqPrepare(c.String("jq"), c.Bool("jq-raw"))
191196
if err != nil {
192197
return err
193198
}
@@ -422,12 +427,11 @@ example:
422427
}
423428
stdout(result)
424429
} else {
425-
v, matches, err := jq(evt)
430+
out, matches, err := jq(evt)
426431
if err != nil {
427432
return fmt.Errorf("jq filter failed: %w", err)
428433
}
429434
if matches {
430-
out, _ := json.MarshalToString(v)
431435
stdout(out)
432436
}
433437
}

fetch.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,14 @@ var fetch = &cli.Command{
2828
Name: "jq",
2929
Usage: "filter returned events with jq expression",
3030
},
31+
&cli.BoolFlag{
32+
Name: "jq-raw",
33+
Usage: "print --jq string results without JSON quoting, like `jq -r`",
34+
},
3135
),
3236
ArgsUsage: "[nip05_or_nip19_code]",
3337
Action: func(ctx context.Context, c *cli.Command) error {
34-
jq, err := jqPrepare(c.String("jq"))
38+
jq, err := jqPrepare(c.String("jq"), c.Bool("jq-raw"))
3539
if err != nil {
3640
return err
3741
}
@@ -131,7 +135,7 @@ var fetch = &cli.Command{
131135
if !matches {
132136
continue
133137
}
134-
out, _ = json.MarshalToString(v)
138+
out = v
135139
}
136140
stdout(out)
137141
}

filter.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ example:
2424
Name: "jq",
2525
Usage: "filter matching events with jq expression",
2626
},
27+
&cli.BoolFlag{
28+
Name: "jq-raw",
29+
Usage: "print --jq string results without JSON quoting, like `jq -r`",
30+
},
2731
),
2832
ArgsUsage: "[event_json] [base_filter_json]",
2933
Action: func(ctx context.Context, c *cli.Command) error {
@@ -59,7 +63,7 @@ example:
5963
return err
6064
}
6165

62-
jq, err := jqPrepare(c.String("jq"))
66+
jq, err := jqPrepare(c.String("jq"), c.Bool("jq-raw"))
6367
if err != nil {
6468
return err
6569
}
@@ -104,7 +108,7 @@ example:
104108
if !matches {
105109
continue
106110
}
107-
out, _ = json.MarshalToString(v)
111+
out = v
108112
}
109113
stdout(out)
110114
} else {

jq.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ def time: .created_at | gmtime | strftime("%H:%M:%S");
1919
def datetime: .created_at | gmtime | strftime("%Y-%m-%dT%H:%M:%SZ");
2020
`
2121

22-
type jqProcessor func(nostr.Event) (any, bool, error)
22+
// jqProcessor runs the compiled jq expression against an event and returns the
23+
// rendered output string. When matches is false the event should be skipped.
24+
type jqProcessor func(nostr.Event) (out string, matches bool, err error)
2325

24-
func jqPrepare(expr string) (jqProcessor, error) {
26+
func jqPrepare(expr string, raw bool) (jqProcessor, error) {
2527
if expr == "" {
2628
return nil, nil
2729
}
@@ -36,30 +38,43 @@ func jqPrepare(expr string) (jqProcessor, error) {
3638
return nil, fmt.Errorf("failed to compile jq expression: %w", err)
3739
}
3840

39-
return func(evt nostr.Event) (any, bool, error) {
41+
return func(evt nostr.Event) (string, bool, error) {
4042
input, err := toJQInput(evt)
4143
if err != nil {
42-
return nil, false, err
44+
return "", false, err
4345
}
4446

4547
iter := code.Run(input)
4648
for {
4749
v, ok := iter.Next()
4850
if !ok {
49-
return v, false, nil
51+
return "", false, nil
5052
}
5153

5254
if err, ok := v.(error); ok {
53-
return v, false, err
55+
return "", false, err
5456
}
5557

5658
if jqTruthy(v) {
57-
return v, true, nil
59+
return jqStringify(v, raw), true, nil
5860
}
5961
}
6062
}, nil
6163
}
6264

65+
// jqStringify renders a jq result value. In raw mode string results are printed
66+
// without JSON quoting (like `jq -r`); non-string results are always
67+
// JSON-encoded.
68+
func jqStringify(v any, raw bool) string {
69+
if raw {
70+
if s, ok := v.(string); ok {
71+
return s
72+
}
73+
}
74+
out, _ := json.MarshalToString(v)
75+
return out
76+
}
77+
6378
func toJQInput(v any) (any, error) {
6479
data, err := json.Marshal(v)
6580
if err != nil {

req.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ example:
4646
Name: "jq",
4747
Usage: "filter returned events with jq expression",
4848
},
49+
&cli.BoolFlag{
50+
Name: "jq-raw",
51+
Usage: "print --jq string results without JSON quoting, like `jq -r`",
52+
},
4953
&cli.BoolFlag{
5054
Name: "no-verify",
5155
Usage: "skip event signature verification from relays",
@@ -124,7 +128,7 @@ example:
124128
return fmt.Errorf("relay URLs are incompatible with --bare or --spell")
125129
}
126130

127-
jq, err := jqPrepare(c.String("jq"))
131+
jq, err := jqPrepare(c.String("jq"), c.Bool("jq-raw"))
128132
if err != nil {
129133
return err
130134
}
@@ -437,7 +441,7 @@ readevents:
437441
if !matches {
438442
continue
439443
}
440-
out, _ = json.MarshalToString(v)
444+
out = v
441445
}
442446
stdout(out)
443447

@@ -641,7 +645,7 @@ func (p PrintingQuerierPublisher) Publish(ctx context.Context, evt nostr.Event)
641645
if !matches {
642646
return nil
643647
}
644-
out, _ = json.MarshalToString(v)
648+
out = v
645649
}
646650
stdout(out)
647651
return nil

0 commit comments

Comments
 (0)