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
data/lib/wab/io/engine.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
|
2
|
-
require '
|
2
|
+
require 'oj'
|
3
3
|
|
4
4
|
module WAB
|
5
|
-
|
6
5
|
module IO
|
7
6
|
|
8
7
|
class Engine
|
@@ -15,26 +14,35 @@ module WAB
|
|
15
14
|
@shell = shell
|
16
15
|
@last_rid = 0
|
17
16
|
@pending = {}
|
18
|
-
@lock = Thread::Mutex.new
|
19
|
-
@queue = Queue.new
|
17
|
+
@lock = Thread::Mutex.new
|
18
|
+
@queue = Queue.new
|
20
19
|
tcnt = 1 if 0 >= tcnt
|
21
20
|
@tcnt = tcnt
|
21
|
+
@timeout_thread = nil
|
22
22
|
end
|
23
23
|
|
24
24
|
def start()
|
25
|
+
proc_threads = []
|
25
26
|
@tcnt.times {
|
26
|
-
Thread.new {
|
27
|
-
|
27
|
+
proc_threads << Thread.new {
|
28
|
+
while true
|
29
|
+
begin
|
30
|
+
break unless process_msg(@queue.pop)
|
31
|
+
rescue Exception => e
|
32
|
+
$stderr.puts %|*-*-* #{e.class}: #{e.message}\n#{e.backtrace.join("\n ")}|
|
33
|
+
end
|
34
|
+
end
|
28
35
|
}
|
29
36
|
}
|
37
|
+
@timeout_thread = Thread.new { timeout_check }
|
30
38
|
|
31
|
-
|
32
|
-
|
33
|
-
Oj.strict_load($stdin, symbol_keys: true) { |msg|
|
39
|
+
Oj.load($stdin, mode: :wab, symbol_keys: true) { |msg|
|
34
40
|
api = msg[:api]
|
35
|
-
|
41
|
+
@shell.info("=> controller #{Oj.dump(msg, mode: :wab)}") if @shell.info?
|
42
|
+
case api
|
43
|
+
when 1
|
36
44
|
@queue.push(msg)
|
37
|
-
|
45
|
+
when 4
|
38
46
|
rid = msg[:rid]
|
39
47
|
call = nil
|
40
48
|
@lock.synchronize {
|
@@ -44,73 +52,142 @@ module WAB
|
|
44
52
|
call.result = msg[:body]
|
45
53
|
call.thread.run
|
46
54
|
end
|
55
|
+
when -2, -3, -6, -15
|
56
|
+
shutdown(msg)
|
57
|
+
break
|
58
|
+
when -9
|
59
|
+
Thread.kill(@timeout_thread)
|
60
|
+
proc_threads.each { |t| Thread.kill(t) }
|
61
|
+
Process.exit(0)
|
47
62
|
else
|
48
|
-
#
|
63
|
+
$stderr.puts "*-*-* Invalid api value (#{api}) in message."
|
49
64
|
end
|
50
65
|
}
|
51
66
|
end
|
52
67
|
|
68
|
+
def shutdown(msg)
|
69
|
+
# TBD kill timeout thread
|
70
|
+
Thread.kill(@timeout_thread)
|
71
|
+
# tell processing threads to shutdown.
|
72
|
+
@tcnt.times { @queue.push(msg) }
|
73
|
+
end
|
53
74
|
|
54
75
|
# Send request to the model portion of the system.
|
55
76
|
#
|
56
77
|
# tql:: the body of the message which should be JSON-TQL as a native Hash
|
57
|
-
def request(tql)
|
58
|
-
call = Call.new()
|
78
|
+
def request(tql, timeout)
|
79
|
+
call = Call.new(timeout)
|
80
|
+
|
59
81
|
@lock.synchronize {
|
60
82
|
@last_rid += 1
|
61
83
|
call.rid = @last_rid.to_s
|
62
84
|
@pending[call.rid] = call
|
63
85
|
}
|
64
|
-
|
86
|
+
|
87
|
+
msg = {rid: call.rid, api: 3, body: tql}
|
88
|
+
@shell.info("=> model: #{Oj.dump(msg, mode: :wab)}") if @shell.info?
|
89
|
+
data = @shell.data(msg, true)
|
65
90
|
# Send the message. Make sure to flush to assure it gets sent.
|
66
|
-
$stdout.puts(data.json
|
67
|
-
$stdout.flush
|
68
|
-
|
91
|
+
$stdout.puts(data.json)
|
92
|
+
$stdout.flush
|
93
|
+
|
69
94
|
# Wait for either the response to arrive or for a timeout. In both
|
70
|
-
# cases #run should be called on the thread.
|
71
|
-
|
95
|
+
# cases #run should be called on the thread. Sleep is used instead of
|
96
|
+
# stop to avoid a race condition where a response arrives before the
|
97
|
+
# thread is stopped.
|
98
|
+
sleep(0.1) while call.result.nil?
|
72
99
|
call.result
|
73
100
|
end
|
74
101
|
|
102
|
+
def send_error(rid, msg, bt=nil)
|
103
|
+
body = { code: -1, error: msg }
|
104
|
+
body[:backtrace] = bt unless bt.nil?
|
105
|
+
$stdout.puts(@shell.data({rid: rid, api: 2, body: body}).json)
|
106
|
+
$stdout.flush
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
# return false to exit loop
|
75
111
|
def process_msg(native)
|
112
|
+
# exit loop if an interrupt (api less than 0)
|
113
|
+
return false if native[:api] < 0
|
114
|
+
|
76
115
|
rid = native[:rid]
|
77
|
-
api = native[:api]
|
78
116
|
body = native[:body]
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
117
|
+
return send_error(rid, 'No body in request.') if body.nil?
|
118
|
+
|
119
|
+
data = @shell.data(body, false)
|
120
|
+
data.detect
|
121
|
+
controller = @shell.controller(data)
|
122
|
+
return send_error(rid, 'No handler found.') if controller.nil?
|
123
|
+
|
124
|
+
reply_body = nil
|
125
|
+
op = body[:op]
|
126
|
+
path = body[:path]
|
127
|
+
query = body[:query]
|
128
|
+
begin
|
129
|
+
if 'NEW' == op && controller.respond_to?(:create)
|
130
|
+
log_operation_with_body('controller.create', path, query, body) if @shell.info?
|
131
|
+
reply_body = controller.create(path, query, data.get(:content))
|
132
|
+
elsif 'GET' == op && controller.respond_to?(:read)
|
133
|
+
log_operation('controller.read', path, query) if @shell.info?
|
134
|
+
reply_body = controller.read(path, query)
|
135
|
+
elsif 'DEL' == op && controller.respond_to?(:delete)
|
136
|
+
log_operation('controller.delete', path, query) if @shell.info?
|
137
|
+
reply_body = controller.delete(path, query)
|
138
|
+
elsif 'MOD' == op && controller.respond_to?(:update)
|
139
|
+
log_operation_with_body('controller.update', path, query, body) if @shell.info?
|
140
|
+
reply_body = controller.update(path, query, data.get(:content))
|
90
141
|
else
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
142
|
+
reply_body = controller.handle(data)
|
143
|
+
end
|
144
|
+
rescue Exception => e
|
145
|
+
return send_error(rid, "#{e.class}: #{e.message}", e.backtrace)
|
146
|
+
end
|
147
|
+
# If reply_body is nil then it is async.
|
148
|
+
unless reply_body.nil?
|
149
|
+
reply_body = reply_body.native if reply_body.is_a?(WAB::Data)
|
150
|
+
msg = {rid: rid, api: 2, body: reply_body}
|
151
|
+
@shell.info("=> view: #{Oj.dump(msg, mode: :wab)}") if @shell.info?
|
152
|
+
$stdout.puts(@shell.data(msg).json)
|
153
|
+
$stdout.flush
|
154
|
+
end
|
155
|
+
true
|
156
|
+
end
|
157
|
+
|
158
|
+
def timeout_check()
|
159
|
+
while true
|
160
|
+
sleep(0.5)
|
161
|
+
timed_out = []
|
162
|
+
now = Time.now
|
163
|
+
@lock.synchronize {
|
164
|
+
@pending.delete_if { |_rid,call|
|
165
|
+
if call.giveup <= now
|
166
|
+
timed_out << call
|
167
|
+
true
|
102
168
|
else
|
103
|
-
|
169
|
+
false
|
104
170
|
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
171
|
+
}
|
172
|
+
}
|
173
|
+
timed_out.each { |call|
|
174
|
+
body = { code: -1, error: "Timed out waiting for #{call.rid}." }
|
175
|
+
unless call.nil?
|
176
|
+
call.result = body
|
177
|
+
call.thread.run
|
109
178
|
end
|
110
|
-
|
179
|
+
}
|
111
180
|
end
|
112
|
-
|
113
|
-
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def log_operation(caller, path, query)
|
186
|
+
@shell.info("=> #{caller}(#{path.join('/')}#{query})")
|
187
|
+
end
|
188
|
+
|
189
|
+
def log_operation_with_body(caller, path, query, body)
|
190
|
+
@shell.info("=> #{caller}(#{path.join('/')}#{query}, #{Oj.dump(body[:content], mode: :wab)})")
|
114
191
|
end
|
115
192
|
|
116
193
|
end # Engine
|
data/lib/wab/io/shell.rb
CHANGED
@@ -1,35 +1,76 @@
|
|
1
1
|
|
2
2
|
require 'time'
|
3
|
-
require 'wab'
|
3
|
+
require 'wab/impl'
|
4
4
|
|
5
5
|
module WAB
|
6
|
-
|
7
6
|
module IO
|
8
7
|
|
9
8
|
# A Shell that uses STDIN and STDOUT for all interactions with the View
|
10
9
|
# and Model. Since the View and Model APIs are asynchronous and Controller
|
11
10
|
# calls are synchronous for simplicity some effort is required to block
|
12
11
|
# where needed to achieve the difference in behavior.
|
13
|
-
class Shell
|
12
|
+
class Shell
|
13
|
+
include WAB::ShellLogger
|
14
14
|
|
15
15
|
attr_reader :path_pos
|
16
16
|
attr_reader :type_key
|
17
|
-
|
17
|
+
attr_accessor :timeout
|
18
|
+
|
18
19
|
# Sets up the shell with the designated number of processing threads and
|
19
20
|
# the type_key.
|
20
21
|
#
|
21
22
|
# tcnt:: processing thread count
|
22
23
|
# type_key:: key to use for the record type
|
23
24
|
def initialize(tcnt, type_key='kind', path_pos=0)
|
24
|
-
|
25
|
+
@controllers = {}
|
26
|
+
@type_key = type_key
|
27
|
+
@path_pos = path_pos
|
25
28
|
@engine = Engine.new(self, tcnt)
|
29
|
+
@timeout = 2.0
|
30
|
+
@logger = Logger.new(STDERR)
|
31
|
+
logger.level = Logger::WARN
|
26
32
|
end
|
27
33
|
|
28
34
|
# Starts listening and processing.
|
29
35
|
def start()
|
30
|
-
@engine.start
|
36
|
+
@engine.start
|
37
|
+
end
|
38
|
+
|
39
|
+
# Register a controller for a named type.
|
40
|
+
#
|
41
|
+
# If a request is received for an unregistered type the default controller
|
42
|
+
# will be used. The default controller is registered with a +nil+ key.
|
43
|
+
#
|
44
|
+
# type:: type name
|
45
|
+
# controller:: Controller instance for handling requests for the identified +type+
|
46
|
+
def register_controller(type, controller)
|
47
|
+
controller.shell = self
|
48
|
+
@controllers[type] = controller
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the controller associated with the type key found in the
|
52
|
+
# data. If a controller has not be registered under that key the default
|
53
|
+
# controller is returned if there is one.
|
54
|
+
#
|
55
|
+
# data:: data to extract the type from for lookup in the controllers
|
56
|
+
def controller(data)
|
57
|
+
path = data.get(:path)
|
58
|
+
path = path.native if path.is_a?(WAB::Data)
|
59
|
+
return path_controller(path) unless path.nil? || (path.length <= @path_pos)
|
60
|
+
|
61
|
+
content = data.get(:content)
|
62
|
+
return @controllers[content.get(@type_key)] || @controllers[nil] unless content.nil?
|
63
|
+
|
64
|
+
@controllers[nil]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the controller according to the type in the path.
|
68
|
+
#
|
69
|
+
# path: path Array such as from a URL
|
70
|
+
def path_controller(path)
|
71
|
+
@controllers[path[@path_pos]] || @controllers[nil]
|
31
72
|
end
|
32
|
-
|
73
|
+
|
33
74
|
# Create and return a new data instance with the provided initial value.
|
34
75
|
# The value must be a Hash or Array. The members of the Hash or Array
|
35
76
|
# must be nil, boolean, String, Integer, Float, BigDecimal, Array, Hash,
|
@@ -42,24 +83,16 @@ module WAB
|
|
42
83
|
# value:: initial value
|
43
84
|
# repair:: flag indicating invalid value should be repaired if possible
|
44
85
|
def data(value={}, repair=false)
|
45
|
-
|
86
|
+
WAB::Impl::Data.new(value, repair)
|
46
87
|
end
|
47
|
-
|
88
|
+
|
48
89
|
### View related methods.
|
49
90
|
|
50
91
|
# Push changed data to the view if it matches one of the subscription
|
51
92
|
# filters.
|
52
93
|
#
|
53
94
|
# data: Wab::Data to push to the view if subscribed
|
54
|
-
def changed(
|
55
|
-
raise NotImplementedError.new
|
56
|
-
end
|
57
|
-
|
58
|
-
# Reply asynchronously to a view request.
|
59
|
-
#
|
60
|
-
# rid:: request identifier the reply is associated with
|
61
|
-
# data:: content of the reply to be sent to the view
|
62
|
-
def reply(rid, data)
|
95
|
+
def changed(_data)
|
63
96
|
raise NotImplementedError.new
|
64
97
|
end
|
65
98
|
|
@@ -70,34 +103,22 @@ module WAB
|
|
70
103
|
#
|
71
104
|
# ref:: object reference
|
72
105
|
def get(ref)
|
73
|
-
|
74
|
-
result
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
end
|
81
|
-
end
|
82
|
-
result[:results]
|
106
|
+
result = query(where: ref.to_i, select: '$')
|
107
|
+
raise WAB::Error.new("nil result get of #{ref}.") if result.nil?
|
108
|
+
raise WAB::Error.new("error on get of #{ref}. #{result[:error]}") if 0 != result[:code]
|
109
|
+
|
110
|
+
ra = result[:results]
|
111
|
+
return nil if (ra.nil? || 0 == ra.length)
|
112
|
+
ra[0]
|
83
113
|
end
|
84
114
|
|
85
115
|
# Evaluates the JSON TQL query. The TQL should be native Ruby objects
|
86
116
|
# that correspond to the TQL JSON format but using Symbol keys instead
|
87
117
|
# of strings.
|
88
118
|
#
|
89
|
-
# If a +handler+ is provided the call is evaluated asynchronously and
|
90
|
-
# the handler is called with the result of the query. If a +handler+ is
|
91
|
-
# supplied the +tql+ must contain an +:rid+ element that is unique
|
92
|
-
# across all handlers.
|
93
|
-
#
|
94
119
|
# tql:: query to evaluate
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
# TBD handle async, maybe just send and leave it at that
|
99
|
-
|
100
|
-
@engine.request(tql)
|
120
|
+
def query(tql)
|
121
|
+
@engine.request(tql, @timeout)
|
101
122
|
end
|
102
123
|
|
103
124
|
# Subscribe to changes in stored data and push changes to the controller
|
@@ -108,34 +129,10 @@ module WAB
|
|
108
129
|
#
|
109
130
|
# controller:: the controller to notify of changed
|
110
131
|
# filter:: the filter to apply to the data. Syntax is that TQL uses for the FILTER clause.
|
111
|
-
def subscribe(
|
132
|
+
def subscribe(_controller, _filter)
|
112
133
|
raise NotImplementedError.new
|
113
134
|
end
|
114
135
|
|
115
|
-
private
|
116
|
-
|
117
|
-
def form_where_eq(key, value)
|
118
|
-
value_class = value.class
|
119
|
-
x = ['EQ', key.to_s]
|
120
|
-
if value.is_a?(String)
|
121
|
-
x << "'" + value
|
122
|
-
elsif Time == value_class
|
123
|
-
x << value.utc.iso8601(9)
|
124
|
-
elsif value.nil? ||
|
125
|
-
TrueClass == value_class ||
|
126
|
-
FalseClass == value_class ||
|
127
|
-
Integer == value_class ||
|
128
|
-
Float == value_class ||
|
129
|
-
String == value_class
|
130
|
-
x << value
|
131
|
-
elsif 2 == RbConfig::CONFIG['MAJOR'] && 4 > RbConfig::CONFIG['MINOR'] && Fixnum == value_class
|
132
|
-
x << value
|
133
|
-
else
|
134
|
-
x << value.to_s
|
135
|
-
end
|
136
|
-
x
|
137
|
-
end
|
138
|
-
|
139
136
|
end # Shell
|
140
137
|
end # IO
|
141
138
|
end # WAB
|
data/lib/wab/io.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
|
4
|
+
# This controller exposes all possible methods expected in a WAB::Controller
|
5
|
+
# subclass, as public methods.
|
6
|
+
#
|
7
|
+
# Since those methods are private in the superclass, they need to be redefined
|
8
|
+
# as public methods to enable the concerned functionality.
|
9
|
+
#
|
10
|
+
# For example, if a controller is intented to provide only read-access, then
|
11
|
+
# just the +read+ method would need to be exposed as a public method. The
|
12
|
+
# remaining methods may remain private.
|
13
|
+
class OpenController < Controller
|
14
|
+
|
15
|
+
def initialize(shell)
|
16
|
+
super(shell)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Catch requests that are not one of the below CRUD methods.
|
20
|
+
#
|
21
|
+
# Raises as it has to be handled specially.
|
22
|
+
def handle(data)
|
23
|
+
raise NotImplementedError.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def create(path, query, data)
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def read(path, query)
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def update(path, query, data)
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete(path, query)
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
end # OpenController
|
43
|
+
end # WAB
|
data/lib/wab/shell.rb
CHANGED
@@ -8,7 +8,7 @@ module WAB
|
|
8
8
|
# As the View conduit the Shell usually makes calls to the controller. The
|
9
9
|
# exception to this control flow direction is when data changes and is
|
10
10
|
# pushed out to the view.
|
11
|
-
#
|
11
|
+
#
|
12
12
|
# As the Model, the Shell must respond to request to update the store using
|
13
13
|
# either the CRUD type operations that match the controller.
|
14
14
|
#
|
@@ -17,51 +17,14 @@ module WAB
|
|
17
17
|
# the methods remain the same.
|
18
18
|
class Shell
|
19
19
|
|
20
|
-
# Sets up the shell with a type_key and path position.
|
21
|
-
#
|
22
|
-
# type_key:: key for the type associated with a record
|
23
|
-
# path_pos:: position in a URL path that is the class or type
|
24
|
-
def initialize(type_key='kind', path_pos=0)
|
25
|
-
@controllers = {}
|
26
|
-
@type_key = type_key
|
27
|
-
@path_pos = path_pos
|
28
|
-
end
|
29
|
-
|
30
|
-
# Starts the shell.
|
31
|
-
def start()
|
32
|
-
end
|
33
|
-
|
34
20
|
# Returns the path where a data type is located. The default is 'kind'.
|
35
21
|
def type_key()
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
# Register a controller for a named type.
|
40
|
-
#
|
41
|
-
# If a request is received for an unregistered type the default controller
|
42
|
-
# will be used. The default controller is registered with a +nil+ key.
|
43
|
-
#
|
44
|
-
# type:: type name
|
45
|
-
# controller:: Controller instance for handling requests for the identified +type+
|
46
|
-
def register_controller(type, controller)
|
47
|
-
controller.shell = self
|
48
|
-
@controllers[type] = controller
|
22
|
+
raise NotImplementedError.new
|
49
23
|
end
|
50
24
|
|
51
|
-
# Returns the
|
52
|
-
|
53
|
-
|
54
|
-
#
|
55
|
-
# data:: data to extract the type from for lookup in the controllers
|
56
|
-
def controller(data)
|
57
|
-
path = data.get(:path)
|
58
|
-
if path.nil? || @path_pos <= path.length
|
59
|
-
content = data.get(:content)
|
60
|
-
if content.is_a?(::WAB::Data)
|
61
|
-
@controllers[content.get(@type_key)] || @controllers[nil]
|
62
|
-
end
|
63
|
-
end
|
64
|
-
@controllers[nil]
|
25
|
+
# Returns the position of the type in a path.
|
26
|
+
def path_pos()
|
27
|
+
raise NotImplementedError.new
|
65
28
|
end
|
66
29
|
|
67
30
|
# Create and return a new data instance with the provided initial value.
|
@@ -75,7 +38,7 @@ module WAB
|
|
75
38
|
#
|
76
39
|
# value:: initial value
|
77
40
|
# repair:: flag indicating invalid value should be repaired if possible
|
78
|
-
def data(
|
41
|
+
def data(_value=nil, _repair=false)
|
79
42
|
raise NotImplementedError.new
|
80
43
|
end
|
81
44
|
|
@@ -85,15 +48,7 @@ module WAB
|
|
85
48
|
# filters.
|
86
49
|
#
|
87
50
|
# data: Wab::Data to push to the view if subscribed
|
88
|
-
def changed(
|
89
|
-
raise NotImplementedError.new
|
90
|
-
end
|
91
|
-
|
92
|
-
# Reply asynchronously to a view request.
|
93
|
-
#
|
94
|
-
# rid:: request identifier the reply is associated with
|
95
|
-
# data:: content of the reply to be sent to the view
|
96
|
-
def reply(rid, data)
|
51
|
+
def changed(_data)
|
97
52
|
raise NotImplementedError.new
|
98
53
|
end
|
99
54
|
|
@@ -103,7 +58,7 @@ module WAB
|
|
103
58
|
# is no match.
|
104
59
|
#
|
105
60
|
# ref:: object reference
|
106
|
-
def get(
|
61
|
+
def get(_ref)
|
107
62
|
raise NotImplementedError.new
|
108
63
|
end
|
109
64
|
|
@@ -111,14 +66,8 @@ module WAB
|
|
111
66
|
# that correspond to the TQL JSON format but using Symbol keys instead
|
112
67
|
# of strings.
|
113
68
|
#
|
114
|
-
# If a +handler+ is provided the call is evaluated asynchronously and
|
115
|
-
# the handler is called with the result of the query. If a +handler+ is
|
116
|
-
# supplied the +tql+ must contain an +:rid+ element that is unique
|
117
|
-
# across all handlers.
|
118
|
-
#
|
119
69
|
# tql:: query to evaluate
|
120
|
-
|
121
|
-
def query(tql, handler=nil)
|
70
|
+
def query(_tql)
|
122
71
|
raise NotImplementedError.new
|
123
72
|
end
|
124
73
|
|
@@ -130,7 +79,43 @@ module WAB
|
|
130
79
|
#
|
131
80
|
# controller:: the controller to notify of changed
|
132
81
|
# filter:: the filter to apply to the data. Syntax is that TQL uses for the FILTER clause.
|
133
|
-
def subscribe(
|
82
|
+
def subscribe(_controller, _filter)
|
83
|
+
raise NotImplementedError.new
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns true if error logging is turned on.
|
87
|
+
def error?
|
88
|
+
raise NotImplementedError.new
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns true if warn logging is turned on.
|
92
|
+
def warn?
|
93
|
+
raise NotImplementedError.new
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns true if info logging is turned on.
|
97
|
+
def info?
|
98
|
+
raise NotImplementedError.new
|
99
|
+
end
|
100
|
+
|
101
|
+
# Logs an error with the shell logger.
|
102
|
+
#
|
103
|
+
# message:: message to log
|
104
|
+
def error(_message)
|
105
|
+
raise NotImplementedError.new
|
106
|
+
end
|
107
|
+
|
108
|
+
# Logs a warning with the shell logger.
|
109
|
+
#
|
110
|
+
# message:: message to log
|
111
|
+
def warn(_message)
|
112
|
+
raise NotImplementedError.new
|
113
|
+
end
|
114
|
+
|
115
|
+
# Logs an info with the shell logger.
|
116
|
+
#
|
117
|
+
# message:: message to log
|
118
|
+
def info(_message)
|
134
119
|
raise NotImplementedError.new
|
135
120
|
end
|
136
121
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module WAB
|
5
|
+
# mixin module meant to be 'included' into `WAB::Shell`, `WAB::IO::Shell`
|
6
|
+
# and `WAB::Impl::Shell`
|
7
|
+
module ShellLogger
|
8
|
+
attr_accessor :logger
|
9
|
+
|
10
|
+
extend Forwardable
|
11
|
+
def_delegators :@logger, :info?, :warn?, :error?, :info, :warn, :error
|
12
|
+
end
|
13
|
+
end
|
data/lib/wab/utils.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module WAB
|
2
|
+
module Utils
|
3
|
+
class << self
|
4
|
+
UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
5
|
+
TIME_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}\.\d{9}Z$/
|
6
|
+
|
7
|
+
def ruby_series
|
8
|
+
RbConfig::CONFIG.values_at('MAJOR', 'MINOR').join.to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
# Detect if `obj` is an instance of `Fixnum` from Ruby older than 2.4.x
|
12
|
+
def pre_24_fixnum?(obj)
|
13
|
+
24 > ruby_series && obj.is_a?(Fixnum)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Determine if a given object is not an empty Hash
|
17
|
+
def populated_hash?(obj)
|
18
|
+
obj.is_a?(Hash) && !obj.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
# Detect if given string matches ISO/IEC UUID format:
|
22
|
+
# "123e4567-e89b-12d3-a456-426655440000"
|
23
|
+
def uuid_format?(str)
|
24
|
+
return false unless 36 == str.length
|
25
|
+
!UUID_REGEX.match(str).nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Detect if given string matches a Time format as encoded by WAB components:
|
29
|
+
# "2017-09-01T12:45:15.123456789Z"
|
30
|
+
def wab_time_format?(str)
|
31
|
+
return false unless 30 == str.length
|
32
|
+
!TIME_REGEX.match(str).nil?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|