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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 86b81a950a3090451b79d8d4841b4acaa340f208
4
- data.tar.gz: 2ac14502f2dabdd7f294be9eeae7c16f068ffadb
3
+ metadata.gz: f7d968658f0ee232237f5817e4edd4b79484564e
4
+ data.tar.gz: cb9cf59b684c7fdc5cdc2e3df9b858d1cf351ca9
5
5
  SHA512:
6
- metadata.gz: 16411e53df75d17339ecdf1efe6b6afc84945c01a71ce2ff2f34a83a3a8e965ba73177286afaf273543711cc5a4646b9742c166699e2f01e80cb813cfa20a32f
7
- data.tar.gz: 8f9ca7584c9395e39ccabaab4e0229b0f4e306651adaea727efdf4460dac1ed45e21df33073b54cdd09a3992c4ba35a0f6adf10fe496ce5a82b85cf9c9bf703e
6
+ metadata.gz: 1730e21a9f21877d3d2f4919423af5bd6c9c23256a163a480ed5474552c61f987a20d3dcf1386a418c5142fc8aaeedb6b5b023ac4aecafd81d346433c5fe69af
7
+ data.tar.gz: a3ec0dca31b501e31f6faa076db5e070f25492a023d4a37f725ec73329e92c1d663a33d175c19fcd1f1fb6c0c7536166f2262c848e9f6350c4468e2960a8cad4
data/README.md CHANGED
@@ -16,19 +16,21 @@ performance Ruby web framework.
16
16
  Ruby on Rails has made Ruby mainstream. While RoR is fine for some
17
17
  applications there are others that might be better served with an alternative.
18
18
  This project was started as an alternative to Ruby on Rails with a focus on
19
- performance and ease of use.
19
+ performance and ease of use. The use of Javascript for views and NoSQL JSON
20
+ databases are some of the most notable differences.
20
21
 
21
- Why develop an alternative to Rails? The popularity of Rails has been waning.
22
- It is still huge but not as popular as it used to be. RoR is not going away
23
- any time soon but for some applications, alternatives are needed.
22
+ Why develop an alternative to Rails? Developers that want to make more custom
23
+ web sites with heavier use of Javascript, Websockets, and SSE along with a
24
+ JSON database don't fit nicely in the Rails mold. WAB attempts to address that
25
+ area that falls outside of Rail's strengths.
24
26
 
25
27
  ## Goals
26
28
 
27
- Lets start with the assumption that we want to continue using Ruby. The goal
28
- of this project is to provide a high performance, easy to use, and a fully
29
- featured web framework with Ruby at the core. By keeping the core, the
30
- business logic, in Ruby but allowing options for other parts to be in different
31
- languages, the best of each can be utilized.
29
+ Lets start with the primary assumption, that we want to continue using
30
+ Ruby. The goal of this project is to provide a high performance, easy to use,
31
+ and a fully featured web framework with Ruby at the core. By keeping the core,
32
+ the business logic, in Ruby but allowing options for other parts to be in
33
+ different languages, the best of each language can be utilized.
32
34
 
33
35
  Targets are a throughput of 100K page fetches per second at a latency of no
34
36
  more than 1 millisecond on a desktop machine. That is more than an order of
@@ -39,15 +41,39 @@ web frameworks across all languages.
39
41
 
40
42
  ## Architecture
41
43
 
42
- The architecture provides many options but it keeps clean and clear APIs
43
- between modules. This pluggable design allows for unit test drivers and
44
- various levels of deployment options from straight Ruby to a high performance
45
- C shell that handles HTTP and data storage.
44
+ The architecture provides many options but keeps a clean and clear API between
45
+ modules. This pluggable design allows for unit test drivers and various levels
46
+ of deployment options from straight Ruby to a high performance C runner that
47
+ handles HTTP and data storage.
46
48
 
47
- ![](http://www.opo.technology/wab/wab_arch.svg)
49
+ Three configuration are planned. One is to use a Runner that calls to the Ruby
50
+ core controller through pipes on ```$stdin``` and ```$stdout```. A second is to implement
51
+ a runner in Ruby. The third is to use a C Runner with embedded Ruby.
52
+
53
+ A Runner that spawns (forks) and runs a Ruby Controller makes use of the
54
+ ```::WAB::IO::Shell```.
55
+
56
+ ![](http://www.opo.technology/wab/wab_remote_arch.svg)
57
+
58
+ The Ruby Runner and C Runner with embedded ruby follow the same architecture.
59
+
60
+ ![](http://www.opo.technology/wab/wab_embedded_arch.svg)
61
+
62
+ Access to data can follow two paths. A direct access to the data is possible
63
+ as portrayed by the red line that flows from HTTP server to the runner and
64
+ onto the Model. The other path is to dive down into the Ruby Controller and
65
+ allow the Controller to modify and control what is returned by a request. The
66
+ Benchmark results in the example/sample/README.md includes the latest results.
67
+
68
+ ![](http://www.opo.technology/wab/wab_access_paths.svg)
48
69
 
49
70
  [Continue reading ...](pages/Architecture.md)
50
71
 
72
+ ## Try It!
73
+
74
+ A sample is now available in the ```examples/sample/``` directory. There are
75
+ some preliminary laptop benchmark results described in the README.
76
+
51
77
  ## Participate and Contribute
52
78
 
53
79
  If you like the idea and want to help out, or become a core developer on the
data/bin/wabur ADDED
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ while (index = ARGV.index('-I'))
5
+ _, path = ARGV.slice!(index, 2)
6
+ $: << path
7
+ end
8
+
9
+ require 'optparse'
10
+ require 'logger'
11
+
12
+ require 'wab'
13
+ require 'wab/impl'
14
+
15
+ # The options for this application are determined by the
16
+ # +WAB::Impl::Configuration+ class which takes an +usage+ string and an
17
+ # +options+ hash. The returned object is a +WAB::Impl::Configuration+
18
+ # object that is used by the Shell to configure itself before being started.
19
+
20
+ # Prepare the +usage+ string.
21
+ # Basically a banner text and description passed on +OptionParser+.
22
+ usage = %{
23
+ Usage: wabur [options]
24
+
25
+ A pure Ruby WAB Runner.
26
+ Configured directly via command-line options or via a configuration file which
27
+ can either be a UNIX-style conf file, or a JSON file, or a YAML file.
28
+ The configuration typically includes designating Controller classes for each
29
+ URL path to be handled.
30
+
31
+ }
32
+
33
+ # Prepare the +options+ hash. Basically a mapping of supported switches and
34
+ # their description that gets parsed by +OptionParser+. It is also used to
35
+ # generate default values.
36
+ options = {
37
+ base: {
38
+ val: '.',
39
+ type: String,
40
+ doc: 'App root directory which is $BASE.',
41
+ arg: 'PATH',
42
+ short: '-b'
43
+ },
44
+ store: {
45
+ dir: {
46
+ val: '$BASE/data',
47
+ type: String,
48
+ doc: "Directory to use for data storage.",
49
+ arg: 'PATH',
50
+ }
51
+ },
52
+ path_prefix: {
53
+ val: '/v1',
54
+ type: String,
55
+ doc: 'URL path prefix for relative handler routing.',
56
+ arg: 'PREFIX',
57
+ },
58
+ handler: {
59
+ val: [],
60
+ doc: 'Type and handler/controller class in the form <type>=<controller class>.',
61
+ short: '-t',
62
+ parse: [:type, :handler],
63
+ arg: 'PAIR',
64
+ },
65
+ type_key: {
66
+ val: 'kind',
67
+ type: String,
68
+ doc: 'Shell type_key.',
69
+ arg: 'KEY',
70
+ },
71
+ http: {
72
+ dir: {
73
+ val: '$BASE/pages',
74
+ type: String,
75
+ doc: 'Directory where HTTP content resides.',
76
+ arg: 'PATH',
77
+ },
78
+ port: {
79
+ val: 6363,
80
+ type: Integer,
81
+ doc: 'HTTP Port to listen on.',
82
+ arg: 'PORT',
83
+ },
84
+ },
85
+ verbosity: {
86
+ val: 'WARN',
87
+ type: String,
88
+ doc: 'Log level. (ERROR, WARN, INFO, DEBUG)',
89
+ arg: 'LEVEL',
90
+ },
91
+ }
92
+
93
+ config = WAB::Impl::Configuration.new(usage, options)
94
+
95
+ # The Configuration object can be modified before initializing the Shell. By
96
+ # setting the +config[:logger]+ the Shell will use that as the logger. The
97
+ # +config[:handler]+ array can also be modified by setting path values along
98
+ # with a Controller instance, a Controller class, or the name of a Controller
99
+ # class.
100
+ config[:logger] = Logger.new(STDOUT)
101
+
102
+ # Start a shell initialized with the final configuration.
103
+ WAB::Impl::Shell.new(config).start
@@ -2,17 +2,22 @@ module WAB
2
2
 
3
3
  # A Controller class or a duck-typed alternative should be created and
4
4
  # registered with a Shell for any type that implements behavior other than
5
- # the default REST API processing. If a public method is not found on the
6
- # class instance then the default REST API processing will be used.
5
+ # the default REST API processing.
6
+ #
7
+ # When a request arrives at the Shell, the expected Controller method is
8
+ # identifed and a check is made to verify if the Controller responds to the
9
+ # method. If it does not, then the +handle+ method is called. Since
10
+ # +respond_to+ includes only public methods, only those methods made public
11
+ # in a Controller subclass are considered. The candidates for making public
12
+ # are +create+, +read+, +update+, and +delete+.
7
13
  #
8
14
  # A description of the available methods is included as private methods.
9
15
  class Controller # :doc: all
10
16
  attr_accessor :shell
11
17
 
12
18
  # Create a instance.
13
- def initialize(shell, async=false)
19
+ def initialize(shell)
14
20
  @shell = shell
15
- # TBD handle async
16
21
  end
17
22
 
18
23
  # Handler for paths that do not match the REST pattern or for unregistered
@@ -22,7 +27,7 @@ module WAB
22
27
  # on to the requester. The result, if not nil, should be a Data instance.
23
28
  #
24
29
  # data:: data to be processed
25
- def handle(data)
30
+ def handle(_data)
26
31
  nil
27
32
  end
28
33
 
@@ -34,8 +39,6 @@ module WAB
34
39
  # Create a new data object. If a query is provided it is treated as a
35
40
  # check against an existing object with the same key/value pairs.
36
41
  #
37
- # The reference to the object created is returned on success.
38
- #
39
42
  # On error an Exception should be raised.
40
43
  #
41
44
  # path:: array of tokens in the path.
@@ -44,50 +47,46 @@ module WAB
44
47
  def create(path, query, data) # :doc:
45
48
  tql = { }
46
49
  kind = path[@shell.path_pos]
47
- if query.is_a?(Hash) && 0 < query.size
48
- where = ['AND']
49
- where << form_where_eq(@shell.type_key, kind)
50
- query.each_pair { |k,v| where << form_where_eq(k, v) }
51
- tql[:where] = where
50
+ if WAB::Utils.populated_hash?(query)
51
+ tql[:where] = and_where(kind, query)
52
52
  end
53
53
  tql[:insert] = data.native
54
54
  shell_query(tql, kind, 'create')
55
55
  end
56
56
 
57
- # Return the objects according to the path and query arguments.
58
- #
59
- # If the path includes an object reference then that object is returned as
60
- # the only member of the results list of a WAB::Data returned.
57
+ # Return the objects according to the path and query arguments. The
58
+ # following patterns supported:
61
59
  #
62
- # If there is no object reference in the path then the attributes are used
63
- # to find matching objects. The objects that have the same key/value pairs
64
- # are returned.
60
+ # * [MyType/12345] looks for MyType with reference ID of 12345
61
+ # * [MyType?name=fred&age=63] looks for all MyTypes with a name of 'fred'
62
+ # and an age of 63.
63
+ # * [MyType/list?Name=name&Age=age] returns only the name and age
64
+ # attributes and places them in a Hash
65
+ # with the keys :Name and :Age along
66
+ # with the record reference as :ref.
65
67
  #
66
68
  # path:: array of tokens in the path.
67
- # query:: query parameters from a URL as a Hash with keys matching paths
68
- # into the target objects and value equal to the target attribute
69
- # values. A path can be an array of keys used to walk a path to
70
- # the target or a +.+ delimited set of keys.
69
+ # query:: query parameters from a URL as a Hash with key and value
70
+ # pairs. Note that duplicate keys will result in only the last
71
+ # option being present,
71
72
  def read(path, query) # :doc:
72
- if @shell.path_pos + 2 == path.length # has an object reference in the path
73
- ref = path[@shell.path_pos + 1].to_i
74
- obj = @shell.get(ref)
75
- obj = obj.native if obj.is_a?(::WAB::Data)
76
- return @shell.data({ code: 0, results: [ { id: ref, data: obj } ]})
77
- end
78
- tql = { }
79
73
  kind = path[@shell.path_pos]
80
- # No id so must be either a simple query by attribute or a list.
81
- if query.is_a?(Hash) && 0 < query.size
82
- where = ['AND']
83
- where << form_where_eq(@shell.type_key, kind)
84
- query.each_pair { |k,v| where << form_where_eq(k, v) }
74
+ # Check for the type and object reference pattern as well as the list
75
+ # pattern.
76
+ if @shell.path_pos + 2 == path.length
77
+ ref = path[@shell.path_pos + 1]
78
+ return list_select(kind, query) if 'list' == ref
79
+
80
+ # Read a single object/record.
81
+ ref = ref.to_i
82
+ obj = @shell.get(ref)
83
+ obj = obj.native if obj.is_a?(WAB::Data)
84
+ results = []
85
+ results << {id: ref, data: obj} unless obj.nil?
86
+ @shell.data({ code: 0, results: results})
85
87
  else
86
- where = form_where_eq(@shell.type_key, kind)
88
+ list_match(kind, query)
87
89
  end
88
- tql[:where] = where
89
- tql[:select] = { id: '$ref', data: '$' }
90
- shell_query(tql, kind, 'read')
91
90
  end
92
91
 
93
92
  # Replaces the object data for the identified object.
@@ -104,13 +103,10 @@ module WAB
104
103
  kind = path[@shell.path_pos]
105
104
  if @shell.path_pos + 2 == path.length # has an object reference in the path
106
105
  tql[:where] = path[@shell.path_pos + 1].to_i
107
- elsif query.is_a?(Hash) && 0 < query.size
108
- where = ['AND']
109
- where << form_where_eq(@shell.type_key, kind)
110
- query.each_pair { |k,v| where << form_where_eq(k, v) }
111
- tql[:where] = where
106
+ elsif WAB::Utils.populated_hash?(query)
107
+ tql[:where] = and_where(kind, query)
112
108
  else
113
- raise ::WAB::Error.new("update on all #{kind} not allowed.")
109
+ raise WAB::Error.new("update on all #{kind} not allowed.")
114
110
  end
115
111
  tql[:update] = data.native
116
112
  shell_query(tql, kind, 'update')
@@ -133,16 +129,13 @@ module WAB
133
129
  def delete(path, query) # :doc:
134
130
  tql = { }
135
131
  kind = path[@shell.path_pos]
136
- if @shell.path_pos + 2 == path.length # has an object reference in the path
137
- tql[:where] = path[@shell.path_pos + 1].to_i
138
- elsif query.is_a?(Hash) && 0 < query.size
139
- where = ['AND']
140
- where << form_where_eq(@shell.type_key, kind)
141
- query.each_pair { |k,v| where << form_where_eq(k, v) }
142
- tql[:where] = where
143
- else
144
- tql[:where] = form_where_eq(@shell.type_key, kind)
145
- end
132
+ tql[:where] = if @shell.path_pos + 2 == path.length # has an object reference in the path
133
+ path[@shell.path_pos + 1].to_i
134
+ elsif WAB::Utils.populated_hash?(query)
135
+ and_where(kind, query)
136
+ else
137
+ form_where_eq(@shell.type_key, kind)
138
+ end
146
139
  tql[:delete] = nil
147
140
  shell_query(tql, kind, 'delete')
148
141
  end
@@ -167,6 +160,33 @@ module WAB
167
160
  @shell.changed(data)
168
161
  end
169
162
 
163
+ # A private method to gather sets of Hashes that include the fields
164
+ # specified in the fields Hash.
165
+ def list_select(kind, fields)
166
+ tql = {}
167
+ select = { ref: '$ref' }
168
+ if WAB::Utils.populated_hash?(fields)
169
+ fields.each_pair { |k,v| select[k] = v }
170
+ end
171
+ tql[:where] = form_where_eq(@shell.type_key, kind)
172
+ tql[:select] = select
173
+ shell_query(tql, kind, 'read')
174
+ end
175
+
176
+ # A private method to gather a list of objects that match the query
177
+ # parameters.
178
+ def list_match(kind, query)
179
+ tql = {}
180
+ # If there is a query set up a where clause.
181
+ tql[:where] = if WAB::Utils.populated_hash?(query)
182
+ and_where(kind, query)
183
+ else
184
+ form_where_eq(@shell.type_key, kind)
185
+ end
186
+ tql[:select] = { id: '$ref', data: '$' }
187
+ shell_query(tql, kind, 'read')
188
+ end
189
+
170
190
  # Form a EQ expression for a TQL where clause. Used as a helper to the
171
191
  # primary API calls.
172
192
  #
@@ -175,48 +195,71 @@ module WAB
175
195
  def form_where_eq(key, value)
176
196
  value_class = value.class
177
197
  x = ['EQ', key.to_s]
178
- if value.is_a?(String)
179
- x << "'" + value
180
- elsif Time == value_class
181
- x << value.utc.iso8601(9)
182
- elsif value.nil? ||
183
- TrueClass == value_class ||
184
- FalseClass == value_class ||
185
- Integer == value_class ||
186
- Float == value_class
187
- x << value
188
- elsif String == value_class
189
- if 0 < value.length && '\'' == value[0]
190
- x << value[1..-1]
191
- elsif /^-?\d+$/.match?(value)
192
- x << value.to_i
193
- elsif /^-?\d*\.?\d+([eE][-+]?\d+)?$/.match?(value)
194
- x << value.to_f
195
- end
196
- # TBD detect other types UUID, HTTP, Time
197
-
198
- elsif '2' == RbConfig::CONFIG['MAJOR'] && '4' > RbConfig::CONFIG['MINOR'] && Fixnum == value_class
199
- x << value
200
- else
201
- x << value.to_s
202
- end
198
+ x << if Time == value_class
199
+ value.utc.iso8601(9)
200
+ elsif value.nil? ||
201
+ TrueClass == value_class ||
202
+ FalseClass == value_class ||
203
+ Integer == value_class ||
204
+ Float == value_class
205
+ value
206
+ elsif String == value_class
207
+ # if the string matches a detectable type then don't quote it
208
+ detect_string(value)
209
+ elsif WAB::Utils.pre_24_fixnum?(value)
210
+ value
211
+ else
212
+ value.to_s
213
+ end
203
214
  x
204
215
  end
205
216
 
206
- # Helper to send TQL requests to the shell either synchronously or
207
- # asynchronously depending on the controller type.
208
- def shell_query(tql, kind, op)
217
+ # Form an AND expression for a TQL where clause.
218
+ def and_where(kind, query)
219
+ where = ['AND']
220
+ where << form_where_eq(@shell.type_key, kind)
221
+ query.each_pair { |k,v| where << form_where_eq(k, v) }
222
+ where
223
+ end
209
224
 
210
- # TBD check for async or not
225
+ # Detects strings that are representation of something else such as an
226
+ # integer, UUID, Time, or URI. Used to convert URL query parameters to TQL
227
+ # types. That also means string are quoted for TQL with a single leading
228
+ # single quote character unless one is already present. No trailing single
229
+ # quote is added.
230
+ def detect_string(value)
231
+ # if the string matches a detectable type then don't quote it
232
+ # ok as is
233
+ return value if !value.empty? && value.start_with?("'")
211
234
 
212
- result = @shell.query(tql, nil) # synchronous call
213
- if result.nil? || 0 != result[:code]
214
- if result.nil?
215
- raise ::WAB::Error.new("nil result on #{kind} #{op}.")
216
- else
217
- raise ::WAB::Error.new("error on #{kind} #{op}. #{result[:error]}")
235
+ if !/^-?\d+$/.match(value).nil?
236
+ value.to_i
237
+ elsif !/^-?\d*\.?\d+([eE][-+]?\d+)?$/.match(value).nil?
238
+ value.to_f
239
+ elsif WAB::Utils.uuid_format?(value)
240
+ WAB::UUID.new(value)
241
+ elsif WAB::Utils.wab_time_format?(value)
242
+ begin
243
+ DateTime.parse(value).to_time
244
+ rescue
245
+ "'" + value
246
+ end
247
+ elsif value.downcase.start_with?('http://')
248
+ begin
249
+ URI(value)
250
+ rescue
251
+ "'" + value
218
252
  end
253
+ else
254
+ "'" + value
219
255
  end
256
+ end
257
+
258
+ # Helper to send TQL requests to the shell.
259
+ def shell_query(tql, kind, op)
260
+ result = @shell.query(tql)
261
+ raise WAB::Error.new("nil result on #{kind} #{op}.") if result.nil?
262
+ raise WAB::Error.new("error on #{kind} #{op}. #{result[:error]}") if 0 != result[:code]
220
263
  result
221
264
  end
222
265
 
data/lib/wab/data.rb CHANGED
@@ -9,14 +9,22 @@ module WAB
9
9
  # class (has the same methods and behavior).
10
10
  class Data
11
11
 
12
+ # Returns true if the Data element or value identified by the path
13
+ # exists where the path elements are separated by the '.' character. The
14
+ # path can also be a array of path node identifiers. For example,
15
+ # child.grandchild is the same as ['child', 'grandchild'].
16
+ def has?(_path)
17
+ raise NotImplementedError.new
18
+ end
19
+
12
20
  # Gets the Data element or value identified by the path where the path
13
21
  # elements are separated by the '.' character. The path can also be a
14
22
  # array of path node identifiers. For example, child.grandchild is the
15
23
  # same as ['child', 'grandchild'].
16
- def get(path)
24
+ def get(_path)
17
25
  raise NotImplementedError.new
18
26
  end
19
-
27
+
20
28
  # Sets the node value identified by the path where the path elements are
21
29
  # separated by the '.' character. The path can also be a array of path
22
30
  # node identifiers. For example, child.grandchild is the same as ['child',
@@ -30,7 +38,7 @@ module WAB
30
38
  # path:: path to location to be set
31
39
  # value:: value to set
32
40
  # repair:: flag indicating invalid value should be repaired if possible
33
- def set(path, value)
41
+ def set(_path, _value)
34
42
  raise NotImplementedError.new
35
43
  end
36
44
 
@@ -49,7 +57,7 @@ module WAB
49
57
  end
50
58
 
51
59
  # Make a deep copy of the Data instance.
52
- def clone()
60
+ def deep_dup()
53
61
  raise NotImplementedError.new
54
62
  end
55
63
 
@@ -59,29 +67,8 @@ module WAB
59
67
  raise NotImplementedError.new
60
68
  end
61
69
 
62
- # Returns true if self and other are either the same or have the same
63
- # contents. This is a deep comparison.
64
- def eql?(other)
65
- raise NotImplementedError.new
66
- end
67
-
68
- # Returns the length of the root element.
69
- def length()
70
- raise NotImplementedError.new
71
- end
72
-
73
- # Returns the number of leaves in the data tree.
74
- def leaf_count()
75
- raise NotImplementedError.new
76
- end
77
-
78
- # Returns the number of nodes in the data tree.
79
- def size()
80
- raise NotImplementedError.new
81
- end
82
-
83
70
  # Encode the data as a JSON string.
84
- def json(indent=0)
71
+ def json(_indent=0)
85
72
  raise NotImplementedError.new
86
73
  end
87
74
 
@@ -102,7 +89,7 @@ module WAB
102
89
  # of the Hash or Array must be nil, boolean, String, Integer, Float,
103
90
  # BigDecimal, Array, Hash, Time, URI::HTTP, or WAB::UUID. Keys to Hashes
104
91
  # must be Symbols.
105
- def initialize(value=nil, repair=false)
92
+ def initialize(_value=nil, _repair=false)
106
93
  raise NotImplementedError.new
107
94
  end
108
95
 
data/lib/wab/errors.rb CHANGED
@@ -1,13 +1,19 @@
1
1
 
2
2
  module WAB
3
3
 
4
- # Base for WAB errors and exceptions.
5
- class Error < StandardError
6
- end # Error
4
+ Error = Class.new(StandardError) # Base for WAB errors and exceptions.
5
+ ParseError = Class.new(Error) # Raised as a result of a error while parsing.
7
6
 
8
- # An Exception that is raised as a result of a parse error while parsing a
9
- # JSON document.
10
- class ParseError < Error
11
- end # ParseError
7
+ class TypeError < Error
8
+ def initialize(msg='Data values must either be a Hash or an Array')
9
+ super(msg)
10
+ end
11
+ end
12
12
 
13
- end # Oj
13
+ class KeyError < Error
14
+ def initialize(msg='Hash keys must be Symbols')
15
+ super(msg)
16
+ end
17
+ end
18
+
19
+ end # WAB
@@ -0,0 +1,25 @@
1
+
2
+ module WAB
3
+ module Impl
4
+
5
+ class BoolExpr < Expr
6
+
7
+ # Create an expression with the provided arguments which must be
8
+ # instances of subclasses of the Expr class.
9
+ #
10
+ # args:: argument to the expression
11
+ def initialize(*args)
12
+ @args = args
13
+ end
14
+
15
+ def append_arg(arg)
16
+ @args << arg
17
+ end
18
+
19
+ def eval(_data)
20
+ false
21
+ end
22
+
23
+ end # BoolExpr
24
+ end # Impl
25
+ end # WAB