jsonpretty 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3455de5bf8ae23a61024c472d6299f87e7ff5a0fe11fd7e4ac29d9b182a343fe
4
- data.tar.gz: 9bb180a3b61581d468280650f186849199c32018fb048ee9081a00a381a5c5e9
3
+ metadata.gz: 89641014acab0632260843ff87e190604e7342d386109dfa77245ec343ff1ee4
4
+ data.tar.gz: 7fe4a6155fed431b9c64735fa3a8c133354c293a60d9a03c5786a15f3e31348d
5
5
  SHA512:
6
- metadata.gz: 6d8780f2c5b04c888a883b02b31805ba15624ae501b00758d880b96f0918df2faa4c72a42684aa9f6133aad7387de740b3a46c472da8bbe6a320811614b7725e
7
- data.tar.gz: befc010615d96350c633547943379330c27daf66dde2343a71eeeab746fa4369f35aec91cc5504bf69e87787e70395a63fb1093462df72c4db4e1af402da1a4d
6
+ metadata.gz: 17e783dbab3458c8c46ef6b9cb3f6065f6552eccbe65b6ba408003709bed7cbc92e98ab4f3d09750363fd2fbd7426df7accb2ea4716ec24f53b5c92e24789d14
7
+ data.tar.gz: d3b01927f851234aba103921e43b8db1cc2d07383ea73c825b78027cd6bfacf8b763be0112aff91ba8e08dd53a1b27d61129aa1dd10861a4267e438d3d7dd6e2
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
1
  *.gem
2
2
  pkg
3
3
  /Gemfile.lock
4
+
5
+ dist/
data/.goreleaser.yaml ADDED
@@ -0,0 +1,30 @@
1
+ # This is an example .goreleaser.yml file with some sensible defaults.
2
+ # Make sure to check the documentation at https://goreleaser.com
3
+ before:
4
+ hooks:
5
+ # You may remove this if you don't use go modules.
6
+ - go mod tidy
7
+ builds:
8
+ - env:
9
+ - CGO_ENABLED=0
10
+ goos:
11
+ - linux
12
+ - windows
13
+ - darwin
14
+ archives:
15
+ - replacements:
16
+ darwin: Darwin
17
+ linux: Linux
18
+ windows: Windows
19
+ 386: i386
20
+ amd64: x86_64
21
+ checksum:
22
+ name_template: 'checksums.txt'
23
+ snapshot:
24
+ name_template: "{{ incpatch .Version }}-next"
25
+ changelog:
26
+ sort: asc
27
+ filters:
28
+ exclude:
29
+ - '^docs:'
30
+ - '^test:'
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
+ === 1.2.0 / 2021-12-30
2
+
3
+ * New version to correspond with go port
4
+
1
5
  === 1.1.2 / 2021-12-27
2
6
 
3
7
  * Fix regex typo, thanks Sami Samhuri
data/README.md CHANGED
@@ -42,4 +42,4 @@ brew gem install jsonpretty
42
42
 
43
43
  ## LICENSE
44
44
 
45
- See [LICENSE](LICENSE).
45
+ See [LICENSE](LICENSE.txt).
data/go.mod ADDED
@@ -0,0 +1,3 @@
1
+ module github.com/nicksieger/jsonpretty
2
+
3
+ go 1.17
@@ -1,3 +1,3 @@
1
1
  class Jsonpretty
2
- VERSION = "1.1.2"
2
+ VERSION = "1.2.0"
3
3
  end
