xjson 0.0.2
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/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
|
+
}
|