wabur 0.2.0d1 → 0.4.0d1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -14
- data/bin/wabur +103 -0
- data/lib/wab/controller.rb +133 -90
- data/lib/wab/data.rb +14 -27
- data/lib/wab/errors.rb +14 -8
- data/lib/wab/impl/bool_expr.rb +25 -0
- data/lib/wab/impl/configuration.rb +166 -0
- data/lib/wab/impl/data.rb +155 -232
- data/lib/wab/impl/expr.rb +26 -0
- data/lib/wab/impl/expr_parser.rb +55 -0
- data/lib/wab/impl/exprs/and.rb +29 -0
- data/lib/wab/impl/exprs/between.rb +41 -0
- data/lib/wab/impl/exprs/eq.rb +28 -0
- data/lib/wab/impl/exprs/gt.rb +30 -0
- data/lib/wab/impl/exprs/gte.rb +30 -0
- data/lib/wab/impl/exprs/has.rb +26 -0
- data/lib/wab/impl/exprs/in.rb +28 -0
- data/lib/wab/impl/exprs/lt.rb +30 -0
- data/lib/wab/impl/exprs/lte.rb +30 -0
- data/lib/wab/impl/exprs/not.rb +27 -0
- data/lib/wab/impl/exprs/or.rb +29 -0
- data/lib/wab/impl/exprs/regex.rb +28 -0
- data/lib/wab/impl/handler.rb +95 -0
- data/lib/wab/impl/model.rb +197 -0
- data/lib/wab/impl/path_expr.rb +14 -0
- data/lib/wab/impl/shell.rb +92 -7
- data/lib/wab/impl/utils.rb +110 -0
- data/lib/wab/impl.rb +24 -0
- data/lib/wab/io/call.rb +4 -7
- data/lib/wab/io/engine.rb +128 -51
- data/lib/wab/io/shell.rb +61 -64
- data/lib/wab/io.rb +0 -2
- data/lib/wab/open_controller.rb +43 -0
- data/lib/wab/shell.rb +46 -61
- data/lib/wab/shell_logger.rb +13 -0
- data/lib/wab/utils.rb +36 -0
- data/lib/wab/uuid.rb +3 -6
- data/lib/wab/version.rb +2 -2
- data/lib/wab.rb +3 -0
- data/pages/Plan.md +20 -14
- data/test/bench_io_shell.rb +49 -0
- data/test/{impl_test.rb → helper.rb} +2 -4
- data/test/mirror_controller.rb +16 -0
- data/test/test_configuration.rb +38 -0
- data/test/test_data.rb +207 -0
- data/test/test_expr.rb +35 -0
- data/test/test_expr_and.rb +24 -0
- data/test/test_expr_between.rb +43 -0
- data/test/test_expr_eq.rb +24 -0
- data/test/test_expr_gt.rb +24 -0
- data/test/test_expr_gte.rb +24 -0
- data/test/test_expr_has.rb +19 -0
- data/test/test_expr_in.rb +24 -0
- data/test/test_expr_lt.rb +24 -0
- data/test/test_expr_lte.rb +24 -0
- data/test/test_expr_not.rb +22 -0
- data/test/test_expr_or.rb +24 -0
- data/test/test_expr_regex.rb +30 -0
- data/test/test_impl.rb +38 -0
- data/test/test_io_shell.rb +189 -0
- data/test/test_model.rb +31 -0
- data/test/test_runner.rb +177 -0
- data/test/tests.rb +3 -8
- metadata +91 -18
- data/lib/wab/model.rb +0 -136
- data/lib/wab/view.rb +0 -21
- data/test/data_test.rb +0 -253
- data/test/ioshell_test.rb +0 -461
@@ -0,0 +1,197 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
require 'oj'
|
4
|
+
|
5
|
+
module WAB
|
6
|
+
module Impl
|
7
|
+
|
8
|
+
# The Model class is used to store data when using the
|
9
|
+
# WAB::Impl::Shell. It is no intended for any other use. The *get* and
|
10
|
+
# *query* methods are the primary means of interacting with the model.
|
11
|
+
#
|
12
|
+
# The Model is simple in that it stores data in a Hash references by *ref*
|
13
|
+
# numbers. Data is stores in a directory as separate JSON files that are
|
14
|
+
# named as the *ref* number as a 16 character hexidecimal.
|
15
|
+
class Model
|
16
|
+
|
17
|
+
# Create a new Model using the designated directory as the store.
|
18
|
+
#
|
19
|
+
# dir:: directory to store data in
|
20
|
+
def initialize(dir)
|
21
|
+
@dir = dir.nil? ? nil : File.expand_path(dir)
|
22
|
+
@cnt = 0
|
23
|
+
@map = {}
|
24
|
+
@lock = Thread::Mutex.new
|
25
|
+
FileUtils.mkdir_p(@dir) unless @dir.nil? || Dir.exist?(@dir)
|
26
|
+
load_files unless @dir.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get a single record in the database. A WAB::Impl::Data object is
|
30
|
+
# returned if not nil.
|
31
|
+
#
|
32
|
+
# ref:: references number of the object to retrieve.
|
33
|
+
def get(ref)
|
34
|
+
@map[ref]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Execute a TQL query.
|
38
|
+
#
|
39
|
+
# _Note that the current implementation does not support nested data
|
40
|
+
# retrieval.
|
41
|
+
#
|
42
|
+
# tql:: query to execute
|
43
|
+
def query(tql)
|
44
|
+
rid = tql[:rid]
|
45
|
+
where = nil
|
46
|
+
filter = nil
|
47
|
+
if tql.has_key?(:where)
|
48
|
+
w = tql[:where]
|
49
|
+
where = w.is_a?(Array) ? ExprParser.parse(w) : w
|
50
|
+
end
|
51
|
+
filter = ExprParser.parse(tql[:filter]) if tql.has_key?(:filter)
|
52
|
+
|
53
|
+
if tql.has_key?(:insert)
|
54
|
+
insert(tql[:insert], rid, where, filter)
|
55
|
+
elsif tql.has_key?(:update)
|
56
|
+
update(tql[:update], rid, where, filter)
|
57
|
+
elsif tql.has_key?(:delete)
|
58
|
+
delete(tql[:delete], rid, where, filter)
|
59
|
+
else
|
60
|
+
select(tql[:select], rid, where, filter)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def insert(obj, rid, where, filter)
|
67
|
+
ref = nil
|
68
|
+
@lock.synchronize {
|
69
|
+
unless where.nil?
|
70
|
+
@map.each_value { |v|
|
71
|
+
if where.eval(v) && (filter.nil? || filter.eval(v))
|
72
|
+
result = { code: -1, error: 'Already exists.' }
|
73
|
+
result[:rid] = rid unless rid.nil?
|
74
|
+
return result
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
@cnt += 1
|
79
|
+
ref = @cnt
|
80
|
+
@map[ref] = Data.new(obj, true)
|
81
|
+
write_to_file(ref, obj)
|
82
|
+
}
|
83
|
+
result = { code: 0, ref: ref }
|
84
|
+
result[:rid] = rid unless rid.nil?
|
85
|
+
result
|
86
|
+
end
|
87
|
+
|
88
|
+
def extract_matches(format, ref, rid, obj)
|
89
|
+
if format.nil?
|
90
|
+
{ id: ref, data: obj.native }
|
91
|
+
else
|
92
|
+
format_obj(format, ref, rid, obj)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def select(format, rid, where, filter)
|
97
|
+
matches = []
|
98
|
+
@lock.synchronize {
|
99
|
+
if where.nil? && filter.nil?
|
100
|
+
@map.each { |ref,obj|
|
101
|
+
matches << extract_matches(format, ref, rid, obj)
|
102
|
+
}
|
103
|
+
else
|
104
|
+
@map.each { |ref,obj|
|
105
|
+
if where.eval(obj) && (filter.nil? || filter.eval(obj))
|
106
|
+
matches << extract_matches(format, ref, rid, obj)
|
107
|
+
end
|
108
|
+
}
|
109
|
+
end
|
110
|
+
}
|
111
|
+
result = { code: 0, results: matches }
|
112
|
+
result[:rid] = rid unless rid.nil?
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
def update(obj, _rid, where, _filter)
|
117
|
+
updated = []
|
118
|
+
@lock.synchronize {
|
119
|
+
if where.is_a?(Expr)
|
120
|
+
# TBD must be able to update portions of an object
|
121
|
+
else
|
122
|
+
# A reference.
|
123
|
+
@map[where] = Data.new(obj, true)
|
124
|
+
updated << where
|
125
|
+
write_to_file(where, obj)
|
126
|
+
end
|
127
|
+
}
|
128
|
+
{ code: 0, updated: updated }
|
129
|
+
end
|
130
|
+
|
131
|
+
def delete(_del_opt, _rid, where, filter)
|
132
|
+
deleted = []
|
133
|
+
@lock.synchronize {
|
134
|
+
if where.is_a?(Expr)
|
135
|
+
@map.each { |ref,obj|
|
136
|
+
if where.eval(obj) && (filter.nil? || filter.eval(obj))
|
137
|
+
deleted << ref
|
138
|
+
@map.delete(ref)
|
139
|
+
end
|
140
|
+
}
|
141
|
+
# A reference.
|
142
|
+
elsif !@map.delete(where).nil?
|
143
|
+
deleted << where
|
144
|
+
remove_file(where)
|
145
|
+
end
|
146
|
+
}
|
147
|
+
{ code: 0, deleted: deleted }
|
148
|
+
end
|
149
|
+
|
150
|
+
def format_obj(format, ref, rid, obj)
|
151
|
+
case format
|
152
|
+
when Hash
|
153
|
+
native = {}
|
154
|
+
format.each { |k,v| native[k] = format_obj(v, ref, rid, obj) }
|
155
|
+
native
|
156
|
+
when Array
|
157
|
+
format.map { |v| format_obj(v, ref, rid, obj) }
|
158
|
+
when String
|
159
|
+
if '$ref' == format
|
160
|
+
ref
|
161
|
+
elsif '$rid' == format
|
162
|
+
rid
|
163
|
+
elsif '$' == format || '$root' == format
|
164
|
+
obj.native
|
165
|
+
elsif !format.empty? && format.start_with?("'")
|
166
|
+
format[1..-1]
|
167
|
+
else
|
168
|
+
obj.get(format)
|
169
|
+
end
|
170
|
+
else
|
171
|
+
format
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def load_files()
|
176
|
+
Dir.foreach(@dir) { |fn|
|
177
|
+
next if '.' == fn[0]
|
178
|
+
ref = fn[0..-6]
|
179
|
+
iref = ref.to_i(16)
|
180
|
+
@cnt = iref if @cnt < iref
|
181
|
+
@map[iref] = Data.new(Oj.load_file(File.join(@dir, fn), mode: :wab), true)
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
def write_to_file(ref, obj)
|
186
|
+
return if @dir.nil?
|
187
|
+
obj.native if obj.is_a?(WAB::Data)
|
188
|
+
File.open(File.join(@dir, "%016x.json" % ref), 'wb') { |f| f.write(Oj.dump(obj, mode: :wab, indent: 0)) }
|
189
|
+
end
|
190
|
+
|
191
|
+
def remove_file(ref)
|
192
|
+
File.delete(File.join(@dir, "%016x.json" % ref)) unless @dir.nil?
|
193
|
+
end
|
194
|
+
|
195
|
+
end # Model
|
196
|
+
end # Impl
|
197
|
+
end # WAB
|
data/lib/wab/impl/shell.rb
CHANGED
@@ -1,16 +1,101 @@
|
|
1
1
|
|
2
|
-
require 'wab'
|
2
|
+
require 'wab/impl/handler'
|
3
|
+
require 'wab/impl/model'
|
3
4
|
|
4
5
|
module WAB
|
5
|
-
|
6
6
|
module Impl
|
7
7
|
|
8
8
|
# The shell for reference Ruby implementation.
|
9
|
-
class Shell
|
9
|
+
class Shell
|
10
|
+
include WAB::ShellLogger
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
# Returns the path where a data type is located. The default is 'kind'.
|
14
|
+
attr_reader :type_key
|
15
|
+
attr_reader :path_pos
|
16
|
+
|
17
|
+
# Call the Model instance with these methods.
|
18
|
+
def_delegators :@model, :get, :query
|
19
|
+
|
20
|
+
# Sets up the shell with the supplied configuration data.
|
21
|
+
#
|
22
|
+
# config:: Configuration object
|
23
|
+
def initialize(config)
|
24
|
+
@pre_path = config[:path_prefix] || '/v1'
|
25
|
+
@path_pos = @pre_path.split('/').length - 1
|
26
|
+
base = config[:base] || '.'
|
27
|
+
@model = Model.new((config['store.dir'] || File.join(base, 'data')).gsub('$BASE', base))
|
28
|
+
@type_key = config[:type_key] || 'kind'
|
29
|
+
@logger = config[:logger]
|
30
|
+
@logger.level = config[:verbosity] unless @logger.nil?
|
31
|
+
@http_dir = (config['http.dir'] || File.join(base, 'pages')).gsub('$BASE', base)
|
32
|
+
@http_port = (config['http.port'] || 6363).to_i
|
33
|
+
@controllers = {}
|
34
|
+
|
35
|
+
requires = config[:require]
|
36
|
+
case requires
|
37
|
+
when Array
|
38
|
+
requires.each { |r| require r.strip }
|
39
|
+
when String
|
40
|
+
requires.split(',').each { |r| require r.strip }
|
41
|
+
end
|
42
|
+
|
43
|
+
if config[:handler].is_a?(Array)
|
44
|
+
config[:handler].each { |hh| register_controller(hh[:type], hh[:handler]) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Start listening. This should be called after registering Controllers
|
49
|
+
# with the Shell.
|
50
|
+
def start()
|
51
|
+
server = WEBrick::HTTPServer.new(Port: @http_port, DocumentRoot: @http_dir)
|
52
|
+
server.mount(@pre_path, WAB::Impl::Handler, self)
|
53
|
+
|
54
|
+
trap 'INT' do server.shutdown end
|
55
|
+
server.start
|
56
|
+
end
|
10
57
|
|
11
|
-
#
|
12
|
-
|
13
|
-
|
58
|
+
# Register a controller for a named type.
|
59
|
+
#
|
60
|
+
# If a request is received for an unregistered type the default controller
|
61
|
+
# will be used. The default controller is registered with a +nil+ key.
|
62
|
+
#
|
63
|
+
# type:: type name
|
64
|
+
# controller:: Controller instance for handling requests for the
|
65
|
+
# identified +type+. This can be a Controller, a Controller
|
66
|
+
# class, or a Controller class name.
|
67
|
+
def register_controller(type, controller)
|
68
|
+
case controller
|
69
|
+
when String
|
70
|
+
controller = Object.const_get(controller).new(self)
|
71
|
+
when Class
|
72
|
+
controller = controller.new(self)
|
73
|
+
end
|
74
|
+
controller.shell = self
|
75
|
+
@controllers[type] = controller
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the controller associated with the type key found in the
|
79
|
+
# data. If a controller has not be registered under that key the default
|
80
|
+
# controller is returned if there is one.
|
81
|
+
#
|
82
|
+
# data:: data to extract the type from for lookup in the controllers
|
83
|
+
def controller(data)
|
84
|
+
path = data.get(:path)
|
85
|
+
path = path.native if path.is_a?(WAB::Data)
|
86
|
+
return path_controller(path) unless path.nil? || (path.length <= @path_pos)
|
87
|
+
|
88
|
+
content = data.get(:content)
|
89
|
+
return @controllers[content.get(@type_key)] || @controllers[nil] unless content.nil?
|
90
|
+
|
91
|
+
@controllers[nil]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the controller according to the type in the path.
|
95
|
+
#
|
96
|
+
# path: path Array such as from a URL
|
97
|
+
def path_controller(path)
|
98
|
+
@controllers[path[@path_pos]] || @controllers[nil]
|
14
99
|
end
|
15
100
|
|
16
101
|
# Create and return a new data instance with the provided initial value.
|
@@ -27,7 +112,7 @@ module WAB
|
|
27
112
|
def data(value={}, repair=false)
|
28
113
|
Data.new(value, repair)
|
29
114
|
end
|
30
|
-
|
115
|
+
|
31
116
|
end # Shell
|
32
117
|
end # Impl
|
33
118
|
end # WAB
|
@@ -0,0 +1,110 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# Convert a key to an +Integer+ or raise.
|
9
|
+
def key_to_int(key)
|
10
|
+
return key if key.is_a?(Integer)
|
11
|
+
|
12
|
+
key = key.to_s if key.is_a?(Symbol)
|
13
|
+
if key.is_a?(String)
|
14
|
+
i = key.to_i
|
15
|
+
return i if i.to_s == key
|
16
|
+
end
|
17
|
+
return key if WAB::Utils.pre_24_fixnum?(key)
|
18
|
+
|
19
|
+
raise WAB::Error, 'path key must be an integer for an Array.'
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns either an +Integer+ or +nil+.
|
23
|
+
def attempt_key_to_int(key)
|
24
|
+
return key if key.is_a?(Integer)
|
25
|
+
|
26
|
+
key = key.to_s if key.is_a?(Symbol)
|
27
|
+
if key.is_a?(String)
|
28
|
+
i = key.to_i
|
29
|
+
return i if i.to_s == key
|
30
|
+
end
|
31
|
+
return key if WAB::Utils.pre_24_fixnum?(key)
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Gets the Data element or value identified by the path where the path
|
36
|
+
# elements are separated by the '.' character.
|
37
|
+
def get_node(root, path)
|
38
|
+
return root[path] if path.is_a?(Symbol)
|
39
|
+
|
40
|
+
path = path.to_s.split('.') unless path.is_a?(Array)
|
41
|
+
node = root
|
42
|
+
|
43
|
+
path.each { |key|
|
44
|
+
if node.is_a?(Hash)
|
45
|
+
node = node[key.to_sym]
|
46
|
+
elsif node.is_a?(Array)
|
47
|
+
i = key.to_i
|
48
|
+
return nil if i.zero? && key != i && key != i.to_s
|
49
|
+
node = node[i]
|
50
|
+
else
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
node
|
56
|
+
end
|
57
|
+
|
58
|
+
# Sets the node value identified by the path where the path elements are
|
59
|
+
# separated by the '.' character.
|
60
|
+
def set_value(node, path, value)
|
61
|
+
path = path.to_s.split('.') unless path.is_a?(Array)
|
62
|
+
|
63
|
+
path[0..-2].each_index { |i|
|
64
|
+
key = path[i]
|
65
|
+
if node.is_a?(Hash)
|
66
|
+
key = key.to_sym
|
67
|
+
unless node.has_key?(key)
|
68
|
+
node[key] = attempt_key_to_int(path[i + 1]).nil? ? {} : []
|
69
|
+
end
|
70
|
+
node = node[key]
|
71
|
+
elsif node.is_a?(Array)
|
72
|
+
key = key_to_int(key)
|
73
|
+
if key < node.length && -node.length < key
|
74
|
+
node = node[key]
|
75
|
+
else
|
76
|
+
entry = attempt_key_to_int(path[i + 1]).nil? ? {} : []
|
77
|
+
if key < -node.length
|
78
|
+
node.unshift(entry)
|
79
|
+
else
|
80
|
+
node[key] = entry
|
81
|
+
end
|
82
|
+
node = entry
|
83
|
+
end
|
84
|
+
else
|
85
|
+
raise WAB::TypeError, "Can not set a member of an #{node.class}."
|
86
|
+
end
|
87
|
+
}
|
88
|
+
|
89
|
+
key = path[-1]
|
90
|
+
|
91
|
+
if node.is_a?(Hash)
|
92
|
+
node[key.to_sym] = value
|
93
|
+
elsif node.is_a?(Array)
|
94
|
+
key = key_to_int(key)
|
95
|
+
if key < -node.length
|
96
|
+
node.unshift(value)
|
97
|
+
else
|
98
|
+
node[key] = value
|
99
|
+
end
|
100
|
+
else
|
101
|
+
raise WAB::TypeError, "Can not set a member of an #{node.class}."
|
102
|
+
end
|
103
|
+
value
|
104
|
+
end
|
105
|
+
|
106
|
+
end # Singleton class
|
107
|
+
|
108
|
+
end # Utils
|
109
|
+
end # Impl
|
110
|
+
end # WAB
|
data/lib/wab/impl.rb
CHANGED
@@ -7,5 +7,29 @@ module WAB
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
require 'wab/impl/configuration'
|
10
11
|
require 'wab/impl/data'
|
12
|
+
require 'wab/impl/expr'
|
13
|
+
require 'wab/impl/path_expr'
|
14
|
+
require 'wab/impl/bool_expr'
|
11
15
|
require 'wab/impl/shell'
|
16
|
+
require 'wab/impl/utils'
|
17
|
+
|
18
|
+
# Require the concrete Expr subclasses so a mapping table can be created for
|
19
|
+
# the parser.
|
20
|
+
|
21
|
+
require 'wab/impl/exprs/between'
|
22
|
+
require 'wab/impl/exprs/eq'
|
23
|
+
require 'wab/impl/exprs/gt'
|
24
|
+
require 'wab/impl/exprs/gte'
|
25
|
+
require 'wab/impl/exprs/has'
|
26
|
+
require 'wab/impl/exprs/in'
|
27
|
+
require 'wab/impl/exprs/lt'
|
28
|
+
require 'wab/impl/exprs/lte'
|
29
|
+
require 'wab/impl/exprs/regex'
|
30
|
+
|
31
|
+
require 'wab/impl/exprs/and'
|
32
|
+
require 'wab/impl/exprs/or'
|
33
|
+
require 'wab/impl/exprs/not'
|
34
|
+
|
35
|
+
require 'wab/impl/expr_parser'
|
data/lib/wab/io/call.rb
CHANGED
@@ -1,21 +1,18 @@
|
|
1
1
|
|
2
|
-
require 'wab'
|
3
|
-
|
4
2
|
module WAB
|
5
|
-
|
6
3
|
module IO
|
7
4
|
|
8
5
|
class Call
|
9
6
|
|
10
|
-
attr_accessor :rid
|
11
7
|
attr_accessor :result
|
12
8
|
attr_accessor :thread
|
9
|
+
attr_accessor :rid
|
10
|
+
attr_accessor :giveup
|
13
11
|
|
14
12
|
def initialize(timeout=2.0)
|
15
|
-
@rid =
|
16
|
-
@result = nil
|
17
|
-
@thread = Thread.current
|
13
|
+
@rid = rid
|
18
14
|
@giveup = Time.now + timeout
|
15
|
+
@thread = Thread.current
|
19
16
|
end
|
20
17
|
|
21
18
|
end # Call
|