wabur 0.2.0d1 → 0.4.0d1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +40 -14
  3. data/bin/wabur +103 -0
  4. data/lib/wab/controller.rb +133 -90
  5. data/lib/wab/data.rb +14 -27
  6. data/lib/wab/errors.rb +14 -8
  7. data/lib/wab/impl/bool_expr.rb +25 -0
  8. data/lib/wab/impl/configuration.rb +166 -0
  9. data/lib/wab/impl/data.rb +155 -232
  10. data/lib/wab/impl/expr.rb +26 -0
  11. data/lib/wab/impl/expr_parser.rb +55 -0
  12. data/lib/wab/impl/exprs/and.rb +29 -0
  13. data/lib/wab/impl/exprs/between.rb +41 -0
  14. data/lib/wab/impl/exprs/eq.rb +28 -0
  15. data/lib/wab/impl/exprs/gt.rb +30 -0
  16. data/lib/wab/impl/exprs/gte.rb +30 -0
  17. data/lib/wab/impl/exprs/has.rb +26 -0
  18. data/lib/wab/impl/exprs/in.rb +28 -0
  19. data/lib/wab/impl/exprs/lt.rb +30 -0
  20. data/lib/wab/impl/exprs/lte.rb +30 -0
  21. data/lib/wab/impl/exprs/not.rb +27 -0
  22. data/lib/wab/impl/exprs/or.rb +29 -0
  23. data/lib/wab/impl/exprs/regex.rb +28 -0
  24. data/lib/wab/impl/handler.rb +95 -0
  25. data/lib/wab/impl/model.rb +197 -0
  26. data/lib/wab/impl/path_expr.rb +14 -0
  27. data/lib/wab/impl/shell.rb +92 -7
  28. data/lib/wab/impl/utils.rb +110 -0
  29. data/lib/wab/impl.rb +24 -0
  30. data/lib/wab/io/call.rb +4 -7
  31. data/lib/wab/io/engine.rb +128 -51
  32. data/lib/wab/io/shell.rb +61 -64
  33. data/lib/wab/io.rb +0 -2
  34. data/lib/wab/open_controller.rb +43 -0
  35. data/lib/wab/shell.rb +46 -61
  36. data/lib/wab/shell_logger.rb +13 -0
  37. data/lib/wab/utils.rb +36 -0
  38. data/lib/wab/uuid.rb +3 -6
  39. data/lib/wab/version.rb +2 -2
  40. data/lib/wab.rb +3 -0
  41. data/pages/Plan.md +20 -14
  42. data/test/bench_io_shell.rb +49 -0
  43. data/test/{impl_test.rb → helper.rb} +2 -4
  44. data/test/mirror_controller.rb +16 -0
  45. data/test/test_configuration.rb +38 -0
  46. data/test/test_data.rb +207 -0
  47. data/test/test_expr.rb +35 -0
  48. data/test/test_expr_and.rb +24 -0
  49. data/test/test_expr_between.rb +43 -0
  50. data/test/test_expr_eq.rb +24 -0
  51. data/test/test_expr_gt.rb +24 -0
  52. data/test/test_expr_gte.rb +24 -0
  53. data/test/test_expr_has.rb +19 -0
  54. data/test/test_expr_in.rb +24 -0
  55. data/test/test_expr_lt.rb +24 -0
  56. data/test/test_expr_lte.rb +24 -0
  57. data/test/test_expr_not.rb +22 -0
  58. data/test/test_expr_or.rb +24 -0
  59. data/test/test_expr_regex.rb +30 -0
  60. data/test/test_impl.rb +38 -0
  61. data/test/test_io_shell.rb +189 -0
  62. data/test/test_model.rb +31 -0
  63. data/test/test_runner.rb +177 -0
  64. data/test/tests.rb +3 -8
  65. metadata +91 -18
  66. data/lib/wab/model.rb +0 -136
  67. data/lib/wab/view.rb +0 -21
  68. data/test/data_test.rb +0 -253
  69. data/test/ioshell_test.rb +0 -461
data/lib/wab/io/engine.rb CHANGED
@@ -1,8 +1,7 @@
1
1
 
2
- require 'wab'
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
- process_msg(@queue.pop)
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
- # TBD create timeout thread, sync on lock to check timeout in pending, sleep for .5
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
- if 1 == api
41
+ @shell.info("=> controller #{Oj.dump(msg, mode: :wab)}") if @shell.info?
42
+ case api
43
+ when 1
36
44
  @queue.push(msg)
37
- elsif 4 == api
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
- # TBD handle error
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() # TBD make timeout seconds a parameter
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
- data = @shell.data({ rid: call.rid, api: 3, body: tql }, true)
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
- Thread.stop
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
- reply = @shell.data({rid: rid, api: 2})
80
- if body.nil?
81
- reply.set('body.code', -1)
82
- reply.set('body.error', 'No body in request.')
83
- else
84
- data = @shell.data(body, false)
85
- data.detect()
86
- controller = @shell.controller(data)
87
- if controller.nil?
88
- reply.set('body.code', -1)
89
- reply.set('body.error', 'No handler found.')
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
- op = body[:op]
92
- begin
93
- if 'NEW' == op && controller.respond_to?(:create)
94
- reply.set('body', controller.create(body[:path], body[:query], data.get(:content)))
95
- elsif 'GET' == op && controller.respond_to?(:read)
96
- reply.set('body', controller.read(body[:path], body[:query]))
97
- elsif 'DEL' == op && controller.respond_to?(:delete)
98
- reply.set('body', controller.delete(body[:path], body[:query]))
99
- elsif 'MOD' == op && controller.respond_to?(:update)
100
- # Also used for TQL queries
101
- reply.set('body', controller.update(body[:path], body[:query], data.get(:content)))
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
- reply.set('body', controller.handle(data))
169
+ false
104
170
  end
105
- rescue Exception => e
106
- reply.set('body.code', -1)
107
- reply.set('body.error', e.message)
108
- reply.set('body.backtrace', e.backtrace)
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
- end
179
+ }
111
180
  end
112
- $stdout.puts(reply.json)
113
- $stdout.flush
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 < ::WAB::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
- super(type_key, path_pos)
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
- ::WAB::Impl::Data.new(value, repair)
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(data)
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
- tql = { where: ref.to_i, select: '$' }
74
- result = @engine.request(tql)
75
- if result.nil? || 0 != result[:code]
76
- if result.nil?
77
- raise ::WAB::Error.new("nil result get of #{ref}.")
78
- else
79
- raise ::WAB::Error.new("error on get of #{ref}. #{result[:error]}")
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
- # handler:: callback handler that implements the #on_result() method
96
- def query(tql, handler=nil)
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(controller, filter)
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
@@ -9,5 +9,3 @@ end
9
9
  require 'wab/io/shell'
10
10
  require 'wab/io/engine'
11
11
  require 'wab/io/call'
12
-
13
-
@@ -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
- @type_key
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 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
- 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(value=nil, repair=false)
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(data)
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(ref)
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
- # handler:: callback handler that implements the #on_result() method
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(controller, filter)
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