sereth_json_spec 1.0beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/seek_test.rb +34 -0
- data/lib/sereth_json_spec/api.rb +64 -0
- data/lib/sereth_json_spec/cache.rb +31 -0
- data/lib/sereth_json_spec/data.rb +225 -0
- data/lib/sereth_json_spec/exports.rb +156 -0
- data/lib/sereth_json_spec/generator.rb +86 -0
- data/lib/sereth_json_spec/imports.rb +23 -0
- data/lib/sereth_json_spec/lib_config.rb +5 -0
- data/lib/sereth_json_spec/utils.rb +58 -0
- data/lib/sereth_json_spec.rb +14 -0
- data/lib/sereth_utils/alias_args.rb +40 -0
- data/lib/sereth_utils/all.rb +15 -0
- data/lib/sereth_utils/callbacks.rb +31 -0
- data/lib/sereth_utils/config.rb +141 -0
- data/lib/sereth_utils/dev_log.rb +140 -0
- data/lib/sereth_utils/parser.rb +70 -0
- data/lib/sereth_utils/stage.rb +80 -0
- data/lib/sereth_utils/symbol_callback.rb +27 -0
- metadata +131 -0
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,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
|