sereth_json_spec 1.0beta1

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 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