diff --git a/internal/options/options.go b/internal/options/options.go index cbf6a08..5c96070 100644 --- a/internal/options/options.go +++ b/internal/options/options.go @@ -2,12 +2,14 @@ package options import ( "flag" + "fmt" ) const ( - path = "path" - exparray = "expect-array" - version = "version" + OptPath = "path" + OptExpectArray = "expect-array" + OptVersion = "version" + OptPreserveArray = "preserve-array" ) // New create an option handler that will parse the options from command line args @@ -19,24 +21,35 @@ func New(args []string) (Handler, error) { h.StringVar( &o.Path, - path, + OptPath, "", "path to get to the JSON value you want to extract, e.g key1.key2", ) h.BoolVar( &o.ExpectArray, - exparray, + OptExpectArray, false, "check that whatever we're processing is an array, and fail if not", ) h.BoolVar( &o.JustPrintVersion, - version, + OptVersion, false, "print the version description for this tool and exit", ) + h.BoolVar( + &o.PreserveArray, + OptPreserveArray, + false, + "instead of turning the top-level array into NDJSON preserve the array, useful for JSON streams", + ) err := h.Parse(args) + + if o.PreserveArray && o.ExpectArray { + return h, fmt.Errorf("options conflict, -%s does not work alongside -%s", OptPreserveArray, OptExpectArray) + } + h.Options = o return h, err @@ -49,6 +62,7 @@ type Handler struct { type Set struct { ExpectArray bool + PreserveArray bool JustPrintVersion bool Path string Args []string diff --git a/internal/options/options_test.go b/internal/options/options_test.go index d6a10eb..4c7a014 100644 --- a/internal/options/options_test.go +++ b/internal/options/options_test.go @@ -60,6 +60,28 @@ func TestOptParse(t *testing.T) { assert.Error(t, e) }, }, + { + name: "preserve array", + in: []string{"-preserve-array"}, + exp: Set{ + PreserveArray: true, + }, + checkErr: func(t *testing.T, e error) { + assert.NoError(t, e) + }, + }, + { + name: "preserve array + expect array", + in: []string{"-preserve-array", "-expect-array"}, + exp: Set{}, + checkErr: func(t *testing.T, e error) { + is := assert.Error(t, e) + if !is { + return + } + assert.Contains(t, e.Error(), "options conflict", "error message") + }, + }, { name: "help", in: []string{"-help"}, diff --git a/processor.go b/processor.go index 32a74d7..0984b5e 100644 --- a/processor.go +++ b/processor.go @@ -43,7 +43,7 @@ func (p processor) run() error { return errNoJSON() } - if c != '[' { + if c != '[' || p.options.PreserveArray { return p.handleNonArray(js, c, true) } @@ -91,6 +91,7 @@ func (p processor) handlePathNodes(nodes []string, scan *json.JSON) error { } return p.handlePathNodes(nodes, scan) } + func (p processor) prepOut() (w io.Writer, finishOut func() error) { if p.buffered { bw := bufio.NewWriter(p.out) @@ -191,7 +192,7 @@ func (p processor) handleNonArray(j *json.JSON, clue byte, topLevel bool) error if err != nil { return err } - if clue == '[' { + if clue == '[' && !p.options.PreserveArray { return errArrayInStream() } @@ -285,5 +286,5 @@ func peekErr(e error) error { } func errArrayInStream() error { - return fmt.Errorf("Found an array in what seemed to be a stream of other types, this is not handled yet. Report your use-case if you need this") + return fmt.Errorf("Found an array in what seemed to be a JSON stream. Consider using -%s", options.OptPreserveArray) } diff --git a/processor_test.go b/processor_test.go index ce7d20b..5799e51 100644 --- a/processor_test.go +++ b/processor_test.go @@ -378,10 +378,18 @@ func TestProcessor(t *testing.T) { }, { name: "array in the stream", - in: sreader(`{"p": 1234} [1 2 3 4]`), + in: sreader(`{"p": 1234} [1,2,3,4]`), exp: `{"p": 1234}` + "\n", expErr: errArrayInStream(), }, + { + name: "two arrays in the stream + preserve array", + in: sreader("[1,2][3,4]"), + opts: options.Set{ + PreserveArray: true, + }, + exp: "[1,2]\n" + "[3,4]\n", + }, } for _, tc := range cases {