xjson 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
+ }