data/main.go ADDED
@@ -0,0 +1,199 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "os"
9
+ "regexp"
10
+ "strings"
11
+ )
12
+
13
+ var (
14
+ version = "dev"
15
+ commit = ""
16
+ date = ""
17
+ builtBy = ""
18
+ )
19
+
20
+ type JsonArg struct {
21
+ file *os.File
22
+ content []byte
23
+ }
24
+
25
+ func (j JsonArg) Close() error {
26
+ if j.file != nil {
27
+ return j.file.Close()
28
+ }
29
+ return nil
30
+ }
31
+
32
+ func usage(code int) {
33
+ fmt.Printf("usage: %s [args|@filename|@- (stdin)]\n", os.Args[0])
34
+ fmt.Printf("Parse and pretty-print JSON, either from stdin or from arguments concatenated together\n")
35
+ os.Exit(code)
36
+ }
37
+
38
+ func versionInfo() string {
39
+ if commit != "" && date != "" {
40
+ return fmt.Sprintf("%s (%s on %s)", version, commit[0:7], date[0:10])
41
+ }
42
+ return version
43
+ }
44
+
45
+ func checkFlags(arg string) {
46
+ if arg[0] == '-' && len(arg) > 1 {
47
+ code := 1
48
+ if arg == "-v" || strings.HasPrefix(arg, "--v") {
49
+ fmt.Printf("jsonpretty version %s\n", versionInfo())
50
+ os.Exit(0)
51
+ } else if arg == "-h" || strings.HasPrefix(arg, "--h") {
52
+ code = 0
53
+ }
54
+ if code == 1 {
55
+ fmt.Printf("unrecognized flag %s\n", arg)
56
+ }
57
+ usage(code)
58
+ }
59
+ }
60
+
61
+ func buildJsonSource(args []JsonArg) *bytes.Buffer {
62
+ buffer := bytes.NewBuffer(nil)
63
+ for _, arg := range args {
64
+ if arg.file != nil {
65
+ _, err := buffer.ReadFrom(arg.file)
66
+ if err != nil {
67
+ fmt.Fprintf(os.Stderr, "error reading file: %v\n", err)
68
+ }
69
+ } else {
70
+ _, err := buffer.Write(arg.content)
71
+ if err != nil {
72
+ fmt.Fprintf(os.Stderr, "error appending content: %v\n", err)
73
+ }
74
+ }
75
+ }
76
+ return buffer
77
+ }
78
+
79
+ func readArguments(args []string) *bytes.Buffer {
80
+ jsonArgs := []JsonArg{}
81
+ defer func() {
82
+ for _, j := range jsonArgs {
83
+ _ = j.Close()
84
+ }
85
+ }()
86
+ for _, arg := range args {
87
+ checkFlags(arg)
88
+
89
+ if arg == "-" || arg == "@-" {
90
+ jsonArgs = append(jsonArgs, JsonArg{file: os.Stdin})
91
+ } else if strings.HasPrefix(arg, "@") {
92
+ file, err := os.Open(arg[1:])
93
+ if err != nil {
94
+ fmt.Fprintf(os.Stderr, "skipping '%s' due to error: %v\n", arg, err)
95
+ continue
96
+ }
97
+ jsonArgs = append(jsonArgs, JsonArg{file: file})
98
+ } else {
99
+ jsonArgs = append(jsonArgs, JsonArg{content: []byte(arg)})
100
+ }
101
+ }
102
+
103
+ if len(jsonArgs) == 0 {
104
+ jsonArgs = append(jsonArgs, JsonArg{file: os.Stdin})
105
+ }
106
+ return buildJsonSource(jsonArgs)
107
+ }
108
+
109
+ var httpRegex *regexp.Regexp = regexp.MustCompile("^HTTP/\\d")
110
+
111
+ func parseHeaders(src *bytes.Buffer) ([]byte, []byte, error) {
112
+ headerBuffer := bytes.NewBuffer(nil)
113
+ line, err := src.ReadBytes('\n')
114
+ if err != nil && err != io.EOF {
115
+ return nil, nil, err
116
+ }
117
+
118
+ if httpRegex.Match(line) {
119
+ for {
120
+ _, err = headerBuffer.Write(line)
121
+ if err != nil {
122
+ return nil, nil, err
123
+ }
124
+ if (len(line) == 1 && line[0] == '\n') ||
125
+ (len(line) == 2 && line[0] == '\r' && line[1] == '\n') {
126
+ break
127
+ }
128
+
129
+ line, err = src.ReadBytes('\n')
130
+
131
+ if err == io.EOF {
132
+ _, err = headerBuffer.Write(line)
133
+ if err != nil {
134
+ return nil, nil, err
135
+ }
136
+ break
137
+ }
138
+
139
+ if err != nil && err != io.EOF {
140
+ return nil, nil, err
141
+ }
142
+ }
143
+ } else {
144
+ newsrc := bytes.NewBuffer(line)
145
+ _, err = newsrc.Write(src.Bytes())
146
+ if err != nil {
147
+ return nil, nil, err
148
+ }
149
+ src = newsrc
150
+ }
151
+ return headerBuffer.Bytes(), src.Bytes(), nil
152
+ }
153
+
154
+ var jsonpRegexp *regexp.Regexp = regexp.MustCompile("^([a-zA-Z$_][^ \t\r\n(]+)\\(")
155
+ var endingParen *regexp.Regexp = regexp.MustCompile("\\)[ \t\r\n]*$")
156
+
157
+ func cleanJsonp(jsonSrc []byte) ([]byte, string) {
158
+ match := jsonpRegexp.FindSubmatch(jsonSrc)
159
+ ending := endingParen.FindSubmatch(jsonSrc)
160
+ if match != nil && ending != nil {
161
+ return jsonSrc[len(match[0]) : len(jsonSrc)-len(ending[0])], string(match[1])
162
+ }
163
+ return jsonSrc, ""
164
+ }
165
+
166
+ func handleParseError(err error, jsonSrc []byte) {
167
+ if err != nil {
168
+ fmt.Fprintf(os.Stderr, "error parsing json: %v\ninput:\n%s\n", err, string(jsonSrc))
169
+ os.Exit(1)
170
+ }
171
+ }
172
+
173
+ func main() {
174
+ buffer := readArguments(os.Args[1:])
175
+ headers, jsonSrc, err := parseHeaders(buffer)
176
+ handleParseError(err, jsonSrc)
177
+
178
+ jsonSrc, jsonpName := cleanJsonp(jsonSrc)
179
+
180
+ if len(headers) > 0 {
181
+ fmt.Print(string(headers))
182
+ }
183
+
184
+ if jsonpName != "" {
185
+ fmt.Printf("jsonp method name: %s\n\n", jsonpName)
186
+ }
187
+
188
+ var jsonObj interface{}
189
+ err = json.Unmarshal(jsonSrc, &jsonObj)
190
+ handleParseError(err, jsonSrc)
191
+
192
+ enc := json.NewEncoder(os.Stdout)
193
+ enc.SetIndent("", " ")
194
+ err = enc.Encode(&jsonObj)
195
+ if err != nil {
196
+ fmt.Fprintf(os.Stderr, "error encoding json: %v\n", err)
197
+ os.Exit(1)
198
+ }
199
+ }
data/main_test.go ADDED
@@ -0,0 +1,180 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "fmt"
6
+ "os"
7
+ "testing"
8
+ )
9
+
10
+ type Testfile struct {
11
+ name string
12
+ content string
13
+ file *os.File
14
+ }
15
+
16
+ var stdinSaved *os.File = os.Stdin
17
+
18
+ func Setup(contents []string, t *testing.T) []Testfile {
19
+ var err error
20
+ os.Stdin, err = os.CreateTemp("", "stdin")
21
+ if err != nil {
22
+ t.FailNow()
23
+ }
24
+
25
+ testfiles := make([]Testfile, len(contents))
26
+ for i, c := range contents {
27
+ testfiles[i] = Testfile{content: c}
28
+ file, err := os.CreateTemp("", fmt.Sprintf("tf%d", i))
29
+ if err != nil {
30
+ t.Logf("unable to create tempfile for '%s'", c)
31
+ t.FailNow()
32
+ }
33
+ _, err = file.Write([]byte(c))
34
+ if err != nil {
35
+ t.Logf("unable to write tempfile with '%s'", c)
36
+ t.FailNow()
37
+ }
38
+ err = file.Close()
39
+ if err != nil {
40
+ t.Logf("unable to close tempfile for '%s'", c)
41
+ t.FailNow()
42
+ }
43
+ testfiles[i].file = file
44
+ testfiles[i].name = file.Name()
45
+ }
46
+ return testfiles
47
+ }
48
+
49
+ func TearDown(testfiles []Testfile) {
50
+ for _, tf := range testfiles {
51
+ _ = os.Remove(tf.name)
52
+ }
53
+ _ = os.Stdin.Close()
54
+ os.Stdin = stdinSaved
55
+ }
56
+
57
+ func assertEqual(expect []byte, actual []byte, t *testing.T) {
58
+ if bytes.Compare(expect, actual) != 0 {
59
+ t.Errorf("bytes did not match:\n. expected:\n %s\n actual:\n %s\n",
60
+ string(expect), string(actual))
61
+ }
62
+ }
63
+
64
+ func TestReadArguments(t *testing.T) {
65
+ files := Setup([]string{"1", "2", "{\"foo\": true, \"bar\": false}"}, t)
66
+ defer TearDown(files)
67
+
68
+ tests := [][]Testfile{
69
+ {}, // nothing
70
+ {Testfile{content: "{\"hello\":\"world\"}"}},
71
+ {Testfile{content: "["}, files[0], Testfile{content: ","}, files[1], Testfile{content: "]"}},
72
+ {files[2]},
73
+ }
74
+
75
+ for i, test := range tests {
76
+ t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) {
77
+ expected := bytes.NewBuffer(nil)
78
+ args := make([]string, len(test))
79
+ for j, testfile := range test {
80
+ if testfile.name != "" {
81
+ args[j] = fmt.Sprintf("@%s", testfile.name)
82
+ } else {
83
+ args[j] = testfile.content
84
+ }
85
+ expected.WriteString(testfile.content)
86
+ }
87
+ expect := expected.Bytes()
88
+ actual := readArguments(args)
89
+ assertEqual(expect, actual.Bytes(), t)
90
+ })
91
+ }
92
+ }
93
+
94
+ func TestReadArgumentsStdin(t *testing.T) {
95
+ f := Setup([]string{}, t)
96
+ defer TearDown(f)
97
+
98
+ expect := []byte("{\"foo\": true, \"bar\": false}")
99
+ _, err := os.Stdin.Write(expect)
100
+ if err != nil {
101
+ t.Logf("unable to write to stdin\n")
102
+ t.FailNow()
103
+ }
104
+ _ = os.Stdin.Close()
105
+
106
+ reopen := func(t *testing.T) {
107
+ f, err := os.Open(os.Stdin.Name())
108
+ if err != nil {
109
+ t.Logf("unable to reopen stdin")
110
+ t.FailNow()
111
+ }
112
+ os.Stdin = f
113
+ }
114
+
115
+ t.Run("no args", func(t *testing.T) {
116
+ reopen(t)
117
+ actual := readArguments([]string{})
118
+ assertEqual(expect, actual.Bytes(), t)
119
+ })
120
+
121
+ t.Run("-", func(t *testing.T) {
122
+ reopen(t)
123
+ actual := readArguments([]string{"-"})
124
+ assertEqual(expect, actual.Bytes(), t)
125
+ })
126
+
127
+ t.Run("@-", func(t *testing.T) {
128
+ reopen(t)
129
+ actual := readArguments([]string{"@-"})
130
+ assertEqual(expect, actual.Bytes(), t)
131
+ })
132
+ }
133
+
134
+ func TestParseHeaders(t *testing.T) {
135
+ tests := [][]string{
136
+ {"", "", ""},
137
+ {"{}", "", "{}"},
138
+ {"not http\n\nhello\nworld", "", "not http\n\nhello\nworld"},
139
+ {"HTTP/1.1 200 OK\nContent-Type: application/json\nContent-Length: 19\n\n{\"hello\":\"world\"}\n",
140
+ "HTTP/1.1 200 OK\nContent-Type: application/json\nContent-Length: 19\n\n",
141
+ "{\"hello\":\"world\"}\n"},
142
+ {"HTTP/1.1 200 OK\nContent-Type: application/json\nContent-Length: 0\n\n",
143
+ "HTTP/1.1 200 OK\nContent-Type: application/json\nContent-Length: 0\n\n",
144
+ ""},
145
+ {"myfunc(1,2,3)", "", "myfunc(1,2,3)"},
146
+ }
147
+
148
+ for i, test := range tests {
149
+ t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) {
150
+ buffer := bytes.NewBuffer(nil)
151
+ buffer.WriteString(test[0])
152
+ headers, jsonsrc, err := parseHeaders(buffer)
153
+ if err != nil {
154
+ t.Logf("error parseHeaders: %v", err)
155
+ t.FailNow()
156
+ }
157
+ assertEqual([]byte(test[1]), headers, t)
158
+ assertEqual([]byte(test[2]), jsonsrc, t)
159
+ })
160
+ }
161
+ }
162
+
163
+ func TestCleanJsonp(t *testing.T) {
164
+ tests := [][]string{
165
+ {"jsonp(true)", "true", "jsonp"},
166
+ {"true", "true", ""},
167
+ {"myJSFunc({\"hello\":\"world\"})", "{\"hello\":\"world\"}", "myJSFunc"},
168
+ {"[1,2,3]", "[1,2,3]", ""},
169
+ {"myfunc(1,2,3)", "1,2,3", "myfunc"},
170
+ {"myfunc(1,2,3)\n", "1,2,3", "myfunc"},
171
+ }
172
+
173
+ for i, test := range tests {
174
+ t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) {
175
+ jsonSrc, jsonpName := cleanJsonp([]byte(test[0]))
176
+ assertEqual([]byte(test[1]), jsonSrc, t)
177
+ assertEqual([]byte(test[2]), []byte(jsonpName), t)
178
+ })
179
+ }
180
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonpretty
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sieger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-27 00:00:00.000000000 Z
11
+ date: 2021-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -47,6 +47,7 @@ extensions: []
47
47
  extra_rdoc_files: []
48
48
  files:
49
49
  - ".gitignore"
50
+ - ".goreleaser.yaml"
50
51
  - CODE_OF_CONDUCT.md
51
52
  - Gemfile
52
53
  - History.txt
@@ -55,9 +56,12 @@ files:
55
56
  - README.md
56
57
  - Rakefile
57
58
  - exe/jsonpretty
59
+ - go.mod
58
60
  - jsonpretty.gemspec
59
61
  - lib/jsonpretty.rb
60
62
  - lib/jsonpretty/version.rb
63
+ - main.go
64
+ - main_test.go
61
65
  homepage: http://github.com/nicksieger/jsonpretty
62
66
  licenses:
63
67
  - MIT