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