json-next 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![](https://publicdomainworks.github.io/buttons/zero88x31.png)
|
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: []
|