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

This commit is contained in:
Yasuhiro Matsumoto
2026-06-23 08:37:26 +09:00
committed by fiatjaf
parent 62fa610a06
commit cbfc16f354
6 changed files with 62 additions and 17 deletions

View File

@@ -229,3 +229,17 @@ func TestNaturalTimestamps(t *testing.T) {
require.Equal(t, nostr.Timestamp(1526711839), evt.CreatedAt)
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)
}

View File

@@ -95,6 +95,11 @@ example:
Usage: "filter returned events with jq expression",
Category: CATEGORY_EXTRAS,
},
&cli.BoolFlag{
Name: "jq-raw",
Usage: "print --jq string results without JSON quoting, like `jq -r`",
Category: CATEGORY_EXTRAS,
},
&cli.BoolFlag{
Name: "nevent",
Usage: "print the nevent code (to stderr) after the event is published",
@@ -187,7 +192,7 @@ example:
return err
}
jq, err := jqPrepare(c.String("jq"))
jq, err := jqPrepare(c.String("jq"), c.Bool("jq-raw"))
if err != nil {
return err
}
@@ -422,12 +427,11 @@ example:
}
stdout(result)
} else {
v, matches, err := jq(evt)
out, matches, err := jq(evt)
if err != nil {
return fmt.Errorf("jq filter failed: %w", err)
}
if matches {
out, _ := json.MarshalToString(v)
stdout(out)
}
}

View File

@@ -28,10 +28,14 @@ var fetch = &cli.Command{
Name: "jq",
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]",
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 {
return err
}
@@ -131,7 +135,7 @@ var fetch = &cli.Command{
if !matches {
continue
}
out, _ = json.MarshalToString(v)
out = v
}
stdout(out)
}

View File

@@ -24,6 +24,10 @@ example:
Name: "jq",
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]",
Action: func(ctx context.Context, c *cli.Command) error {
@@ -59,7 +63,7 @@ example:
return err
}
jq, err := jqPrepare(c.String("jq"))
jq, err := jqPrepare(c.String("jq"), c.Bool("jq-raw"))
if err != nil {
return err
}
@@ -104,7 +108,7 @@ example:
if !matches {
continue
}
out, _ = json.MarshalToString(v)
out = v
}
stdout(out)
} else {

29
jq.go
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");
`
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 == "" {
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 func(evt nostr.Event) (any, bool, error) {
return func(evt nostr.Event) (string, bool, error) {
input, err := toJQInput(evt)
if err != nil {
return nil, false, err
return "", false, err
}
iter := code.Run(input)
for {
v, ok := iter.Next()
if !ok {
return v, false, nil
return "", false, nil
}
if err, ok := v.(error); ok {
return v, false, err
return "", false, err
}
if jqTruthy(v) {
return v, true, nil
return jqStringify(v, raw), true, 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) {
data, err := json.Marshal(v)
if err != nil {

10
req.go
View File

@@ -46,6 +46,10 @@ example:
Name: "jq",
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{
Name: "no-verify",
Usage: "skip event signature verification from relays",
@@ -124,7 +128,7 @@ example:
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 {
return err
}
@@ -437,7 +441,7 @@ readevents:
if !matches {
continue
}
out, _ = json.MarshalToString(v)
out = v
}
stdout(out)
@@ -641,7 +645,7 @@ func (p PrintingQuerierPublisher) Publish(ctx context.Context, evt nostr.Event)
if !matches {
return nil
}
out, _ = json.MarshalToString(v)
out = v
}
stdout(out)
return nil