sereth_json_spec 1.0beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d91f53ee69673463cb82eb96ba77a8b04c5a34e1
4
+ data.tar.gz: c5deca35e2b055c162ff67bd519004ee9afe58da
5
+ SHA512:
6
+ metadata.gz: 496e9738522aca5a13174a736247b9c18afa06fbf87886229d5cd53b6b19c87cefc71166307bc6b8cc37976cea4a856f4a79c62c04f15031f4e4662d8767f38f
7
+ data.tar.gz: c93d872c3231b3b7edbe68ecf56746c5e620bcc8ff708dc86b20dfd866dc490657df5bfca3031b33522d3cf046b6bd0e1a7fd8fe997702ad55de372ac4094cdb
data/lib/seek_test.rb ADDED
@@ -0,0 +1,34 @@
1
+ string = "a" * 10_000
2
+ string_v = "a" * 10_000 + "b"
3
+
4
+ require 'benchmark'
5
+ puts Benchmark.measure{string.match(/a.*?b/)}
6
+ puts Benchmark.measure{string.match(/a[^b]*b/)}
7
+ puts Benchmark.measure{string_v.match(/a[^b]*b/)}
8
+ puts Benchmark.measure{string.match(/a.*/)}
9
+
10
+ data = {:state => :start}
11
+ matcher = Object.new
12
+ matcher_class = class << matcher; self; end
13
+ match = ""
14
+
15
+ matcher_class.send(:define_method, :start) do |c|
16
+ # Start state runs only once, so it's the last one
17
+ next if c != 'a'
18
+ match << c
19
+ data[:state] = :not_b
20
+ next
21
+ end
22
+
23
+
24
+ matcher_class.send(:define_method, :not_b) do |c|
25
+ match << c
26
+ next if c != 'b'
27
+ break
28
+ end
29
+ puts Benchmark.measure {
30
+ string.each_char {|c|
31
+ state = data[:state]
32
+ matcher.send(state, c)
33
+ }
34
+ }
@@ -0,0 +1,64 @@
1
+ module Sereth::JsonSpec
2
+ module Extender
3
+ # Generate the active path of this spec, for use with sub-specs
4
+ def json_spec_path
5
+ path = self.collection_name if self.respond_to? :collection_name
6
+ path ||= "#{self.name}"
7
+ return path
8
+ end
9
+
10
+ # Returns a json listing of fields. The values of fields will and field types (if possible)
11
+ def json_spec_schema(spec)
12
+ Data.export(json_spec_path, spec, DummyUtil.new)
13
+ end
14
+
15
+ # Iterate over all the specs defined in the current class
16
+ def each_json_spec(&block)
17
+ Data.each(json_spec_path, &block)
18
+ end
19
+
20
+ # Registered a new spec of a given name
21
+ def json_spec(name, &block)
22
+ # Parse the input token data
23
+ Data.generate(json_spec_path, name, &block)
24
+ end
25
+ end
26
+
27
+ def self.included(target)
28
+ raise "JsonSpec must be prepended."
29
+ end
30
+
31
+ # Set up the default spec
32
+ def self.prepended(target)
33
+ target.send(:extend, Extender)
34
+ end
35
+
36
+ # Export item as JSON of a given spec. An invalid spec will generate
37
+ # an exception
38
+ def to_json(options = {})
39
+ if options.has_key?(:spec)
40
+ Data.export(self.class.json_spec_path, options[:spec], self)
41
+ elsif defined?(super)
42
+ super
43
+ end
44
+ end
45
+
46
+ # Wraps the proper to_json call for use with rails render method
47
+ def as_json(options = {})
48
+ if options.has_key?(:spec)
49
+ RunnerUtil.new(self.class.json_spec_path, options[:spec], self)
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ # Perform the import operation
56
+ def from_json(data, options)
57
+ data = JSON.parse(data) if data.is_a?(String)
58
+ if options.has_key?(:spec)
59
+ Data.import(self.class.json_spec_path, options[:spec], self, data)
60
+ elsif defined?(super)
61
+ super
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ # Stub class for the JSON spec cache API. Can be extended to provide actual cache functionality
2
+ module Sereth::JsonSpec
3
+ # TODO Override this cache with other types
4
+ class Cache
5
+ @cache = {}
6
+ class << self
7
+ # Configure the cache provider
8
+ def provide(provider)
9
+ @provider = provider
10
+ end
11
+
12
+ # True if the cache is enabled
13
+ def enabled?
14
+ return @provider.enabled? if !@provier.nil?
15
+ true
16
+ end
17
+
18
+ # Either retrieves teh cached data, or a nil in the event of expiry/non-caching
19
+ def retrieve(*args)
20
+ return @provider.retrieve(*args) if !@provider.nil?
21
+ @cache[args]
22
+ end
23
+
24
+ # Store the generated value in the hash
25
+ def store(value, *args)
26
+ return @provider.store(value, *args) if !@provider.nil?
27
+ @cache[args] = value
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,225 @@
1
+ module Sereth::JsonSpec
2
+ class Data
3
+ # Spec Data Storage
4
+ @specs = {}
5
+ @schema = nil
6
+ @aliases = {}
7
+ @alias_getter = lambda {}
8
+
9
+ class << self
10
+ def import(path, name, inst, data)
11
+ spec = self.get(path, name)
12
+ if !spec.nil?
13
+ spec.import!(inst, data)
14
+ else
15
+ raise "Error: Spec #{path}/#{name} not defined"
16
+ end
17
+ end
18
+
19
+ def export(path, name, inst)
20
+ spec = self.get(path, name)
21
+ if !spec.nil?
22
+ spec.export!(inst)
23
+ else
24
+ raise "Error: Spec #{path}/#{name} not defined"
25
+ end
26
+ end
27
+
28
+ def generate(path, name, &block)
29
+ data_inst = self.new(path, name)
30
+ Generator.new(path, name, data_inst).instance_eval(&block)
31
+
32
+ @specs[[path, name]] = data_inst
33
+ end
34
+
35
+ def get(path, name)
36
+ @specs[[path, name]]
37
+ end
38
+
39
+ # Iterate over each spec as (spec_path, spec_name, value) or (spec_name, value)
40
+ def each(path = nil, &block)
41
+ @specs.each do |k, v|
42
+ next if !path.nil? && k.first != path
43
+ block.call(v) if block.arity == 1
44
+ block.call(k.last, v) if block.arity == 2
45
+ block.call(k.first, k.last, v)
46
+ end
47
+ end
48
+ end
49
+
50
+ ## Spec Initialization
51
+ # Receive responder queries from extending spec
52
+ def respond_to_missing?(node_name, include_all = nil)
53
+ @spec.send(:respond_to_missing?, node_name)
54
+ end
55
+
56
+ # Receive handler requets from extending spec
57
+ def method_missing(method, *args, &block)
58
+ @spec.send(method, *args, &block)
59
+ end
60
+
61
+ def initialize(path, name)
62
+ @path = path
63
+ @name = name
64
+ @raw = {}
65
+ # Holds the methods that will be executed to generate a spec
66
+ @spec = Object.new
67
+ # Used to define methods on @spec
68
+ @spec_class = class << @spec; self; end
69
+
70
+ # Holds the procs which will be used to update an instance, may be subset of full spec
71
+ @setters = {}
72
+ @subnodes = {}
73
+
74
+ # Data for execution.
75
+ @command_queue = []
76
+ @if_count = 0
77
+ # Array, since reference to spec needs to be available in a different context
78
+ @extended_spec = []
79
+ local_extended_spec = @extended_spec
80
+
81
+ # Query responders from extended spec
82
+ @spec_class.send :define_method, :respond_to_missing? do |node_name|
83
+ local_extended_spec.first.respond_to?(node_name)
84
+ end
85
+
86
+ # Pass undefined handlers to the extended spec
87
+ @spec_class.send :define_method, :method_missing do |method, *args, &block|
88
+ if !local_extended_spec.empty?
89
+ local_extended_spec.first.send(method, *args, &block)
90
+ else
91
+ super(method, *args, &block)
92
+ end
93
+ end
94
+ end
95
+
96
+ # Queue up a node_name accessor for standard attributes
97
+ # Expectation: node_name always originates from a symbol, so no need to escape
98
+ def command!(node_name, array, subnode = nil, type: nil, get: nil, set: nil)
99
+ # Add the command to the queue
100
+ @command_queue.delete(node_name)
101
+ @command_queue.push(node_name)
102
+
103
+ # Generate the command on the spec object
104
+ exporter = nil
105
+
106
+ if array && type.nil?
107
+ exporter = Exports.collection!(node_name, type, get, subnode)
108
+ elsif array && type.is_a?(Class)
109
+ exporter = Exports.typed_collection!(node_name, type, get, subnode)
110
+ elsif type.nil?
111
+ exporter = Exports.basic!(node_name, type, get, subnode)
112
+ elsif type.is_a?(Class)
113
+ exporter = Exports.typed_basic!(node_name, type, get, subnode)
114
+ else
115
+ # Handle invalid types
116
+ raise "Invalid json_spec type: #{type}"
117
+ end
118
+
119
+ # Generate the importer object
120
+ if set.is_a?(Proc) || set.is_a?(Symbol)
121
+ @setters[node_name.to_s] = Imports.basic!(set)
122
+ elsif !subnode.nil?
123
+ @setters[node_name.to_s] = Imports.subnode!(subnode, get)
124
+ end
125
+
126
+ # Declare the generator method in the data object
127
+ @spec_class.send :define_method, node_name, &exporter
128
+ @raw[node_name] = type
129
+ end
130
+
131
+ # Declare a conditional executior with execution break-in
132
+ def if!(cond_proc, subnode)
133
+ @if_count += 1
134
+ if_name = "__json_conditional_#{@if_count}__".to_sym
135
+ @command_queue.push(if_name)
136
+
137
+ # Conditionals should not
138
+ conditional = proc do |inst, *extra|
139
+ if inst.is_a?(DummyUtil)
140
+ inst = DummyUtil.new('Conditional')
141
+ result = true
142
+ else
143
+ result = inst.instance_eval(&cond_proc)
144
+ end
145
+ return subnode.export_inside!(inst) if result && subnode
146
+ return nil
147
+ end
148
+
149
+ @spec_class.send :define_method, if_name, &conditional
150
+ @raw[if_name] = subnode
151
+ end
152
+
153
+ # Declare a super-spec to extend this from
154
+ def extends!(spec)
155
+ @extended_spec.clear.push(spec)
156
+ end
157
+
158
+ ## Spec Execution
159
+ # Iterate over all commands defined in this object, and all commands in super-objects
160
+ def each_command!(complete = {}, &block)
161
+ @command_queue.each do |command|
162
+ block.call(command, complete) if !complete[command]
163
+ complete[command] = true
164
+ end
165
+
166
+ @extended_spec.first.each_command!(complete, &block) if !@extended_spec.empty?
167
+ end
168
+
169
+ extend Sereth::Callbacks
170
+ # Handle caching
171
+ around_method :execution_inside! do |runner, args|
172
+ if Cache.enabled?
173
+ cache = Cache.retrieve(*args)
174
+ cache = Cache.store(runner.call, *args) if !cache
175
+ else
176
+ cache = runner.call
177
+ end
178
+ cache
179
+ end
180
+
181
+ # Execute the spec for the given instance, and return the raw results
182
+ def export_inside!(inst)
183
+ ret = ""
184
+ ph = ""
185
+ # Run every command from the command queue
186
+ each_command! do |command, complete|
187
+ begin
188
+ res = @spec.send(command, inst, complete)
189
+ ret << ph << res if res
190
+ ph = "," if ph == ""
191
+ rescue ExportError => e
192
+ raise e
193
+ rescue => e
194
+ raise ExportError.new(@path, @name, e)
195
+ end
196
+ end
197
+ return ret
198
+ end
199
+
200
+ # Execute the spec for the given instance, and place the result in an object
201
+ def export!(inst)
202
+ '{' << export_inside!(inst) << '}'
203
+ end
204
+
205
+ # Retrieve the data import handlers for this spec, and any extended specs
206
+ def get_setters!
207
+ return @extended_spec.first.get_setter!.merge(@setters) if !@extended_spec.empty?
208
+ return @setters
209
+ end
210
+
211
+ # Perform the data import operations for the current context
212
+ def import!(inst, data)
213
+ # The data being populated must be a hash
214
+ return if data.nil?
215
+ raise "Data must be an object." if !data.is_a?(Hash)
216
+
217
+ # Perform the import operations on all requested elements
218
+ setters = get_setters!
219
+ data.each do |key, val|
220
+ setter = setters[key]
221
+ setter.call(inst, val) if !setter.nil?
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,156 @@
1
+ module Sereth::JsonSpec
2
+ # Code generator to create functions that will extract an attribute from an object
3
+ class Exports
4
+ class << self
5
+ ## Handler Generation
6
+ # Create a handler for normal nodes
7
+ def basic!(node_name, type, gen_proc, subnode = nil)
8
+ # Handle normal objects
9
+ if gen_proc
10
+ # Proc based node value
11
+ return proc do |inst, *extra|
12
+ if subnode
13
+ "\"#{node_name}\":#{subnode.export!(inst.instance_eval(&gen_proc))}"
14
+ else
15
+ "\"#{node_name}\":#{inst.instance_eval(&gen_proc).to_json}"
16
+ end
17
+ end
18
+ else
19
+ # Basic node value
20
+ return proc do |inst, *extra|
21
+ if subnode
22
+ "\"#{node_name}\":#{subnode.export!(inst.send(node_name))}"
23
+ else
24
+ "\"#{node_name}\":#{inst.send(node_name).to_json}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ # Create a handler for typed nodes
31
+ def typed_basic!(node_name, type, gen_proc, subnode = nil)
32
+ # Handle typed objects - Requires extra handling for schema generation
33
+ if gen_proc
34
+ # Proc based node value
35
+ return proc do |inst, *extra|
36
+ item = inst.instance_eval(&gen_proc)
37
+ is_dummy = item.is_a?(DummyUtil)
38
+ if item.is_a?(type) || item.nil? || is_dummy
39
+ if subnode
40
+ "\"#{node_name}\":#{subnode.export!(item)}"
41
+ else
42
+ if is_dummy
43
+ "\"#{node_name}\":#{item.to_json(type)}"
44
+ else
45
+ "\"#{node_name}\":#{item.to_json}"
46
+ end
47
+ end
48
+ else
49
+ raise "Invalid type in JSON spec: Expected [#{type}] got #{item.class}"
50
+ end
51
+ end
52
+ else
53
+ # Basic node value
54
+ return proc do |inst, *extra|
55
+ item = inst.send(node_name)
56
+ is_dummy = item.is_a?(DummyUtil)
57
+ if item.is_a?(type) || item.nil? || is_dummy
58
+ if subnode
59
+ "\"#{node_name}\":#{subnode.export!(item)}"
60
+ else
61
+ next "\"#{node_name}\":#{item.to_json(type)}" if is_dummy
62
+ next "\"#{node_name}\":#{item.to_json}"
63
+ end
64
+ else
65
+ raise "Invalid type in JSON spec: Expected [#{type}] got #{item.class}"
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ # Create a handler for normal collections
72
+ def collection!(node_name, type, gen_proc, subnode = nil)
73
+ # Handle collections
74
+ if gen_proc
75
+ # Proc based array values
76
+ return proc do |inst, *extra|
77
+ pre_parse = inst.instance_eval(&gen_proc)
78
+ pre_parse = [] if pre_parse.nil?
79
+ pre_parse = [pre_parse] if !pre_parse.kind_of?(Array)
80
+
81
+ if subnode
82
+ parsed = pre_parse.map{|item| subnode.export!(item)}
83
+ else
84
+ parsed = pre_parse.map{|item| item.to_json}
85
+ end
86
+
87
+ "\"#{node_name}\":[#{parsed.join(",")}]"
88
+ end
89
+ else
90
+ # Basic array values
91
+ return proc do |inst, *extra|
92
+ pre_parse = inst.send(node_name)
93
+ pre_parse = [pre_parse] if !pre_parse.kind_of?(Array)
94
+
95
+ if subnode
96
+ parsed = pre_parse.map{|item| subnode.export!(item)}
97
+ else
98
+ parsed = pre_parse.map{|item| item.to_json}
99
+ end
100
+
101
+ "\"#{node_name}\":[#{parsed.join(",")}]"
102
+ end
103
+ end
104
+ end
105
+
106
+ # Create a handler for typed collections
107
+ def typed_collection!(node_name, type, gen_proc, subnode = nil)
108
+ # Handle collections
109
+ if gen_proc
110
+ # Proc based array values
111
+ return proc do |inst, *extra|
112
+ pre_parse = inst.instance_eval(&gen_proc)
113
+ pre_parse = [] if pre_parse.nil?
114
+ pre_parse = [pre_parse] if !pre_parse.kind_of?(Array)
115
+
116
+ if subnode
117
+ parsed = pre_parse.map do |item|
118
+ next subnode.export!(item) if item.is_a?(type) || item.is_a?(DummyUtil)
119
+ raise "Invalid type in JSON spec: Expected [#{type}] got #{item.class}"
120
+ end
121
+ else
122
+ parsed = pre_parse.map do |item|
123
+ next item.to_json(type) if item.is_a?(DummyUtil)
124
+ next item.to_json if item.is_a?(type)
125
+ raise "Invalid type in JSON spec: Expected [#{type}] got #{item.class}"
126
+ end
127
+ end
128
+
129
+ "\"#{node_name}\":[#{parsed.join(",")}]"
130
+ end
131
+ else
132
+ # Basic array values
133
+ return proc do |inst, *extra|
134
+ pre_parse = inst.send(node_name)
135
+ pre_parse = [pre_parse] if !pre_parse.kind_of?(Array)
136
+
137
+ if subnode
138
+ parsed = pre_parse.map do |item|
139
+ next subnode.export!(item) if item.is_a?(type) || item.is_a?(DummyUtil)
140
+ raise "Invalid type in JSON spec: Expected [#{type}] got #{item.class}"
141
+ end
142
+ else
143
+ parsed = pre_parse.map do |item|
144
+ next item.to_json(type) if item.is_a?(DummyUtil)
145
+ next item.to_json if item.is_a?(type)
146
+ raise "Invalid type in JSON spec: Expected [#{type}] got #{item.class}"
147
+ end
148
+ end
149
+
150
+ "\"#{node_name}\":[#{parsed.join(",")}]"
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,86 @@
1
+ module Sereth::JsonSpec
2
+ class Generator
3
+ # Remove all potentially unnecessary methods
4
+ core_methods = %w(__id__ __send__ object_id instance_eval methods class nil? is_a?
5
+ respond_to?)
6
+ instance_methods.each {|m| undef_method(m) unless core_methods.include?(m.to_s)}
7
+
8
+ # Initialize the Spec path, name, and data store
9
+ def initialize(path, name, data_store)
10
+ @path = path
11
+ @name = name
12
+ @data_store = data_store
13
+ end
14
+
15
+ ## Primary node creation mechanism
16
+ private
17
+ # Generate a new JsonSpecData instance, and populates it with a
18
+ def generate_subnode!(node_name = nil, &block)
19
+ # Generate and populate sub-node
20
+ new_name = "#{@name}/#{node_name}" if !node_name.nil?
21
+ new_name ||= @name
22
+ subnode = Data.new(@path, new_name)
23
+ self.class.new(@path, new_name, subnode).instance_eval(&block)
24
+ return subnode
25
+ end
26
+
27
+ public
28
+ # Tell the system that all function names are valid
29
+ def respond_to_missing?(node_name, include_private = false)
30
+ return true
31
+ end
32
+
33
+ # Default handler for creating nodes and sub-nodes
34
+ def method_missing(node_name, sym_or_arr = nil, sym = nil, *_,
35
+ type: nil, get: nil, set: nil, &block)
36
+ # Determine if the data is an array
37
+ arr = (sym_or_arr == Array)
38
+ # Get symbol param shorthand
39
+ sym = sym_or_arr if sym_or_arr.kind_of?(Symbol)
40
+ # The data getter will query the function named by the specified getter symbol if set
41
+ get ||= sym if sym.is_a?(Symbol)
42
+ # The data getter will fall back to querying the same function as the node name
43
+ get ||= node_name
44
+
45
+ if block
46
+ # Objects do not support extended options. Use keys in the subnode.
47
+ subnode = generate_subnode!(node_name, &block) if !block.nil?
48
+ subnode ||= nil
49
+ @data_store.command!(node_name, arr, subnode, get: get)
50
+ else
51
+ raise "Getter must not be a lambda" if get.is_a?(Proc) && get.lambda?
52
+ raise "Setter must not be a lambda" if set.is_a?(Proc) && set.lambda?
53
+ raise "Type must be a class" if !type.nil? && !type.is_a?(Class)
54
+ @data_store.command!(node_name, arr, nil, type: type, get: get, set: set)
55
+ end
56
+ end
57
+
58
+ # Create a conditional handler to run in the context of the data instance under
59
+ # operation. If this hanlder returns true run any supplied block.
60
+ def if!(cond_proc, &block)
61
+ # Initialize optional sub-node
62
+ subnode = generate_subnode!(&block) if !block.nil?
63
+ subnode ||= nil
64
+
65
+ # Add the subnode to the queue for execution
66
+ @data_store.if!(cond_proc, subnode)
67
+ end
68
+
69
+ ## Extended Operations
70
+ # Direct access to node creator
71
+ def override!(node_name, *args, &block)
72
+ self.method_missing(node_name.to_sym, *args, &block)
73
+ end
74
+
75
+ # Reuse an existing spec, while overrideing unecessary data
76
+ def extends!(path_or_name, name = nil)
77
+ path = path_or_name if !name.nil?
78
+ path ||= @path
79
+
80
+ name = path_or_name if name.nil?
81
+
82
+ # Supply the extension info to underlying data object
83
+ @data_store.extends!(Data.get(path, name))
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,23 @@
1
+ module Sereth::JsonSpec
2
+ class Imports
3
+ class << self
4
+ # Create a proc to execute the update operation given a update handler (setter)
5
+ def basic!(setter)
6
+ return proc do |inst, val|
7
+ # Ensure the basic value is a literal before setting
8
+ val = val.to_s unless val.is_a?(String) || val.is_a?(Numeric) || val.nil?
9
+ # Run the setter for the basic value
10
+ inst.instance_exec(inst, val, &setter)
11
+ end
12
+ end
13
+
14
+ # Create a proc to handle updates to subnodes
15
+ def subnode!(subnode, getter)
16
+ return proc do |inst, val|
17
+ new_inst = inst.instance_eval(&getter)
18
+ subnode.import!(new_inst, val)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ config_for :json_spec do
2
+ alias_value :str, default: '', parse: proc {|raw| "#{raw}"}
3
+
4
+ arg "-d #{str}", "--desc=#{str}", desc: "Pring something about a value"
5
+ end
@@ -0,0 +1,58 @@
1
+ module Sereth::JsonSpec
2
+ # The runner is used to queue up the to_json call for use with as_json
3
+ class RunnerUtil
4
+ def initialize(path, name, inst)
5
+ @path = path
6
+ @name = name
7
+ @inst = inst
8
+ end
9
+
10
+ def to_json(*_)
11
+ Data.export(@path, @name, @inst)
12
+ end
13
+ end
14
+
15
+ # A dummy object for representing the instance of the item being jsonified.
16
+ class DummyUtil
17
+ def initialize(prefix = nil)
18
+ @prefix = prefix
19
+ end
20
+
21
+ # TODO: proper type generation
22
+ def to_json(type = nil)
23
+ return "\"#{@prefix}BasicType\"" if !type
24
+ "\"#{@prefix}#{type.name}\""
25
+ end
26
+
27
+ # For array arguments, acts as a single argument array
28
+ def each
29
+ yield self
30
+ end
31
+
32
+ # For all accessors returns an object that jsonifies into a blank string
33
+ def method_missing(*arguments, &block)
34
+ self
35
+ end
36
+ end
37
+
38
+ # Info about the failed export
39
+ class ExportError < StandardError
40
+ def initialize(path, name, child = nil)
41
+ @path = path
42
+ @name = name
43
+ @child = child
44
+ end
45
+
46
+ def message
47
+ ret = "Error exporting JSON data for [#{@path}/#{@name}]"
48
+ if @child
49
+ ret << ":\n\t"
50
+ ret << @child.message
51
+ end
52
+ end
53
+
54
+ def backtrace
55
+ @child.backtrace
56
+ end
57
+ end
58
+ end