mirror of
https://github.com/tomnomnom/gron
synced 2024-10-18 08:42:21 +00:00
Refactors ungron main, updates help and readme
This commit is contained in:
parent
4c131fe26c
commit
9a2b7530f5
61
README.mkd
61
README.mkd
|
@ -79,6 +79,53 @@ The output of `gron` is valid JavaScript:
|
|||
name: 'Tom' }
|
||||
```
|
||||
|
||||
## ungronning
|
||||
gron can also turn its output back into JSON:
|
||||
```
|
||||
▶ gron testdata/two.json | gron -u
|
||||
{
|
||||
"contact": {
|
||||
"email": "mail@tomnomnom.com",
|
||||
"twitter": "@TomNomNom"
|
||||
},
|
||||
"github": "https://github.com/tomnomnom/",
|
||||
"likes": [
|
||||
"code",
|
||||
"cheese",
|
||||
"meat"
|
||||
],
|
||||
"name": "Tom"
|
||||
}
|
||||
```
|
||||
|
||||
This means use can use gron with `grep` and other tools to modify JSON:
|
||||
```
|
||||
▶ gron testdata/two.json | grep likes | gron --ungron
|
||||
{
|
||||
"likes": [
|
||||
"code",
|
||||
"cheese",
|
||||
"meat"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
To preserve array keys, arrays are padded with `null` when values are missing:
|
||||
```
|
||||
▶ gron testdata/two.json | grep likes | grep -v cheese
|
||||
json.likes = [];
|
||||
json.likes[0] = "code";
|
||||
json.likes[2] = "meat";
|
||||
▶ gron testdata/two.json | grep likes | grep -v cheese | gron --ungron
|
||||
{
|
||||
"likes": [
|
||||
"code",
|
||||
null,
|
||||
"meat"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Get Help
|
||||
|
||||
```
|
||||
|
@ -86,20 +133,26 @@ The output of `gron` is valid JavaScript:
|
|||
Transform JSON (from a file, URL, or stdin) into discrete assignments to make it greppable
|
||||
|
||||
Usage:
|
||||
gron [file|url]
|
||||
gron [OPTIONS] [FILE|URL|-]
|
||||
|
||||
Options:
|
||||
-u, --ungron Reverse the operation (turn assignments back into JSON)
|
||||
|
||||
Exit Codes:
|
||||
0 OK
|
||||
1 Failed to open file
|
||||
2 Failed to read input
|
||||
3 Failed to decode JSON
|
||||
4 Failed to from statements
|
||||
4 Failed to form statements
|
||||
5 Failed to fetch URL
|
||||
6 Failed to parse statements
|
||||
7 Failed to encode JSON
|
||||
|
||||
Examples:
|
||||
gron /tmp/apiresponse.json
|
||||
gron http://headers.jsontest.com/
|
||||
curl -s http://headers.jsontest.com/ | gron
|
||||
gron http://jsonplaceholder.typicode.com/users/1
|
||||
curl -s http://jsonplaceholder.typicode.com/users/1 | gron
|
||||
gron http://jsonplaceholder.typicode.com/users/1 | grep company | gron --ungron
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
|
57
main.go
57
main.go
|
@ -18,7 +18,8 @@ const (
|
|||
exitJSONDecode
|
||||
exitFormStatements
|
||||
exitFetchURL
|
||||
exitUnknown
|
||||
exitParseStatements
|
||||
exitJSONEncode
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -26,28 +27,38 @@ func init() {
|
|||
h := "Transform JSON (from a file, URL, or stdin) into discrete assignments to make it greppable\n\n"
|
||||
|
||||
h += "Usage:\n"
|
||||
h += " gron [file|url]\n\n"
|
||||
h += " gron [OPTIONS] [FILE|URL|-]\n\n"
|
||||
|
||||
h += "Options:\n"
|
||||
h += " -u, --ungron\tReverse the operation (turn assignments back into JSON)\n\n"
|
||||
|
||||
h += "Exit Codes:\n"
|
||||
h += fmt.Sprintf(" %d\t%s\n", exitOK, "OK")
|
||||
h += fmt.Sprintf(" %d\t%s\n", exitOpenFile, "Failed to open file")
|
||||
h += fmt.Sprintf(" %d\t%s\n", exitReadInput, "Failed to read input")
|
||||
h += fmt.Sprintf(" %d\t%s\n", exitJSONDecode, "Failed to decode JSON")
|
||||
h += fmt.Sprintf(" %d\t%s\n", exitFormStatements, "Failed to from statements")
|
||||
h += fmt.Sprintf(" %d\t%s\n", exitFormStatements, "Failed to form statements")
|
||||
h += fmt.Sprintf(" %d\t%s\n", exitFetchURL, "Failed to fetch URL")
|
||||
h += fmt.Sprintf(" %d\t%s\n", exitParseStatements, "Failed to parse statements")
|
||||
h += fmt.Sprintf(" %d\t%s\n", exitJSONEncode, "Failed to encode JSON")
|
||||
h += "\n"
|
||||
|
||||
h += "Examples:\n"
|
||||
h += " gron /tmp/apiresponse.json\n"
|
||||
h += " gron http://headers.jsontest.com/ \n"
|
||||
h += " curl -s http://headers.jsontest.com/ | gron\n"
|
||||
h += " gron http://jsonplaceholder.typicode.com/users/1 \n"
|
||||
h += " curl -s http://jsonplaceholder.typicode.com/users/1 | gron\n"
|
||||
h += " gron http://jsonplaceholder.typicode.com/users/1 | grep company | gron --ungron\n"
|
||||
|
||||
fmt.Fprintf(os.Stderr, h)
|
||||
}
|
||||
}
|
||||
|
||||
var ungronFlag bool
|
||||
|
||||
func main() {
|
||||
ungronFlag := flag.Bool("ungron", false, "Turn statements into JSON instead")
|
||||
flag.BoolVar(&ungronFlag, "ungron", false, "Turn statements into JSON instead")
|
||||
flag.BoolVar(&ungronFlag, "u", false, "Turn statements into JSON instead")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
var raw io.Reader
|
||||
|
@ -73,7 +84,7 @@ func main() {
|
|||
|
||||
var exitCode int
|
||||
var err error
|
||||
if *ungronFlag {
|
||||
if ungronFlag {
|
||||
exitCode, err = ungron(raw, os.Stdout)
|
||||
} else {
|
||||
exitCode, err = gron(raw, os.Stdout)
|
||||
|
@ -121,31 +132,19 @@ func gron(r io.Reader, w io.Writer) (int, error) {
|
|||
func ungron(r io.Reader, w io.Writer) (int, error) {
|
||||
scanner := bufio.NewScanner(r)
|
||||
|
||||
// Get all the idividually parsed statements
|
||||
var parsed []interface{}
|
||||
// Make a list of statements from the input
|
||||
var ss statements
|
||||
for scanner.Scan() {
|
||||
l := newLexer(scanner.Text())
|
||||
u, err := ungronTokens(l.lex())
|
||||
|
||||
if err != nil {
|
||||
return exitUnknown, fmt.Errorf("failed to translate tokens into datastructure: %s", err)
|
||||
}
|
||||
|
||||
parsed = append(parsed, u)
|
||||
ss.AddFull(scanner.Text())
|
||||
}
|
||||
// TODO: Handle any scanner errors
|
||||
|
||||
if len(parsed) == 0 {
|
||||
return exitUnknown, fmt.Errorf("no statements were parsed")
|
||||
if err := scanner.Err(); err != nil {
|
||||
return exitReadInput, fmt.Errorf("failed to read input statements")
|
||||
}
|
||||
|
||||
merged := parsed[0]
|
||||
for _, p := range parsed[1:] {
|
||||
m, err := recursiveMerge(merged, p)
|
||||
if err != nil {
|
||||
return exitUnknown, fmt.Errorf("failed to merge statements: %s", err)
|
||||
}
|
||||
merged = m
|
||||
// ungron the statements
|
||||
merged, err := ss.ungron()
|
||||
if err != nil {
|
||||
return exitParseStatements, fmt.Errorf("failed to parse input statements")
|
||||
}
|
||||
|
||||
// If there's only one top level key and it's "json", make that the top level thing
|
||||
|
@ -160,7 +159,7 @@ func ungron(r io.Reader, w io.Writer) (int, error) {
|
|||
|
||||
j, err := json.MarshalIndent(merged, "", " ")
|
||||
if err != nil {
|
||||
return exitUnknown, fmt.Errorf("failed to convert statements to JSON: %s", err)
|
||||
return exitJSONEncode, fmt.Errorf("failed to convert statements to JSON: %s", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s\n", j)
|
||||
|
|
|
@ -17,6 +17,11 @@ func (ss *statements) Add(prefix, value string) {
|
|||
*ss = append(*ss, fmt.Sprintf("%s = %s;", prefix, value))
|
||||
}
|
||||
|
||||
// AddFull adds a new statement to the list given the entire statement
|
||||
func (ss *statements) AddFull(s string) {
|
||||
*ss = append(*ss, s)
|
||||
}
|
||||
|
||||
// AddMulti adds a whole other list of statements
|
||||
func (ss *statements) AddMulti(l statements) {
|
||||
*ss = append(*ss, l...)
|
||||
|
@ -32,6 +37,38 @@ func (ss statements) Swap(i, j int) {
|
|||
ss[i], ss[j] = ss[j], ss[i]
|
||||
}
|
||||
|
||||
// ungron turns statements into a proper datastructur
|
||||
func (ss statements) ungron() (interface{}, error) {
|
||||
|
||||
// Get all the idividually parsed statements
|
||||
var parsed []interface{}
|
||||
for _, s := range ss {
|
||||
l := newLexer(s)
|
||||
u, err := ungronTokens(l.lex())
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to translate tokens into datastructure: %s", err)
|
||||
}
|
||||
|
||||
parsed = append(parsed, u)
|
||||
}
|
||||
|
||||
if len(parsed) == 0 {
|
||||
return nil, fmt.Errorf("no statements were parsed")
|
||||
}
|
||||
|
||||
merged := parsed[0]
|
||||
for _, p := range parsed[1:] {
|
||||
m, err := recursiveMerge(merged, p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to merge statements: %s", err)
|
||||
}
|
||||
merged = m
|
||||
}
|
||||
return merged, nil
|
||||
|
||||
}
|
||||
|
||||
// Less compares two statements for sort.Sort
|
||||
// Implements a natural sort to keep array indexes in order
|
||||
func (ss statements) Less(a, b int) bool {
|
||||
|
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
@ -291,3 +292,40 @@ func BenchmarkMakePrefixInt(b *testing.B) {
|
|||
_, _ = makePrefix("json", 212)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUngronStatements(t *testing.T) {
|
||||
in := statements{
|
||||
`json.contact = {};`,
|
||||
`json.contact["e-mail"][0] = "mail@tomnomnom.com";`,
|
||||
`json.contact["e-mail"][1] = "test@tomnomnom.com";`,
|
||||
`json.contact["e-mail"][3] = "foo@tomnomnom.com";`,
|
||||
`json.contact.twitter = "@TomNomNom";`,
|
||||
}
|
||||
|
||||
want := map[string]interface{}{
|
||||
"json": map[string]interface{}{
|
||||
"contact": map[string]interface{}{
|
||||
"e-mail": []interface{}{
|
||||
0: "mail@tomnomnom.com",
|
||||
1: "test@tomnomnom.com",
|
||||
3: "foo@tomnomnom.com",
|
||||
},
|
||||
"twitter": "@TomNomNom",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
have, err := in.ungron()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("want nil error but have: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("Have: %#v", have)
|
||||
t.Logf("Want: %#v", want)
|
||||
|
||||
eq := reflect.DeepEqual(have, want)
|
||||
if !eq {
|
||||
t.Errorf("have and want are not equal")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue