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