From 60d1292f80950d5177d3f8afb60eeaba9a63dde0 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 5 Feb 2025 09:44:16 -0300 Subject: [PATCH] parse multiline json from input on nak event and nak req, use iterators instead of channels for more efficient stdin parsing. --- event.go | 31 ++++++++++++-------- helpers.go | 86 +++++++++++++++++++++++++++++++----------------------- req.go | 2 +- 3 files changed, 70 insertions(+), 49 deletions(-) diff --git a/event.go b/event.go index 2c5f590..d0ba01d 100644 --- a/event.go +++ b/event.go @@ -154,21 +154,20 @@ example: doAuth := c.Bool("auth") - // then process input and generate events - for stdinEvent := range getStdinLinesOrBlank() { - evt := nostr.Event{ - Tags: make(nostr.Tags, 0, 3), - } + // then process input and generate events: - kindWasSupplied := false + // will reuse this + var evt nostr.Event + + // this is called when we have a valid json from stdin + handleEvent := func(stdinEvent string) error { + evt.Content = "" + + kindWasSupplied := strings.Contains(stdinEvent, `"kind"`) mustRehashAndResign := false - if stdinEvent != "" { - if err := easyjson.Unmarshal([]byte(stdinEvent), &evt); err != nil { - ctx = lineProcessingError(ctx, "invalid event received from stdin: %s", err) - continue - } - kindWasSupplied = strings.Contains(stdinEvent, `"kind"`) + if err := easyjson.Unmarshal([]byte(stdinEvent), &evt); err != nil { + return fmt.Errorf("invalid event received from stdin: %s", err) } if kind := c.Uint("kind"); slices.Contains(c.FlagNames(), "kind") { @@ -324,6 +323,14 @@ example: log(nevent + "\n") } } + + return nil + } + + for stdinEvent := range getJsonsOrBlank() { + if err := handleEvent(stdinEvent); err != nil { + ctx = lineProcessingError(ctx, err.Error()) + } } exitIfLineProcessingError(ctx) diff --git a/helpers.go b/helpers.go index 3a3d9ce..73c13cb 100644 --- a/helpers.go +++ b/helpers.go @@ -4,11 +4,13 @@ import ( "bufio" "context" "fmt" + "iter" "math/rand" "net/http" "net/textproto" "net/url" "os" + "slices" "strings" "time" @@ -43,59 +45,71 @@ func isPiped() bool { return stat.Mode()&os.ModeCharDevice == 0 } -func getStdinLinesOrBlank() chan string { - multi := make(chan string) - if hasStdinLines := writeStdinLinesOrNothing(multi); !hasStdinLines { - single := make(chan string, 1) - single <- "" - close(single) - return single - } else { - return multi +func getJsonsOrBlank() iter.Seq[string] { + var curr strings.Builder + + return func(yield func(string) bool) { + for stdinLine := range getStdinLinesOrBlank() { + // we're look for an event, but it may be in multiple lines, so if json parsing fails + // we'll try the next line until we're successful + curr.WriteString(stdinLine) + stdinEvent := curr.String() + + var dummy any + if err := json.Unmarshal([]byte(stdinEvent), &dummy); err != nil { + continue + } + + if !yield(stdinEvent) { + return + } + + curr.Reset() + } } } -func getStdinLinesOrArguments(args cli.Args) chan string { +func getStdinLinesOrBlank() iter.Seq[string] { + return func(yield func(string) bool) { + if hasStdinLines := writeStdinLinesOrNothing(yield); !hasStdinLines { + return + } else { + return + } + } +} + +func getStdinLinesOrArguments(args cli.Args) iter.Seq[string] { return getStdinLinesOrArgumentsFromSlice(args.Slice()) } -func getStdinLinesOrArgumentsFromSlice(args []string) chan string { +func getStdinLinesOrArgumentsFromSlice(args []string) iter.Seq[string] { // try the first argument if len(args) > 0 { - argsCh := make(chan string, 1) - go func() { - for _, arg := range args { - argsCh <- arg - } - close(argsCh) - }() - return argsCh + return slices.Values(args) } // try the stdin - multi := make(chan string) - if !writeStdinLinesOrNothing(multi) { - close(multi) + return func(yield func(string) bool) { + writeStdinLinesOrNothing(yield) } - return multi } -func writeStdinLinesOrNothing(ch chan string) (hasStdinLines bool) { +func writeStdinLinesOrNothing(yield func(string) bool) (hasStdinLines bool) { if isPiped() { // piped - go func() { - scanner := bufio.NewScanner(os.Stdin) - scanner.Buffer(make([]byte, 16*1024*1024), 256*1024*1024) - hasEmittedAtLeastOne := false - for scanner.Scan() { - ch <- strings.TrimSpace(scanner.Text()) - hasEmittedAtLeastOne = true + scanner := bufio.NewScanner(os.Stdin) + scanner.Buffer(make([]byte, 16*1024*1024), 256*1024*1024) + hasEmittedAtLeastOne := false + for scanner.Scan() { + if !yield(strings.TrimSpace(scanner.Text())) { + return } - if !hasEmittedAtLeastOne { - ch <- "" - } - close(ch) - }() + hasEmittedAtLeastOne = true + } + if !hasEmittedAtLeastOne { + yield("") + } return true } else { // not piped diff --git a/req.go b/req.go index 09cae23..a5442da 100644 --- a/req.go +++ b/req.go @@ -107,7 +107,7 @@ example: }() } - for stdinFilter := range getStdinLinesOrBlank() { + for stdinFilter := range getJsonsOrBlank() { filter := nostr.Filter{} if stdinFilter != "" { if err := easyjson.Unmarshal([]byte(stdinFilter), &filter); err != nil {