wabur 0.2.0d1 → 0.4.0d1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -14
- data/bin/wabur +103 -0
- data/lib/wab/controller.rb +133 -90
- data/lib/wab/data.rb +14 -27
- data/lib/wab/errors.rb +14 -8
- data/lib/wab/impl/bool_expr.rb +25 -0
- data/lib/wab/impl/configuration.rb +166 -0
- data/lib/wab/impl/data.rb +155 -232
- data/lib/wab/impl/expr.rb +26 -0
- data/lib/wab/impl/expr_parser.rb +55 -0
- data/lib/wab/impl/exprs/and.rb +29 -0
- data/lib/wab/impl/exprs/between.rb +41 -0
- data/lib/wab/impl/exprs/eq.rb +28 -0
- data/lib/wab/impl/exprs/gt.rb +30 -0
- data/lib/wab/impl/exprs/gte.rb +30 -0
- data/lib/wab/impl/exprs/has.rb +26 -0
- data/lib/wab/impl/exprs/in.rb +28 -0
- data/lib/wab/impl/exprs/lt.rb +30 -0
- data/lib/wab/impl/exprs/lte.rb +30 -0
- data/lib/wab/impl/exprs/not.rb +27 -0
- data/lib/wab/impl/exprs/or.rb +29 -0
- data/lib/wab/impl/exprs/regex.rb +28 -0
- data/lib/wab/impl/handler.rb +95 -0
- data/lib/wab/impl/model.rb +197 -0
- data/lib/wab/impl/path_expr.rb +14 -0
- data/lib/wab/impl/shell.rb +92 -7
- data/lib/wab/impl/utils.rb +110 -0
- data/lib/wab/impl.rb +24 -0
- data/lib/wab/io/call.rb +4 -7
- data/lib/wab/io/engine.rb +128 -51
- data/lib/wab/io/shell.rb +61 -64
- data/lib/wab/io.rb +0 -2
- data/lib/wab/open_controller.rb +43 -0
- data/lib/wab/shell.rb +46 -61
- data/lib/wab/shell_logger.rb +13 -0
- data/lib/wab/utils.rb +36 -0
- data/lib/wab/uuid.rb +3 -6
- data/lib/wab/version.rb +2 -2
- data/lib/wab.rb +3 -0
- data/pages/Plan.md +20 -14
- data/test/bench_io_shell.rb +49 -0
- data/test/{impl_test.rb → helper.rb} +2 -4
- data/test/mirror_controller.rb +16 -0
- data/test/test_configuration.rb +38 -0
- data/test/test_data.rb +207 -0
- data/test/test_expr.rb +35 -0
- data/test/test_expr_and.rb +24 -0
- data/test/test_expr_between.rb +43 -0
- data/test/test_expr_eq.rb +24 -0
- data/test/test_expr_gt.rb +24 -0
- data/test/test_expr_gte.rb +24 -0
- data/test/test_expr_has.rb +19 -0
- data/test/test_expr_in.rb +24 -0
- data/test/test_expr_lt.rb +24 -0
- data/test/test_expr_lte.rb +24 -0
- data/test/test_expr_not.rb +22 -0
- data/test/test_expr_or.rb +24 -0
- data/test/test_expr_regex.rb +30 -0
- data/test/test_impl.rb +38 -0
- data/test/test_io_shell.rb +189 -0
- data/test/test_model.rb +31 -0
- data/test/test_runner.rb +177 -0
- data/test/tests.rb +3 -8
- metadata +91 -18
- data/lib/wab/model.rb +0 -136
- data/lib/wab/view.rb +0 -21
- data/test/data_test.rb +0 -253
- data/test/ioshell_test.rb +0 -461
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7d968658f0ee232237f5817e4edd4b79484564e
|
4
|
+
data.tar.gz: cb9cf59b684c7fdc5cdc2e3df9b858d1cf351ca9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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?
|
22
|
-
|
23
|
-
|
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
|
28
|
-
of this project is to provide a high performance, easy to use,
|
29
|
-
featured web framework with Ruby at the core. By keeping the core,
|
30
|
-
business logic, in Ruby but allowing options for other parts to be in
|
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
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
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
|
data/lib/wab/controller.rb
CHANGED
@@ -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.
|
6
|
-
#
|
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
|
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(
|
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
|
48
|
-
where =
|
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
|
-
#
|
63
|
-
#
|
64
|
-
#
|
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
|
68
|
-
#
|
69
|
-
#
|
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
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
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
|
108
|
-
where =
|
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
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
-
#
|
207
|
-
|
208
|
-
|
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
|
-
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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(
|
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(
|
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
|
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(
|
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(
|
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
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
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
|