add --jq-raw flag to print jq string results unquoted, like jq -r

This commit is contained in:
Yasuhiro Matsumoto
2026-06-22 21:14:26 -03:00
committed by fiatjaf
parent 62fa610a06
commit cbfc16f354
6 changed files with 62 additions and 17 deletions
+14
View File
@@ -229,3 +229,17 @@ func TestNaturalTimestamps(t *testing.T) {
require.Equal(t, nostr.Timestamp(1526711839), evt.CreatedAt) require.Equal(t, nostr.Timestamp(1526711839), evt.CreatedAt)
require.Equal(t, "nn", evt.Content) require.Equal(t, "nn", evt.Content)
} }
func TestEventJQ(t *testing.T) {
// default: string results come out JSON-quoted, like plain jq
quoted := call(t, "nak event --ts 1699485669 -c hello --jq .content")
require.Equal(t, `"hello"`, quoted)
// --jq-raw: string results come out unquoted, like `jq -r`
raw := call(t, "nak event --ts 1699485669 -c hello --jq .content --jq-raw")
require.Equal(t, "hello", raw)
// --jq-raw on a non-string result is still JSON-encoded, like `jq -r`
num := call(t, "nak event --ts 1699485669 -k 7 --jq .kind --jq-raw")
require.Equal(t, "7", num)
}
+7 -3
View File
@@ -95,6 +95,11 @@ example:
Usage: "filter returned events with jq expression", Usage: "filter returned events with jq expression",
Category: CATEGORY_EXTRAS, Category: CATEGORY_EXTRAS,
}, },
&cli.BoolFlag{
Name: "jq-raw",
Usage: "print --jq string results without JSON quoting, like `jq -r`",
Category: CATEGORY_EXTRAS,
},
&cli.BoolFlag{ &cli.BoolFlag{
Name: "nevent", Name: "nevent",
Usage: "print the nevent code (to stderr) after the event is published", Usage: "print the nevent code (to stderr) after the event is published",
@@ -187,7 +192,7 @@ example:
return err return err
} }
jq, err := jqPrepare(c.String("jq")) jq, err := jqPrepare(c.String("jq"), c.Bool("jq-raw"))
if err != nil { if err != nil {
return err return err
} }
@@ -422,12 +427,11 @@ example:
} }
stdout(result) stdout(result)
} else { } else {
v, matches, err := jq(evt) out, matches, err := jq(evt)
if err != nil { if err != nil {
return fmt.Errorf("jq filter failed: %w", err) return fmt.Errorf("jq filter failed: %w", err)
} }
if matches { if matches {
out, _ := json.MarshalToString(v)
stdout(out) stdout(out)
} }
} }
+6 -2
View File
@@ -28,10 +28,14 @@ var fetch = &cli.Command{
Name: "jq", Name: "jq",
Usage: "filter returned events with jq expression", Usage: "filter returned events with jq expression",
}, },
&cli.BoolFlag{
Name: "jq-raw",
Usage: "print --jq string results without JSON quoting, like `jq -r`",
},
), ),
ArgsUsage: "[nip05_or_nip19_code]", ArgsUsage: "[nip05_or_nip19_code]",
Action: func(ctx context.Context, c *cli.Command) error { Action: func(ctx context.Context, c *cli.Command) error {
jq, err := jqPrepare(c.String("jq")) jq, err := jqPrepare(c.String("jq"), c.Bool("jq-raw"))
if err != nil { if err != nil {
return err return err
} }
@@ -131,7 +135,7 @@ var fetch = &cli.Command{
if !matches { if !matches {
continue continue
} }
out, _ = json.MarshalToString(v) out = v
} }
stdout(out) stdout(out)
} }
+6 -2
View File
@@ -24,6 +24,10 @@ example:
Name: "jq", Name: "jq",
Usage: "filter matching events with jq expression", Usage: "filter matching events with jq expression",
}, },
&cli.BoolFlag{
Name: "jq-raw",
Usage: "print --jq string results without JSON quoting, like `jq -r`",
},
), ),
ArgsUsage: "[event_json] [base_filter_json]", ArgsUsage: "[event_json] [base_filter_json]",
Action: func(ctx context.Context, c *cli.Command) error { Action: func(ctx context.Context, c *cli.Command) error {
@@ -59,7 +63,7 @@ example:
return err return err
} }
jq, err := jqPrepare(c.String("jq")) jq, err := jqPrepare(c.String("jq"), c.Bool("jq-raw"))
if err != nil { if err != nil {
return err return err
} }
@@ -104,7 +108,7 @@ example:
if !matches { if !matches {
continue continue
} }
out, _ = json.MarshalToString(v) out = v
} }
stdout(out) stdout(out)
} else { } else {
+22 -7
View File
@@ -19,9 +19,11 @@ def time: .created_at | gmtime | strftime("%H:%M:%S");
def datetime: .created_at | gmtime | strftime("%Y-%m-%dT%H:%M:%SZ"); def datetime: .created_at | gmtime | strftime("%Y-%m-%dT%H:%M:%SZ");
` `
type jqProcessor func(nostr.Event) (any, bool, error) // jqProcessor runs the compiled jq expression against an event and returns the
// rendered output string. When matches is false the event should be skipped.
type jqProcessor func(nostr.Event) (out string, matches bool, err error)
func jqPrepare(expr string) (jqProcessor, error) { func jqPrepare(expr string, raw bool) (jqProcessor, error) {
if expr == "" { if expr == "" {
return nil, nil return nil, nil
} }
@@ -36,30 +38,43 @@ func jqPrepare(expr string) (jqProcessor, error) {
return nil, fmt.Errorf("failed to compile jq expression: %w", err) return nil, fmt.Errorf("failed to compile jq expression: %w", err)
} }
return func(evt nostr.Event) (any, bool, error) { return func(evt nostr.Event) (string, bool, error) {
input, err := toJQInput(evt) input, err := toJQInput(evt)
if err != nil { if err != nil {
return nil, false, err return "", false, err
} }
iter := code.Run(input) iter := code.Run(input)
for { for {
v, ok := iter.Next() v, ok := iter.Next()
if !ok { if !ok {
return v, false, nil return "", false, nil
} }
if err, ok := v.(error); ok { if err, ok := v.(error); ok {
return v, false, err return "", false, err
} }
if jqTruthy(v) { if jqTruthy(v) {
return v, true, nil return jqStringify(v, raw), true, nil
} }
} }
}, nil }, nil
} }
// jqStringify renders a jq result value. In raw mode string results are printed
// without JSON quoting (like `jq -r`); non-string results are always
// JSON-encoded.
func jqStringify(v any, raw bool) string {
if raw {
if s, ok := v.(string); ok {
return s
}
}
out, _ := json.MarshalToString(v)
return out
}
func toJQInput(v any) (any, error) { func toJQInput(v any) (any, error) {
data, err := json.Marshal(v) data, err := json.Marshal(v)
if err != nil { if err != nil {
+7 -3
View File
@@ -46,6 +46,10 @@ example:
Name: "jq", Name: "jq",
Usage: "filter returned events with jq expression", Usage: "filter returned events with jq expression",
}, },
&cli.BoolFlag{
Name: "jq-raw",
Usage: "print --jq string results without JSON quoting, like `jq -r`",
},
&cli.BoolFlag{ &cli.BoolFlag{
Name: "no-verify", Name: "no-verify",
Usage: "skip event signature verification from relays", Usage: "skip event signature verification from relays",
@@ -124,7 +128,7 @@ example:
return fmt.Errorf("relay URLs are incompatible with --bare or --spell") return fmt.Errorf("relay URLs are incompatible with --bare or --spell")
} }
jq, err := jqPrepare(c.String("jq")) jq, err := jqPrepare(c.String("jq"), c.Bool("jq-raw"))
if err != nil { if err != nil {
return err return err
} }
@@ -437,7 +441,7 @@ readevents:
if !matches { if !matches {
continue continue
} }
out, _ = json.MarshalToString(v) out = v
} }
stdout(out) stdout(out)
@@ -641,7 +645,7 @@ func (p PrintingQuerierPublisher) Publish(ctx context.Context, evt nostr.Event)
if !matches { if !matches {
return nil return nil
} }
out, _ = json.MarshalToString(v) out = v
} }
stdout(out) stdout(out)
return nil return nil