xjson 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.rdoc +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +3 -0
- data/doc/Xjson.html +1669 -0
- data/doc/Xjson/XjsonIncludeError.html +124 -0
- data/doc/Xjson/XjsonReferenceError.html +124 -0
- data/doc/_index.html +124 -0
- data/doc/class_list.html +51 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +58 -0
- data/doc/css/style.css +496 -0
- data/doc/file.CHANGELOG.html +79 -0
- data/doc/file.README.html +74 -0
- data/doc/file_list.html +61 -0
- data/doc/frames.html +17 -0
- data/doc/index.html +74 -0
- data/doc/js/app.js +314 -0
- data/doc/js/full_list.js +216 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +195 -0
- data/doc/top-level-namespace.html +110 -0
- data/lib/xjson.rb +270 -0
- data/markdown/README.md +130 -0
- data/test/golden/test.json +50 -0
- data/test/input/inc-arr.ext.json +4 -0
- data/test/input/inc-hash.ext.json +4 -0
- data/test/input/test.ext.json +33 -0
- data/test/test_xjson.rb +30 -0
- metadata +72 -0
data/lib/xjson.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'json'
|
2
|
+
# require 'byebug'
|
3
|
+
|
4
|
+
class Xjson
|
5
|
+
|
6
|
+
class XjsonIncludeError < RuntimeError; end
|
7
|
+
class XjsonReferenceError < RuntimeError; end
|
8
|
+
|
9
|
+
VERSION = "0.0.2"
|
10
|
+
def Xjson.version
|
11
|
+
Xjson::VERSION
|
12
|
+
end
|
13
|
+
|
14
|
+
def Xjson.load( filename )
|
15
|
+
Marshal.load( File.read( filename ) )
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :ext_data
|
19
|
+
attr_reader :data
|
20
|
+
attr_reader :dir
|
21
|
+
|
22
|
+
def initialize( xjson_file )
|
23
|
+
@cur_file = []
|
24
|
+
@cur_data = []
|
25
|
+
@ext_data = {}
|
26
|
+
@ext_data = read_json_file( xjson_file )
|
27
|
+
@data = expand( @ext_data )
|
28
|
+
end
|
29
|
+
|
30
|
+
# Read xjson file.
|
31
|
+
def read_json_file( xjson_file )
|
32
|
+
@cur_file.unshift xjson_file
|
33
|
+
if xjson_file[0] != "<"
|
34
|
+
JSON.parse( File.read( xjson_file ) )
|
35
|
+
else
|
36
|
+
JSON.parse( STDIN.read )
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Write expanded json file.
|
41
|
+
def write_json_file( json_file )
|
42
|
+
File.write( json_file, JSON.pretty_generate( @data ) + "\n" )
|
43
|
+
end
|
44
|
+
|
45
|
+
# Flatten by one level within array.
|
46
|
+
def flatten( data )
|
47
|
+
case data
|
48
|
+
when Array;
|
49
|
+
res = []
|
50
|
+
data.each do |i|
|
51
|
+
if i.class == Array
|
52
|
+
res += i
|
53
|
+
else
|
54
|
+
res.push i
|
55
|
+
end
|
56
|
+
end
|
57
|
+
res
|
58
|
+
else
|
59
|
+
data
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def find_in_array_of_hash( scope, key, value )
|
64
|
+
index = 0
|
65
|
+
while index < scope.length
|
66
|
+
if Regexp.new( value).match( scope[ index ][ key ] )
|
67
|
+
return index
|
68
|
+
end
|
69
|
+
index += 1
|
70
|
+
end
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def reference_handle( data, ref_desc )
|
75
|
+
if ref_desc[0] != ":"
|
76
|
+
reference_handle( data, ":#{ref_desc}" )
|
77
|
+
else
|
78
|
+
# Relative reference from root.
|
79
|
+
path = ref_desc.split( ":" )[1..-1]
|
80
|
+
scope = data
|
81
|
+
while path[0..-2].any?
|
82
|
+
if path[0] == "*"
|
83
|
+
# Wildcard for array.
|
84
|
+
unless path[1] && path[2]
|
85
|
+
raise XjsonReferenceError,
|
86
|
+
"Invalid reference: \"#{ref_desc}\" in \"#{@cur_file[0]}\", missing match key and value ..."
|
87
|
+
end
|
88
|
+
index = find_in_array_of_hash( scope, path[1], path[2] )
|
89
|
+
unless index
|
90
|
+
raise XjsonReferenceError,
|
91
|
+
"Invalid reference: \"#{ref_desc}\" in \"#{@cur_file[0]}\", key and value not matched ..."
|
92
|
+
end
|
93
|
+
scope = scope[ index ]
|
94
|
+
path.shift( 2 )
|
95
|
+
else
|
96
|
+
begin
|
97
|
+
index = Integer( path[0] )
|
98
|
+
scope = scope[ index ]
|
99
|
+
rescue
|
100
|
+
scope = scope[ path[0] ]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
path.shift
|
104
|
+
unless scope
|
105
|
+
raise XjsonReferenceError,
|
106
|
+
"Invalid reference: \"#{ref_desc}\" in \"#{@cur_file[0]}\"..."
|
107
|
+
end
|
108
|
+
end
|
109
|
+
[ scope, path[-1] ]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def reference( data, ref_desc )
|
114
|
+
path, label = reference_handle( data, ref_desc )
|
115
|
+
begin
|
116
|
+
index = Integer( label )
|
117
|
+
scope = path[ index ]
|
118
|
+
rescue
|
119
|
+
# scope = scope[ path[0] ]
|
120
|
+
scope = path[ label ]
|
121
|
+
end
|
122
|
+
scope
|
123
|
+
end
|
124
|
+
|
125
|
+
def override_desc( data, exp )
|
126
|
+
path, label = reference_handle( data, exp[0] )
|
127
|
+
{ path: path, label: label, value: exp[1] }
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def override_apply( desc, overwrite = false )
|
132
|
+
if desc[:label] == "*"
|
133
|
+
desc[:path].each do |place|
|
134
|
+
if not( place[ desc[:value][0] ] ) || overwrite
|
135
|
+
place[ desc[:value][0] ] = desc[:value][1]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
else
|
139
|
+
if not( desc[:path][desc[:label]] ) || overwrite
|
140
|
+
desc[:path][desc[:label]] = desc[:value]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
def override( data, exp, overwrite = false )
|
147
|
+
desc = override_desc( data, exp )
|
148
|
+
override_apply( desc, overwrite )
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
# Expand json recursively.
|
153
|
+
def expand( data )
|
154
|
+
|
155
|
+
case data
|
156
|
+
|
157
|
+
when TrueClass; data
|
158
|
+
|
159
|
+
when FalseClass; data
|
160
|
+
|
161
|
+
when Float; data
|
162
|
+
|
163
|
+
when Integer; data
|
164
|
+
|
165
|
+
when String; data
|
166
|
+
|
167
|
+
when Array;
|
168
|
+
ret = []
|
169
|
+
@cur_data.unshift ret
|
170
|
+
data.each do |v|
|
171
|
+
value = expand( v )
|
172
|
+
ret.push( value ) if value
|
173
|
+
end
|
174
|
+
@cur_data.shift
|
175
|
+
ret
|
176
|
+
|
177
|
+
when Hash
|
178
|
+
|
179
|
+
if data.size == 1
|
180
|
+
|
181
|
+
# Most possible extension.
|
182
|
+
|
183
|
+
k, v = data.first
|
184
|
+
|
185
|
+
case k
|
186
|
+
|
187
|
+
when "@eval"
|
188
|
+
%x"#{expand(v)}".split("\n")
|
189
|
+
|
190
|
+
when "@env"
|
191
|
+
ENV[expand(v)]
|
192
|
+
|
193
|
+
when "@join"
|
194
|
+
flatten( v[1..-1].map{|i| expand( i )} ).join( v[0] )
|
195
|
+
|
196
|
+
when "@flat"
|
197
|
+
flatten( v[1..-1].map{|i| expand( i )} )
|
198
|
+
|
199
|
+
when "@self"
|
200
|
+
reference( @ext_data, expand(v) )
|
201
|
+
|
202
|
+
when "@over";
|
203
|
+
override( @cur_data[0], expand(v), true )
|
204
|
+
nil
|
205
|
+
|
206
|
+
when "@base";
|
207
|
+
override( @cur_data[0], expand(v), false )
|
208
|
+
nil
|
209
|
+
|
210
|
+
when "@null"
|
211
|
+
nil
|
212
|
+
|
213
|
+
when "@include";
|
214
|
+
jsonfile = expand(v)
|
215
|
+
subdata = read_json_file( jsonfile )
|
216
|
+
expdata = expand( subdata )
|
217
|
+
if expdata.class == Hash
|
218
|
+
if @cur_data[0].class == expdata.class
|
219
|
+
expdata.each do |ke,ve|
|
220
|
+
@cur_data[0][ ke ] = ve
|
221
|
+
end
|
222
|
+
else
|
223
|
+
raise XjsonIncludeError,
|
224
|
+
"Included file (\"#{jsonfile}\") must contain a hash as top level"
|
225
|
+
end
|
226
|
+
elsif expdata.class == Array
|
227
|
+
expdata.each do |ve|
|
228
|
+
@cur_data[0].push ve
|
229
|
+
end
|
230
|
+
else
|
231
|
+
raise XjsonIncludeError,
|
232
|
+
"Included file (\"#{jsonfile}\") must contain a hash or a an array as top level"
|
233
|
+
end
|
234
|
+
@cur_file.shift
|
235
|
+
nil
|
236
|
+
|
237
|
+
else
|
238
|
+
# Non-extension.
|
239
|
+
{ k => expand( v ) }
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
else
|
244
|
+
ret = {}
|
245
|
+
@cur_data.unshift ret
|
246
|
+
data.each do |k,v|
|
247
|
+
case k
|
248
|
+
when "@null"; nil
|
249
|
+
else
|
250
|
+
value = expand( v )
|
251
|
+
ret[ k ] = value if value
|
252
|
+
end
|
253
|
+
end
|
254
|
+
@cur_data.shift
|
255
|
+
ret
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Return JSON data as string.
|
261
|
+
def to_s
|
262
|
+
JSON.pretty_generate( @data )
|
263
|
+
end
|
264
|
+
|
265
|
+
# Dump JSON data as marshal.
|
266
|
+
def dump( filename )
|
267
|
+
File.write( filename, Marshal.dump( @data ) )
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
data/markdown/README.md
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# Overview
|
2
|
+
|
3
|
+
Xjson is an extension to JSON format. The Xjson library processes the
|
4
|
+
extensions and outputs standard JSON.
|
5
|
+
|
6
|
+
Xjson (as JSON format) is compatible with standard JSON, i.e. the
|
7
|
+
syntax is the same and Xjson can be processed with JSON tools. The
|
8
|
+
extensions are special Xjson keywords that have semantics which the
|
9
|
+
Xjson processor manages.
|
10
|
+
|
11
|
+
Xjson renders JSON databases more dynamic and easy to maintain. The
|
12
|
+
same could be achieved with a separate pre-processor, but Xjson
|
13
|
+
provides tighter integration to JSON. This means that JSON aware text
|
14
|
+
editors can be utilized and the JSON file (with extensions) looks and
|
15
|
+
feels like a "normal" JSON file.
|
16
|
+
|
17
|
+
|
18
|
+
# Extensions
|
19
|
+
|
20
|
+
The extensions provide dynamic behaviour and modularity support.
|
21
|
+
|
22
|
+
* `@eval`: Evaluate "system"/"shell" command.
|
23
|
+
|
24
|
+
* `@env`: Reference an environment variable.
|
25
|
+
|
26
|
+
* `@join`: Join pieces of strings with given separator.
|
27
|
+
|
28
|
+
* `@flat`: Flatten the list by one level.
|
29
|
+
|
30
|
+
* `@self`: Refer to existing key/value pair, i.e. support for variables.
|
31
|
+
|
32
|
+
* `@over`: Overwrite existing value or create if not existing.
|
33
|
+
|
34
|
+
* `@base`: Set base (default) value, if not existing.
|
35
|
+
|
36
|
+
* `@null`: No operation.
|
37
|
+
|
38
|
+
* `@include`: Expand another Xjson/JSON file inplace.
|
39
|
+
|
40
|
+
|
41
|
+
Example:
|
42
|
+
|
43
|
+
```
|
44
|
+
{
|
45
|
+
"opts": [
|
46
|
+
{ "@eval": "/prj/sbin/list_opts" },
|
47
|
+
{ "@join": [ " ",
|
48
|
+
"-conf_file",
|
49
|
+
{ "@join": [ "/",
|
50
|
+
{ "@self": "workdir" },
|
51
|
+
"unit/data.txt" ] }
|
52
|
+
]
|
53
|
+
}
|
54
|
+
],
|
55
|
+
"sub-defs": { "@include": "submodule.json" }
|
56
|
+
}
|
57
|
+
|
58
|
+
```
|
59
|
+
|
60
|
+
Extensions are captured within a Hash with one key/value pair. The
|
61
|
+
pair will be recursively processed, and hence the extensions can be
|
62
|
+
nested.
|
63
|
+
|
64
|
+
Value of "opts" is an array, and the extensions can be directly stored
|
65
|
+
as array members.
|
66
|
+
|
67
|
+
"sub-defs" gives a unique label (key) for the extension, and each key
|
68
|
+
must be unique in JSON, or it will be silently overwritten and
|
69
|
+
disappear.
|
70
|
+
|
71
|
+
Hence depending on the context of the extension, it should be written
|
72
|
+
either with or without the extension label.
|
73
|
+
|
74
|
+
Extension arguments and results:
|
75
|
+
|
76
|
+
```
|
77
|
+
@eval: <string> => <string>
|
78
|
+
@env: <string> => <string>
|
79
|
+
@join: <separator>, <list-of-strings> => <string>
|
80
|
+
@flat: <array-of-atoms-or-arrays> => <array>
|
81
|
+
@self: <key-reference> => <value>
|
82
|
+
@over: <key-reference>, <value> => -
|
83
|
+
@base: <key-reference>, <value> => -
|
84
|
+
@null: false => -
|
85
|
+
@include: <filename-string> => <hash>
|
86
|
+
|
87
|
+
```
|
88
|
+
|
89
|
+
# Key Reference
|
90
|
+
|
91
|
+
Key Reference (KR) is used to refer the items in the database
|
92
|
+
itself. It can be used with `@self`, `@over`, and `@base` extensions.
|
93
|
+
|
94
|
+
Key Reference is a String, where ":" is used as hierarchy
|
95
|
+
separator. The string should start with ":" to designate the root, but
|
96
|
+
when if a pure top-entry reference is performed, then a plain key name
|
97
|
+
is accepted (e.g. just "opts", and not ":opts").
|
98
|
+
|
99
|
+
Key Reference is split into path selectors with the ":" character. The
|
100
|
+
path selectors are used one by one. Typically the path selector is a
|
101
|
+
key and the corresponding value is selected to be used for the next
|
102
|
+
path selector.
|
103
|
+
|
104
|
+
However, for arrays, the path selector can be an index. The index
|
105
|
+
selects the Nth item in the array.
|
106
|
+
|
107
|
+
In addition to selecting keys and indexed items, there is a wildcard
|
108
|
+
selector. The "*" character is used as wildcard. Wildcard behaves
|
109
|
+
differently depending on its location in the Key Reference string.
|
110
|
+
|
111
|
+
If wildcard is the last part of the KR, then it must correspond to an
|
112
|
+
array in the hierarchy. The wildcard thus means all the members in the
|
113
|
+
array. Then array members must be Hash entries, if KR is used with
|
114
|
+
`@over` or `@base`. The value in this case is array of two, where
|
115
|
+
first entry is key for the Hash members and second entry is the
|
116
|
+
assigned value.
|
117
|
+
|
118
|
+
If wildcard is used in the middle of the KR, then the next two path
|
119
|
+
selectors are used as key and value matching path, and finally the
|
120
|
+
third path selector is key to a value. Wildcard must correspond to an
|
121
|
+
array of Hash members, and the array member that matches the key and
|
122
|
+
value, will be selected. In allows travelling through the array
|
123
|
+
without knowing the array index of the desired member.
|
124
|
+
|
125
|
+
Example:
|
126
|
+
|
127
|
+
```
|
128
|
+
"base-name": { "@base": [ ":modules:*", [ "start", "starter" ] ] },
|
129
|
+
"over-name": { "@over": [ ":modules:*:name:main:stop", "stopper" ] },
|
130
|
+
```
|
@@ -0,0 +1,50 @@
|
|
1
|
+
{
|
2
|
+
"action": "test",
|
3
|
+
"regressions": {
|
4
|
+
"all": {
|
5
|
+
"test": [
|
6
|
+
{
|
7
|
+
"name": "t_run_1_1",
|
8
|
+
"dir": "test_dir"
|
9
|
+
},
|
10
|
+
{
|
11
|
+
"name": "t_run_1_2",
|
12
|
+
"dir": "test_dir-1_2"
|
13
|
+
},
|
14
|
+
{
|
15
|
+
"name": "t_run_1_3",
|
16
|
+
"dir": "test_dir"
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"foobar1": "test1",
|
20
|
+
"dir": "test_dir"
|
21
|
+
},
|
22
|
+
{
|
23
|
+
"foobar2": "test2",
|
24
|
+
"dir": "test_dir"
|
25
|
+
}
|
26
|
+
]
|
27
|
+
}
|
28
|
+
},
|
29
|
+
"foobar1": "test1",
|
30
|
+
"foobar2": "test2",
|
31
|
+
"release": "210701",
|
32
|
+
"minor": "3",
|
33
|
+
"runroot": "prog-",
|
34
|
+
"home": "FOOBAR",
|
35
|
+
"dut_files": [
|
36
|
+
"/release/210701/src/main.c",
|
37
|
+
[
|
38
|
+
"YES: 3"
|
39
|
+
],
|
40
|
+
{
|
41
|
+
"myarr": [
|
42
|
+
"bar",
|
43
|
+
1,
|
44
|
+
2,
|
45
|
+
3
|
46
|
+
]
|
47
|
+
}
|
48
|
+
],
|
49
|
+
"eof": true
|
50
|
+
}
|