Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds "decode all" option #92

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
optMonochrome = 1 << iota
optNoSort
optJSON
optAllObjects
)

// Output colors
Expand Down Expand Up @@ -95,6 +96,7 @@ func main() {
versionFlag bool
insecureFlag bool
jsonFlag bool
allObjectsFlag bool
)

flag.BoolVar(&ungronFlag, "ungron", false, "")
Expand All @@ -111,6 +113,8 @@ func main() {
flag.BoolVar(&insecureFlag, "insecure", false, "")
flag.BoolVar(&jsonFlag, "j", false, "")
flag.BoolVar(&jsonFlag, "json", false, "")
flag.BoolVar(&allObjectsFlag, "a", false, "")
flag.BoolVar(&allObjectsFlag, "all", false, "")

flag.Parse()

Expand Down Expand Up @@ -165,9 +169,12 @@ func main() {
var a actionFn = gron
if ungronFlag {
a = ungron
} else if allObjectsFlag {
a = gronStreamAll
} else if streamFlag {
a = gronStream
}

exitCode, err := a(rawInput, colorable.NewColorableStdout(), opts)

if exitCode != exitOK {
Expand Down Expand Up @@ -312,6 +319,117 @@ out:

}

// gronStreamAll is like the gron action, but it treats the input
// as multiple JSON objects. There's a bit of code duplication from the
// gron action, but it'd be fairly messy to combine the two actions
func gronStreamAll(r io.Reader, w io.Writer, opts int) (int, error) {
var err error
errstr := "failed to form statements"
var i int
var sc *bufio.Scanner
var buf []byte
var conv func(s statement) string
var top statement
var makePrefix func(index int) statement
var moved int64

// In order to read all the objects, we need a positionable stream
// because the JSON decoder reads past the end of a complete JSON
// item in order to complete a parse. Using `Seek()` doesn't work
// that well because whilst we have the position of the end of the
// JSON parse (via `d.InputOffset()`), we don't know the current
// position of the stream (and thus can't use `io.SeekCurrent`)
// meaning we have to `io.SeekStart` every time and that ends up
// getting progressively slower each time. Instead we use
// a `bytes.Buffer` as our `io.Reader`.
buf, err = io.ReadAll(r)
if err != nil {
goto out
}

if opts&optMonochrome > 0 {
conv = statementToString
} else {
conv = statementToColorString
}

// Helper function to make the prefix statements for each line
makePrefix = func(index int) statement {
return statement{
{"json", typBare},
{"[", typLBrace},
{fmt.Sprintf("%d", index), typNumericKey},
{"]", typRBrace},
}
}

// The first line of output needs to establish that the top-level
// thing is actually an array...
top = statement{
{"json", typBare},
{"=", typEquals},
{"[]", typEmptyArray},
{";", typSemi},
}

if opts&optJSON > 0 {
top, err = top.jsonify()
if err != nil {
goto out
}
}

fmt.Fprintln(w, conv(top))

i = 0

for {
var offset int64
var ss statements

br := bytes.NewReader(buf[moved:])

ss, offset, err = statementsFromJSONOffset(br, makePrefix(i))
i++
if err != nil && err != io.EOF {
goto out
}

// Go's maps do not have well-defined ordering, but we want a consistent
// output for a given input, so we must sort the statements
if opts&optNoSort == 0 {
sort.Sort(ss)
}

for _, s := range ss {
if opts&optJSON > 0 {
s, err = s.jsonify()
if err != nil {
goto out
}

}
fmt.Fprintln(w, conv(s))
}

if err == io.EOF {
return exitOK, nil
}

moved = moved + offset
}
if err = sc.Err(); err != nil {
errstr = "error reading multiline input: %s"
}

out:
if err != nil {
return exitFormStatements, fmt.Errorf(errstr+": %s", err)
}
return exitOK, nil

}

// ungron is the reverse of gron. Given assignment statements as input,
// it returns JSON. The only option is optMonochrome
func ungron(r io.Reader, w io.Writer, opts int) (int, error) {
Expand Down
15 changes: 15 additions & 0 deletions statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,21 @@ func statementsFromJSON(r io.Reader, prefix statement) (statements, error) {
return ss, nil
}

// statementsFromJSONOffset takes an io.Reader containing JSON
// and returns statements or an error on failure
func statementsFromJSONOffset(r io.Reader, prefix statement) (statements, int64, error) {
var top interface{}
d := json.NewDecoder(r)
d.UseNumber()
err := d.Decode(&top)
if err != nil {
return nil, 0, err
}
ss := make(statements, 0, 32)
ss.fill(prefix, top)
return ss, d.InputOffset(), nil
}

// fill takes a prefix statement and some value and recursively fills
// the statement list using that value
func (ss *statements) fill(prefix statement, v interface{}) {
Expand Down