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

|
57
|
+
|
58
|
+
The Ruby Runner and C Runner with embedded ruby follow the same architecture.
|
59
|
+
|
60
|
+

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

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