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