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