wabur 0.2.0d1 → 0.4.0d1
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 +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
|