json-next 1.1.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 +7 -0
- data/HISTORY.md +3 -0
- data/Manifest.txt +15 -0
- data/README.md +256 -0
- data/Rakefile +26 -0
- data/lib/json/next.rb +19 -0
- data/lib/json/next/commata.rb +171 -0
- data/lib/json/next/parser/hanson.rb +101 -0
- data/lib/json/next/parser/son.rb +45 -0
- data/lib/json/next/pattern.rb +127 -0
- data/lib/json/next/version.rb +24 -0
- data/test/helper.rb +10 -0
- data/test/test_commata.rb +92 -0
- data/test/test_parser_hanson.rb +52 -0
- data/test/test_parser_son.rb +41 -0
- data/test/test_version.rb +37 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a4bc912a3a1cfbfee8f003ec071192735ec5f638
|
4
|
+
data.tar.gz: 493c00f3d34dc0c3f868f28d52e254b07686b4c5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 717d9c8b9bdc8f63fb8abbc2de0e76b6d1355bb17edf30702c61449ff5ce9f739d1f9f3bac5c11eeb169d2488a04ef17f9624c63fdb1e614d713ab7bbdb19b1a
|
7
|
+
data.tar.gz: a57f3d1a612907827b3ae7decbff6bd55d963f214499a551fe166d6a427029d02f5a0ceee7b28ded0ee269a31282b14ca731beae9f007174ae6f7197626f1cb2
|
data/HISTORY.md
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
HISTORY.md
|
2
|
+
Manifest.txt
|
3
|
+
README.md
|
4
|
+
Rakefile
|
5
|
+
lib/json/next.rb
|
6
|
+
lib/json/next/commata.rb
|
7
|
+
lib/json/next/parser/hanson.rb
|
8
|
+
lib/json/next/parser/son.rb
|
9
|
+
lib/json/next/pattern.rb
|
10
|
+
lib/json/next/version.rb
|
11
|
+
test/helper.rb
|
12
|
+
test/test_commata.rb
|
13
|
+
test/test_parser_hanson.rb
|
14
|
+
test/test_parser_son.rb
|
15
|
+
test/test_version.rb
|
data/README.md
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
# jasony
|
2
|
+
|
3
|
+
jasony gem - read generation y / next generation json versions (HanSON, SON, etc.) with comments, unquoted keys, multi-line strings, trailing commas, optional commas, and more
|
4
|
+
|
5
|
+
|
6
|
+
* home :: [github.com/datatxt/jasony](https://github.com/datatxt/jasony)
|
7
|
+
* bugs :: [github.com/datatxt/jasony/issues](https://github.com/datatxt/jasony/issues)
|
8
|
+
* gem :: [rubygems.org/gems/jasony](https://rubygems.org/gems/jasony)
|
9
|
+
* rdoc :: [rubydoc.info/gems/jasony](http://rubydoc.info/gems/jasony)
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
## Usage - `HANSON.parse`, `SON.parse`
|
15
|
+
|
16
|
+
[HanSON](#hanson) •
|
17
|
+
[SON](#son)
|
18
|
+
|
19
|
+
|
20
|
+
### HanSON
|
21
|
+
|
22
|
+
_HanSON - JSON for Humans by Tim Jansen et al_
|
23
|
+
|
24
|
+
HanSON is an extension of JSON with a few simple additions to the spec:
|
25
|
+
|
26
|
+
- quotes for strings are optional if they follow JavaScript identifier rules.
|
27
|
+
- you can alternatively use backticks, as in ES6's template string literal, as quotes for strings.
|
28
|
+
A backtick-quoted string may span several lines and you are not required to escape regular quote characters,
|
29
|
+
only backticks. Backslashes still need to be escaped, and all other backslash-escape sequences work like in
|
30
|
+
regular JSON.
|
31
|
+
- for single-line strings, single quotes (`''`) are supported in addition to double quotes (`""`)
|
32
|
+
- you can use JavaScript comments, both single line (`//`) and multi-line comments (`/* */`), in all places where JSON allows whitespace.
|
33
|
+
- Commas after the last list element or object property will be ignored.
|
34
|
+
|
35
|
+
|
36
|
+
Example:
|
37
|
+
|
38
|
+
``` js
|
39
|
+
{
|
40
|
+
listName: "Sesame Street Monsters", // note that listName needs no quotes
|
41
|
+
content: [
|
42
|
+
{
|
43
|
+
name: "Cookie Monster",
|
44
|
+
/* Note the template quotes and unescaped regular quotes in the next string */
|
45
|
+
background: `Cookie Monster used to be a
|
46
|
+
monster that ate everything, especially cookies.
|
47
|
+
These days he is forced to eat "healthy" food.`
|
48
|
+
}, {
|
49
|
+
// You can single-quote strings too:
|
50
|
+
name: 'Herry Monster',
|
51
|
+
background: `Herry Monster is a furry blue monster with a purple nose.
|
52
|
+
He's mostly retired today.`
|
53
|
+
}, // don't worry, the trailing comma will be ignored
|
54
|
+
]
|
55
|
+
}
|
56
|
+
```
|
57
|
+
|
58
|
+
Use `HANSON.convert` to convert HanSON text to ye old' JSON text:
|
59
|
+
|
60
|
+
``` json
|
61
|
+
{
|
62
|
+
"listName": "Sesame Street Monsters",
|
63
|
+
"content": [
|
64
|
+
{ "name": "Cookie Monster",
|
65
|
+
"background": "Cookie Monster used to be a\n ... to eat \"healthy\" food."
|
66
|
+
},
|
67
|
+
{ "name": "Herry Monster",
|
68
|
+
"background": "Herry Monster is a furry blue monster with a purple nose.\n ... today."
|
69
|
+
}
|
70
|
+
]
|
71
|
+
}
|
72
|
+
```
|
73
|
+
|
74
|
+
Use `HANSON.parse` instead of `JSON.parse` to parse text to ruby hash / array / etc.:
|
75
|
+
|
76
|
+
``` ruby
|
77
|
+
{
|
78
|
+
"listName" => "Sesame Street Monsters",
|
79
|
+
"content" => [
|
80
|
+
{ "name" => "Cookie Monster",
|
81
|
+
"background" => "Cookie Monster used to be a\n ... to eat \"healthy\" food."
|
82
|
+
},
|
83
|
+
{ "name" => "Herry Monster",
|
84
|
+
"background" => "Herry Monster is a furry blue monster with a purple nose.\n ... today."
|
85
|
+
}
|
86
|
+
]
|
87
|
+
}
|
88
|
+
```
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
### SON
|
93
|
+
|
94
|
+
_SON - Simple Object Notation by Aleksander Gurin et al_
|
95
|
+
|
96
|
+
Simple data format similar to JSON, but with some minor changes:
|
97
|
+
|
98
|
+
- comments starts with `#` sign and ends with newline (`\n`)
|
99
|
+
- comma after an object key-value pair is optional
|
100
|
+
- comma after an array item is optional
|
101
|
+
|
102
|
+
JSON is compatible with SON in a sense that
|
103
|
+
JSON data is also SON data, but not vise versa.
|
104
|
+
|
105
|
+
Example:
|
106
|
+
|
107
|
+
```
|
108
|
+
{
|
109
|
+
# Personal information
|
110
|
+
|
111
|
+
"name": "Alexander Grothendieck"
|
112
|
+
"fields": "mathematics"
|
113
|
+
"main_topics": [
|
114
|
+
"Etale cohomology"
|
115
|
+
"Motives"
|
116
|
+
"Topos theory"
|
117
|
+
"Schemes"
|
118
|
+
]
|
119
|
+
"numbers": [1 2 3 4]
|
120
|
+
"mixed": [1.1 -2 true false null]
|
121
|
+
}
|
122
|
+
```
|
123
|
+
|
124
|
+
Use `SON.convert` to convert SON text to ye old' JSON text:
|
125
|
+
|
126
|
+
``` json
|
127
|
+
{
|
128
|
+
"name": "Alexander Grothendieck",
|
129
|
+
"fields": "mathematics",
|
130
|
+
"main_topics": [
|
131
|
+
"Etale cohomology",
|
132
|
+
"Motives",
|
133
|
+
"Topos theory",
|
134
|
+
"Schemes"
|
135
|
+
],
|
136
|
+
"numbers": [1, 2, 3, 4],
|
137
|
+
"mixed": [1.1, -2, true, false, null]
|
138
|
+
}
|
139
|
+
```
|
140
|
+
|
141
|
+
Use `SON.parse` instead of `JSON.parse` to parse text to ruby hash / array / etc.:
|
142
|
+
|
143
|
+
``` ruby
|
144
|
+
|
145
|
+
{
|
146
|
+
"name" => "Alexander Grothendieck",
|
147
|
+
"fields" => "mathematics",
|
148
|
+
"main_topics" =>
|
149
|
+
["Etale cohomology", "Motives", "Topos theory", "Schemes"],
|
150
|
+
"numbers" => [1, 2, 3, 4],
|
151
|
+
"mixed" => [1.1, -2, true, false, nil]
|
152
|
+
}
|
153
|
+
```
|
154
|
+
|
155
|
+
|
156
|
+
|
157
|
+
### Live Examples
|
158
|
+
|
159
|
+
|
160
|
+
``` ruby
|
161
|
+
require 'jasony'
|
162
|
+
|
163
|
+
text1 =<<TXT
|
164
|
+
{
|
165
|
+
listName: "Sesame Street Monsters", // note that listName needs no quotes
|
166
|
+
content: [
|
167
|
+
{
|
168
|
+
name: "Cookie Monster",
|
169
|
+
/* Note the template quotes and unescaped regular quotes in the next string */
|
170
|
+
background: `Cookie Monster used to be a
|
171
|
+
monster that ate everything, especially cookies.
|
172
|
+
These days he is forced to eat "healthy" food.`
|
173
|
+
}, {
|
174
|
+
// You can single-quote strings too:
|
175
|
+
name: 'Herry Monster',
|
176
|
+
background: `Herry Monster is a furry blue monster with a purple nose.
|
177
|
+
He's mostly retired today.`
|
178
|
+
}, // don't worry, the trailing comma will be ignored
|
179
|
+
]
|
180
|
+
}
|
181
|
+
TXT
|
182
|
+
|
183
|
+
pp HANSON.parse( text1 ) # note: is the same as JSON.parse( HANSON.convert( text ))
|
184
|
+
```
|
185
|
+
|
186
|
+
resulting in:
|
187
|
+
|
188
|
+
``` ruby
|
189
|
+
{
|
190
|
+
"listName" => "Sesame Street Monsters",
|
191
|
+
"content" => [
|
192
|
+
{ "name" => "Cookie Monster",
|
193
|
+
"background" => "Cookie Monster used to be a\n ... to eat \"healthy\" food."
|
194
|
+
},
|
195
|
+
{ "name" => "Herry Monster",
|
196
|
+
"background" => "Herry Monster is a furry blue monster with a purple nose.\n ... today."
|
197
|
+
}
|
198
|
+
]
|
199
|
+
}
|
200
|
+
```
|
201
|
+
|
202
|
+
and
|
203
|
+
|
204
|
+
``` ruby
|
205
|
+
|
206
|
+
text2 =<<TXT
|
207
|
+
{
|
208
|
+
# Personal information
|
209
|
+
|
210
|
+
"name": "Alexander Grothendieck"
|
211
|
+
"fields": "mathematics"
|
212
|
+
"main_topics": [
|
213
|
+
"Etale cohomology"
|
214
|
+
"Motives"
|
215
|
+
"Topos theory"
|
216
|
+
"Schemes"
|
217
|
+
]
|
218
|
+
"numbers": [1 2 3 4]
|
219
|
+
"mixed": [1.1 -2 true false null]
|
220
|
+
}
|
221
|
+
TXT
|
222
|
+
|
223
|
+
pp SON.parse( text2 ) # note: is the same as JSON.parse( SON.convert( text ))
|
224
|
+
```
|
225
|
+
|
226
|
+
resulting in:
|
227
|
+
|
228
|
+
``` ruby
|
229
|
+
{
|
230
|
+
"name" => "Alexander Grothendieck",
|
231
|
+
"fields" => "mathematics",
|
232
|
+
"main_topics" =>
|
233
|
+
["Etale cohomology", "Motives", "Topos theory", "Schemes"],
|
234
|
+
"numbers" => [1, 2, 3, 4],
|
235
|
+
"mixed" => [1.1, -2, true, false, nil]
|
236
|
+
}
|
237
|
+
```
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
## More JSON Formats
|
242
|
+
|
243
|
+
See the [Awesome JSON (What's Next?)](https://github.com/datatxt/awesome-json-next) collection / page.
|
244
|
+
|
245
|
+
|
246
|
+
|
247
|
+
## License
|
248
|
+
|
249
|
+

|
250
|
+
|
251
|
+
The `jasony` scripts are dedicated to the public domain.
|
252
|
+
Use it as you please with no restrictions whatsoever.
|
253
|
+
|
254
|
+
## Questions? Comments?
|
255
|
+
|
256
|
+
Post them to the [wwwmake forum](http://groups.google.com/group/wwwmake). Thanks!
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'hoe'
|
2
|
+
require './lib/json/next/version.rb'
|
3
|
+
|
4
|
+
Hoe.spec 'json-next' do
|
5
|
+
|
6
|
+
self.version = JSON::Next::VERSION
|
7
|
+
|
8
|
+
self.summary = 'json-next - read generation y / next generation json versions (HanSON, SON, etc.) with comments, unquoted keys, multi-line strings, trailing commas, optional commas, and more'
|
9
|
+
self.description = summary
|
10
|
+
|
11
|
+
self.urls = ['https://github.com/datatxt/json-next']
|
12
|
+
|
13
|
+
self.author = 'Gerald Bauer'
|
14
|
+
self.email = 'ruby-talk@ruby-lang.org'
|
15
|
+
|
16
|
+
# switch extension to .markdown for gihub formatting
|
17
|
+
self.readme_file = 'README.md'
|
18
|
+
self.history_file = 'HISTORY.md'
|
19
|
+
|
20
|
+
self.licenses = ['Public Domain']
|
21
|
+
|
22
|
+
self.spec_extras = {
|
23
|
+
required_ruby_version: '>= 1.9.2'
|
24
|
+
}
|
25
|
+
|
26
|
+
end
|
data/lib/json/next.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'strscan' ## StringScanner
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
|
8
|
+
# our own code
|
9
|
+
require 'json/next/version' # note: let version always go first
|
10
|
+
require 'json/next/pattern' # shared utils for "custom" parser
|
11
|
+
require 'json/next/commata'
|
12
|
+
|
13
|
+
require 'json/next/parser/hanson'
|
14
|
+
require 'json/next/parser/son'
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
# say hello
|
19
|
+
puts JSON::Next.banner if defined?( $RUBYLIBS_DEBUG )
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module JSON
|
4
|
+
module Next
|
5
|
+
|
6
|
+
|
7
|
+
### auto-add commas for objects and arrays
|
8
|
+
class Commata
|
9
|
+
|
10
|
+
|
11
|
+
## convenience helper
|
12
|
+
def self.convert( str, opts={} )
|
13
|
+
self.new.convert( str, opts )
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def convert( str, opts={} )
|
18
|
+
@debug = opts.fetch( :debug, false )
|
19
|
+
@out = ""
|
20
|
+
@buffer = StringScanner.new( str )
|
21
|
+
|
22
|
+
skip_whitespaces
|
23
|
+
parse_value
|
24
|
+
@out
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def debug?() @debug; end
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
def skip_whitespaces
|
33
|
+
@buffer.skip( /[ \t\n]*/ ) ## skip trailing WHITESPACE
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def parse_string
|
38
|
+
|
39
|
+
if @buffer.peek(1) == '"' ## double quote
|
40
|
+
@buffer.getch # consume double quote
|
41
|
+
value = @buffer.scan_until( /(?=")/) ## fix: allow escaped double quote e.g. \" too!!!
|
42
|
+
@buffer.getch # consume double quote
|
43
|
+
|
44
|
+
puts %{string value >>#{value}<<} if debug?
|
45
|
+
@out << ' "'
|
46
|
+
@out << value
|
47
|
+
@out << '" '
|
48
|
+
|
49
|
+
skip_whitespaces
|
50
|
+
else
|
51
|
+
puts "!! format error: string literal - expected opening quote (\") - rest is >>#{@buffer.rest}<<"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
def parse_object
|
58
|
+
|
59
|
+
if @buffer.peek(1) == '{'
|
60
|
+
@buffer.getch # consume '{'
|
61
|
+
@out << ' { '
|
62
|
+
skip_whitespaces
|
63
|
+
|
64
|
+
if @buffer.peek(1) == '}' ## empty object?
|
65
|
+
@buffer.getch # consume '{'
|
66
|
+
@out << ' } '
|
67
|
+
skip_whitespaces
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
71
|
+
loop do
|
72
|
+
parse_string
|
73
|
+
if @buffer.peek(1) == ':'
|
74
|
+
@buffer.getch # consume ':'
|
75
|
+
@out << ' : '
|
76
|
+
skip_whitespaces
|
77
|
+
|
78
|
+
parse_value
|
79
|
+
if @buffer.peek(1) == '}'
|
80
|
+
@buffer.getch # consume '}'
|
81
|
+
@out << ' } '
|
82
|
+
skip_whitespaces
|
83
|
+
return ## use break - why? why not?
|
84
|
+
else
|
85
|
+
if @buffer.peek(1) == ','
|
86
|
+
@buffer.getch # consume ','
|
87
|
+
@out << ' , '
|
88
|
+
skip_whitespaces
|
89
|
+
else
|
90
|
+
puts "object literal - auto-add comma for key-value pair" if debug?
|
91
|
+
@out << ' , '
|
92
|
+
end
|
93
|
+
end
|
94
|
+
else
|
95
|
+
puts "!! format error: object literal - expected colon (:) - rest is >>#{@buffer.rest}<<"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
else
|
99
|
+
puts "!! format error: object literal - expected curly open bracket ({) - rest is >>#{@buffer.rest}<<"
|
100
|
+
end
|
101
|
+
end # method parse_object
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
def parse_array
|
106
|
+
|
107
|
+
if @buffer.peek(1) == '['
|
108
|
+
@buffer.getch # consume '['
|
109
|
+
@out << ' [ '
|
110
|
+
skip_whitespaces
|
111
|
+
|
112
|
+
if @buffer.peek(1) == ']' ## empty array?
|
113
|
+
@buffer.getch # consume ']'
|
114
|
+
@out << ' ] '
|
115
|
+
skip_whitespaces
|
116
|
+
return
|
117
|
+
end
|
118
|
+
|
119
|
+
loop do
|
120
|
+
parse_value
|
121
|
+
if @buffer.peek(1) == ']'
|
122
|
+
@buffer.getch # consume ']'
|
123
|
+
@out << ' ] '
|
124
|
+
skip_whitespaces
|
125
|
+
return ## use break - why? why not?
|
126
|
+
else
|
127
|
+
if @buffer.peek(1) == ','
|
128
|
+
@buffer.getch # consume ','
|
129
|
+
@out << ' , '
|
130
|
+
skip_whitespaces
|
131
|
+
else
|
132
|
+
puts "array literal - auto-add comma for value" if debug?
|
133
|
+
@out << ' , '
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
else
|
138
|
+
puts "!! format error: array literal - expected square open bracket ([) - rest is >>#{@buffer.rest}<<"
|
139
|
+
end
|
140
|
+
end # method parse_array
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
def parse_value
|
145
|
+
if @buffer.peek(1) == '{'
|
146
|
+
parse_object
|
147
|
+
elsif @buffer.peek(1) == '['
|
148
|
+
parse_array
|
149
|
+
elsif @buffer.peek(1) == '"'
|
150
|
+
parse_string
|
151
|
+
else
|
152
|
+
## assume number or literal/identifier
|
153
|
+
value = @buffer.scan( /[_$a-zA-Z0-9.+\-]+/ )
|
154
|
+
puts %{literal value >>#{value}<<} if debug?
|
155
|
+
@out << " "
|
156
|
+
@out << value
|
157
|
+
@out << " "
|
158
|
+
|
159
|
+
skip_whitespaces
|
160
|
+
end
|
161
|
+
|
162
|
+
## todo/fix: check if eof reached ?? if not report warning - more data available??
|
163
|
+
## wrap in object ({}) or array ([])
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
end # class Commata
|
168
|
+
|
169
|
+
|
170
|
+
end # module Next
|
171
|
+
end # module JSON
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# based on github.com/timjansen/hanson
|
5
|
+
# Thanks to Tim Jansen
|
6
|
+
|
7
|
+
|
8
|
+
module HANSON
|
9
|
+
|
10
|
+
|
11
|
+
BACKTICK_ML_QUOTE = JSON::Next::BACKTICK_ML_QUOTE
|
12
|
+
SINGLE_QUOTE = JSON::Next::SINGLE_QUOTE
|
13
|
+
DOUBLE_QUOTE = JSON::Next::DOUBLE_QUOTE
|
14
|
+
|
15
|
+
CLANG_ML_COMMENT = JSON::Next::CLANG_ML_COMMENT
|
16
|
+
CLANG_COMMENT = JSON::Next::CLANG_COMMENT
|
17
|
+
|
18
|
+
KEYWORDS = JSON::Next::KEYWORDS
|
19
|
+
IDENTIFIER = JSON::Next::IDENTIFIER
|
20
|
+
TRAILING_COMMA = JSON::Next::TRAILING_COMMA
|
21
|
+
|
22
|
+
UNESCAPE_MAP = JSON::Next::UNESCAPE_MAP
|
23
|
+
ML_ESCAPE_MAP = JSON::Next::ML_ESCAPE_MAP
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
def self.strip_comments( text ) ## pass 1
|
28
|
+
text.gsub( /#{BACKTICK_ML_QUOTE}|#{SINGLE_QUOTE}|#{DOUBLE_QUOTE}|#{CLANG_ML_COMMENT}|#{CLANG_COMMENT}/ox ) do |match|
|
29
|
+
## puts "match: >>#{match}<< : #{match.class.name}"
|
30
|
+
if match[0] == ?/ ## comments start with // or /*
|
31
|
+
## puts "!!! removing comments"
|
32
|
+
'' ## remove / strip comments
|
33
|
+
else
|
34
|
+
match
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def self.normalize_quotes( text ) ## pass 2
|
41
|
+
text.gsub( /#{BACKTICK_ML_QUOTE}|#{SINGLE_QUOTE}|#{DOUBLE_QUOTE}/ox ) do |match|
|
42
|
+
## puts "match: >>#{match}<< : #{match.class.name}"
|
43
|
+
|
44
|
+
m = Regexp.last_match
|
45
|
+
if m[:backtick_ml_quote]
|
46
|
+
## puts "!!! ml_quote -- convert to double quotes"
|
47
|
+
str = m[:backtick_ml_quote]
|
48
|
+
str = str.gsub( /\\./ ) {|r| UNESCAPE_MAP[r] || r }
|
49
|
+
str = str.gsub( /[\n\r\t"]/ ) { |r| ML_ESCAPE_MAP[r] }
|
50
|
+
'"' + str + '"'
|
51
|
+
elsif m[:single_quote]
|
52
|
+
## puts "!!! single_quote -- convert to double quotes"
|
53
|
+
str = m[:single_quote]
|
54
|
+
str = str.gsub( /\\./ ) {|r| UNESCAPE_MAP[r] || r }
|
55
|
+
str = str.gsub( /"/, %{\\"} )
|
56
|
+
'"' + str + '"'
|
57
|
+
else
|
58
|
+
match
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def self.convert( text )
|
65
|
+
|
66
|
+
# text is the HanSON string to convert.
|
67
|
+
|
68
|
+
# todo: add keep_line_numbers options - why? why not?
|
69
|
+
# see github.com/timjansen/hanson
|
70
|
+
|
71
|
+
## pass 1: remove/strip comments
|
72
|
+
text = strip_comments( text )
|
73
|
+
|
74
|
+
## pass 2: requote/normalize quotes
|
75
|
+
text = normalize_quotes( text )
|
76
|
+
|
77
|
+
## pass 3: quote unquoted and remove trailing commas
|
78
|
+
text = text.gsub( /#{KEYWORDS}|#{IDENTIFIER}|#{DOUBLE_QUOTE}|#{TRAILING_COMMA}/ox ) do |match|
|
79
|
+
## puts "match: >>#{match}<< : #{match.class.name}"
|
80
|
+
|
81
|
+
m = Regexp.last_match
|
82
|
+
if m[:identifier]
|
83
|
+
## puts "!!! identfier -- wrap in double quotes"
|
84
|
+
'"' + m[:identifier] + '"'
|
85
|
+
elsif m[:trailing_comma]
|
86
|
+
## puts "!!! trailing comma -- remove"
|
87
|
+
''
|
88
|
+
else
|
89
|
+
match
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
text
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def self.parse( text )
|
98
|
+
JSON.parse( self.convert( text ) )
|
99
|
+
end
|
100
|
+
|
101
|
+
end # module HANSON
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# based on github.com/aleksandergurin/simple-object-notation
|
5
|
+
# Thanks to Aleksander Gurin
|
6
|
+
|
7
|
+
|
8
|
+
module SON
|
9
|
+
|
10
|
+
|
11
|
+
DOUBLE_QUOTE = JSON::Next::DOUBLE_QUOTE
|
12
|
+
|
13
|
+
SHELL_COMMENT = JSON::Next::SHELL_COMMENT
|
14
|
+
|
15
|
+
|
16
|
+
def self.strip_comments( text ) ## pass 1
|
17
|
+
text.gsub( /#{DOUBLE_QUOTE}|#{SHELL_COMMENT}/ox ) do |match|
|
18
|
+
## puts "match: >>#{match}<< : #{match.class.name}"
|
19
|
+
if match[0] == ?# ## comments start with #
|
20
|
+
## puts "!!! removing comments"
|
21
|
+
'' ## remove / strip comments
|
22
|
+
else
|
23
|
+
match
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
def self.convert( text )
|
32
|
+
|
33
|
+
# text is the SON string to convert.
|
34
|
+
|
35
|
+
text = strip_comments( text ) ## pass 1
|
36
|
+
text = JSON::Next::Commata.convert( text ) ## pass 2 - auto-add (missing optional) commas
|
37
|
+
text
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def self.parse( text )
|
42
|
+
JSON.parse( self.convert( text ) )
|
43
|
+
end
|
44
|
+
|
45
|
+
end # module SON
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module JSON
|
4
|
+
module Next
|
5
|
+
|
6
|
+
## note: regex pattern \\ needs to get escaped twice, thus, \\.
|
7
|
+
## and for literal \\ use \\\\\.
|
8
|
+
|
9
|
+
## todo: check for newlines [^] incl. newline ?
|
10
|
+
|
11
|
+
## note: add named captures for "inner" unquotes string values
|
12
|
+
|
13
|
+
###############
|
14
|
+
##### quotes
|
15
|
+
|
16
|
+
|
17
|
+
BACKTICK_ML_QUOTE = %< ` (?<backtick_ml_quote>
|
18
|
+
(?:
|
19
|
+
\\\\. | [^`]
|
20
|
+
)*
|
21
|
+
)
|
22
|
+
`
|
23
|
+
>
|
24
|
+
|
25
|
+
## => \\\\. -- allow backslash escapes e.g. \n \t \\ etc.
|
26
|
+
## => [^`] -- everything except backquote
|
27
|
+
|
28
|
+
## todo/fix - check if [^`] includes/matches newline too (yes)? -- add \n for multi-line!
|
29
|
+
|
30
|
+
|
31
|
+
SINGLE_QUOTE = %< ' (?<single_quote>
|
32
|
+
(?:
|
33
|
+
\\\\. | [^']
|
34
|
+
)*
|
35
|
+
)
|
36
|
+
'
|
37
|
+
>
|
38
|
+
|
39
|
+
DOUBLE_QUOTE = %< " (?<double_quote>
|
40
|
+
(?:
|
41
|
+
\\\\. | [^"]
|
42
|
+
)*
|
43
|
+
)
|
44
|
+
"
|
45
|
+
>
|
46
|
+
|
47
|
+
|
48
|
+
#######################
|
49
|
+
#### comments
|
50
|
+
|
51
|
+
CLANG_ML_COMMENT = %< /\\*
|
52
|
+
.*?
|
53
|
+
\\*/
|
54
|
+
>
|
55
|
+
|
56
|
+
## use . instead of [^] - why? why not?
|
57
|
+
## note: check if . incl. newlines too (only in multi-line (m) option - why? why not??
|
58
|
+
## fix/todo: include newline!!! \n - for multi-line!!!
|
59
|
+
|
60
|
+
## note: *? is NON-greedy
|
61
|
+
|
62
|
+
|
63
|
+
CLANG_COMMENT = %< //
|
64
|
+
.*?
|
65
|
+
(?:
|
66
|
+
\\n | $
|
67
|
+
)
|
68
|
+
>
|
69
|
+
|
70
|
+
## note: check if . incl. newlines too (only in multi-line (m) option - why? why not??
|
71
|
+
## note: *? is NON-greedy
|
72
|
+
|
73
|
+
|
74
|
+
SHELL_COMMENT = %< [#]
|
75
|
+
.*?
|
76
|
+
(?:
|
77
|
+
\\n | $
|
78
|
+
)
|
79
|
+
>
|
80
|
+
|
81
|
+
## note: use [#] instead of # to avoid confusion with # comment in regex
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
KEYWORDS = %<
|
87
|
+
(?:
|
88
|
+
true | false | null
|
89
|
+
)
|
90
|
+
(?=
|
91
|
+
[^\\w_$] | $
|
92
|
+
)
|
93
|
+
>
|
94
|
+
|
95
|
+
IDENTIFIER = %<
|
96
|
+
(?<identifier>
|
97
|
+
[a-zA-Z_$]
|
98
|
+
[\\w_$]*
|
99
|
+
)
|
100
|
+
>
|
101
|
+
|
102
|
+
TRAILING_COMMA = %<
|
103
|
+
(?<trailing_comma>,)
|
104
|
+
(?=
|
105
|
+
\\s*
|
106
|
+
[}\\]]
|
107
|
+
)
|
108
|
+
>
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
UNESCAPE_MAP = {
|
113
|
+
%<\\"> => %<">, ## "\\\"" => "\"",
|
114
|
+
%<\\`> => %<`>, ## "\\`" => "`",
|
115
|
+
%<\\'> => %<'> ## "\\'" => "'"
|
116
|
+
}
|
117
|
+
|
118
|
+
ML_ESCAPE_MAP = {
|
119
|
+
%<\n> => %<\\n>, ## "\n" => "\\n",
|
120
|
+
%<\r> => %<\\r>, ## "\r" => "\\r",
|
121
|
+
%<\t> => %<\\t>, ## "\t" => "\\t",
|
122
|
+
%<"> => %<\\"> ## "\"" => "\\\""
|
123
|
+
}
|
124
|
+
|
125
|
+
|
126
|
+
end # module Next
|
127
|
+
end # module JSON
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module JSON
|
4
|
+
module Next
|
5
|
+
|
6
|
+
MAJOR = 1 ## todo: namespace inside version or something - why? why not??
|
7
|
+
MINOR = 1
|
8
|
+
PATCH = 0
|
9
|
+
VERSION = [MAJOR,MINOR,PATCH].join('.')
|
10
|
+
|
11
|
+
def self.version
|
12
|
+
VERSION
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.banner
|
16
|
+
"json-next/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.root
|
20
|
+
"#{File.expand_path( File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) )}"
|
21
|
+
end
|
22
|
+
|
23
|
+
end # module Next
|
24
|
+
end # module JSON
|
data/test/helper.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_commata.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
class TestCommata < MiniTest::Test
|
12
|
+
|
13
|
+
def test_commata
|
14
|
+
|
15
|
+
sample1a =<<TXT
|
16
|
+
{
|
17
|
+
"name": "Alexander Grothendieck"
|
18
|
+
"fields": "mathematics"
|
19
|
+
"main_topics": [
|
20
|
+
"Etale cohomology"
|
21
|
+
"Motives"
|
22
|
+
"Topos theory"
|
23
|
+
"Schemes"
|
24
|
+
]
|
25
|
+
}
|
26
|
+
TXT
|
27
|
+
|
28
|
+
exp_sample1b = {
|
29
|
+
"name" => "Alexander Grothendieck",
|
30
|
+
"fields" => "mathematics",
|
31
|
+
"main_topics" =>
|
32
|
+
["Etale cohomology", "Motives", "Topos theory", "Schemes"]
|
33
|
+
}
|
34
|
+
|
35
|
+
|
36
|
+
sample2a =<<TXT
|
37
|
+
{
|
38
|
+
"nested": {
|
39
|
+
"name": "Alexander Grothendieck"
|
40
|
+
"fields": "mathematics"
|
41
|
+
"array": ["one" "two"] }
|
42
|
+
"numbers": [1 2 3 4 5]
|
43
|
+
"more_numbers": [1.1 2.0 0.3 4.444 -5.1]
|
44
|
+
"mixed": [1 true false null]
|
45
|
+
}
|
46
|
+
TXT
|
47
|
+
|
48
|
+
sample2a1 =<<TXT
|
49
|
+
{
|
50
|
+
"nested": {
|
51
|
+
"name": "Alexander Grothendieck",
|
52
|
+
"fields": "mathematics",
|
53
|
+
"array": ["one", "two"] },
|
54
|
+
"numbers": [1, 2, 3, 4, 5],
|
55
|
+
"more_numbers": [1.1, 2.0, 0.3, 4.444, -5.1],
|
56
|
+
"mixed": [1, true, false, null]
|
57
|
+
}
|
58
|
+
TXT
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
exp_sample2b = {
|
63
|
+
"nested" => {
|
64
|
+
"name" => "Alexander Grothendieck",
|
65
|
+
"fields" => "mathematics",
|
66
|
+
"array" => ["one", "two"]
|
67
|
+
},
|
68
|
+
"numbers" => [1, 2, 3, 4, 5],
|
69
|
+
"more_numbers" => [1.1, 2.0, 0.3, 4.444, -5.1],
|
70
|
+
"mixed" => [1, true, false, nil]
|
71
|
+
}
|
72
|
+
|
73
|
+
|
74
|
+
sample1b = JSON::Next::Commata.convert( sample1a, debug: true )
|
75
|
+
puts sample1b
|
76
|
+
|
77
|
+
assert_equal exp_sample1b, JSON.parse( sample1b )
|
78
|
+
|
79
|
+
|
80
|
+
sample2b = JSON::Next::Commata.convert( sample2a, debug: true )
|
81
|
+
puts sample2b
|
82
|
+
|
83
|
+
assert_equal exp_sample2b, JSON.parse( sample2b )
|
84
|
+
|
85
|
+
|
86
|
+
sample2b1 = JSON::Next::Commata.convert( sample2a1, debug: true )
|
87
|
+
puts sample2b1
|
88
|
+
|
89
|
+
assert_equal exp_sample2b, JSON.parse( sample2b1 )
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_parser_hanson.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
class TestParser < MiniTest::Test
|
12
|
+
|
13
|
+
def test_hanson
|
14
|
+
exp_sample1 = {
|
15
|
+
"listName"=>"Sesame Street Monsters",
|
16
|
+
"content"=>
|
17
|
+
[{"name"=>"Cookie Monster",
|
18
|
+
"background"=> "Cookie Monster used to be a\n monster that ate everything, especially cookies.\n These days he is forced to eat \"healthy\" food."
|
19
|
+
},
|
20
|
+
{"name"=>"Herry Monster",
|
21
|
+
"background"=> "Herry Monster is a furry blue monster with a purple nose.\n He's mostly retired today."
|
22
|
+
}
|
23
|
+
]}
|
24
|
+
|
25
|
+
sample1 =<<TXT
|
26
|
+
{
|
27
|
+
listName: "Sesame Street Monsters", // note that listName needs no quotes
|
28
|
+
content: [
|
29
|
+
{
|
30
|
+
name: "Cookie Monster",
|
31
|
+
/* Note the template quotes and unescaped regular quotes in the next string */
|
32
|
+
background: `Cookie Monster used to be a
|
33
|
+
monster that ate everything, especially cookies.
|
34
|
+
These days he is forced to eat "healthy" food.`
|
35
|
+
}, {
|
36
|
+
// You can single-quote strings too:
|
37
|
+
name: 'Herry Monster',
|
38
|
+
background: `Herry Monster is a furry blue monster with a purple nose.
|
39
|
+
He's mostly retired today.`
|
40
|
+
}, // don't worry, the trailing comma will be ignored
|
41
|
+
]
|
42
|
+
}
|
43
|
+
TXT
|
44
|
+
|
45
|
+
puts HANSON.convert( sample1 )
|
46
|
+
|
47
|
+
pp HANSON.parse( sample1 )
|
48
|
+
|
49
|
+
assert_equal exp_sample1, HANSON.parse( sample1 )
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_parser_son.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
class TestParserSon < MiniTest::Test
|
12
|
+
|
13
|
+
def test_son
|
14
|
+
exp_sample1 = {
|
15
|
+
"name"=>"Alexander Grothendieck",
|
16
|
+
"fields"=>"mathematics",
|
17
|
+
"main_topics"=>
|
18
|
+
["Etale cohomology", "Motives", "Topos theory", "Schemes"]
|
19
|
+
}
|
20
|
+
|
21
|
+
sample1 =<<TXT
|
22
|
+
{
|
23
|
+
# Personal information
|
24
|
+
|
25
|
+
"name": "Alexander Grothendieck"
|
26
|
+
"fields": "mathematics"
|
27
|
+
"main_topics": [
|
28
|
+
"Etale cohomology"
|
29
|
+
"Motives"
|
30
|
+
"Topos theory"
|
31
|
+
"Schemes"
|
32
|
+
]
|
33
|
+
}
|
34
|
+
TXT
|
35
|
+
|
36
|
+
puts SON.convert( sample1 )
|
37
|
+
|
38
|
+
assert_equal exp_sample1, SON.parse( sample1 )
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_version.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
class TestVersion < MiniTest::Test
|
12
|
+
|
13
|
+
|
14
|
+
def test_version
|
15
|
+
|
16
|
+
pp JSON::Next::UNESCAPE_MAP
|
17
|
+
pp JSON::Next::ML_ESCAPE_MAP
|
18
|
+
|
19
|
+
pp JSON::Next::BACKTICK_ML_QUOTE
|
20
|
+
puts JSON::Next::BACKTICK_ML_QUOTE
|
21
|
+
pp JSON::Next::SINGLE_QUOTE
|
22
|
+
puts JSON::Next::SINGLE_QUOTE
|
23
|
+
pp JSON::Next::DOUBLE_QUOTE
|
24
|
+
puts JSON::Next::DOUBLE_QUOTE
|
25
|
+
|
26
|
+
pp JSON::Next::CLANG_ML_COMMENT
|
27
|
+
puts JSON::Next::CLANG_ML_COMMENT
|
28
|
+
pp JSON::Next::CLANG_COMMENT
|
29
|
+
puts JSON::Next::CLANG_COMMENT
|
30
|
+
|
31
|
+
|
32
|
+
puts JSON::Next::VERSION
|
33
|
+
assert true
|
34
|
+
## assume everything ok if get here
|
35
|
+
end
|
36
|
+
|
37
|
+
end # class TestVersion
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: json-next
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gerald Bauer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rdoc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hoe
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.15'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.15'
|
41
|
+
description: json-next - read generation y / next generation json versions (HanSON,
|
42
|
+
SON, etc.) with comments, unquoted keys, multi-line strings, trailing commas, optional
|
43
|
+
commas, and more
|
44
|
+
email: ruby-talk@ruby-lang.org
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files:
|
48
|
+
- HISTORY.md
|
49
|
+
- Manifest.txt
|
50
|
+
- README.md
|
51
|
+
files:
|
52
|
+
- HISTORY.md
|
53
|
+
- Manifest.txt
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- lib/json/next.rb
|
57
|
+
- lib/json/next/commata.rb
|
58
|
+
- lib/json/next/parser/hanson.rb
|
59
|
+
- lib/json/next/parser/son.rb
|
60
|
+
- lib/json/next/pattern.rb
|
61
|
+
- lib/json/next/version.rb
|
62
|
+
- test/helper.rb
|
63
|
+
- test/test_commata.rb
|
64
|
+
- test/test_parser_hanson.rb
|
65
|
+
- test/test_parser_son.rb
|
66
|
+
- test/test_version.rb
|
67
|
+
homepage: https://github.com/datatxt/json-next
|
68
|
+
licenses:
|
69
|
+
- Public Domain
|
70
|
+
metadata: {}
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options:
|
73
|
+
- "--main"
|
74
|
+
- README.md
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 1.9.2
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 2.6.7
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: json-next - read generation y / next generation json versions (HanSON, SON,
|
93
|
+
etc.) with comments, unquoted keys, multi-line strings, trailing commas, optional
|
94
|
+
commas, and more
|
95
|
+
test_files: []
|