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
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
class ExprParser
|
5
|
+
XMAP = {
|
6
|
+
between: Between,
|
7
|
+
eq: Eq,
|
8
|
+
gt: Gt,
|
9
|
+
gte: Gte,
|
10
|
+
in: In,
|
11
|
+
has: Has,
|
12
|
+
lt: Lt,
|
13
|
+
lte: Lte,
|
14
|
+
regex: Regex,
|
15
|
+
and: And,
|
16
|
+
or: Or,
|
17
|
+
not: Not,
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
class << self
|
21
|
+
# Parses an Array into a set of Expr subsclasses.
|
22
|
+
#
|
23
|
+
# native:: native Ruby representation of the expression.
|
24
|
+
def parse(native)
|
25
|
+
raise WAB::TypeError.new('Invalid expression. Must be an Array.') unless native.is_a?(Array)
|
26
|
+
|
27
|
+
op = native[0]
|
28
|
+
op = op.downcase.to_sym unless op.is_a?(Symbol)
|
29
|
+
|
30
|
+
xclass = XMAP[op]
|
31
|
+
raise WAB::Error.new("#{op} is not a valid expression function.") if xclass.nil?
|
32
|
+
|
33
|
+
args = []
|
34
|
+
native[1..-1].each { |n|
|
35
|
+
args << if n.is_a?(Array)
|
36
|
+
parse(n)
|
37
|
+
elsif n.is_a?(String)
|
38
|
+
unless n.empty?
|
39
|
+
if "'" == n[0]
|
40
|
+
n[1..-1]
|
41
|
+
else
|
42
|
+
WAB::Impl::Data.detect_string(n)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
else
|
46
|
+
n
|
47
|
+
end
|
48
|
+
}
|
49
|
+
xclass.new(*args)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end # ExprParser
|
54
|
+
end # Impl
|
55
|
+
end # WAB
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# A logical AND expression.
|
6
|
+
class And < BoolExpr
|
7
|
+
|
8
|
+
# Create an AND expression with the provided arguments which must be
|
9
|
+
# instances of subclasses of the Expr class.
|
10
|
+
#
|
11
|
+
# args:: argument to the AND expression
|
12
|
+
def initialize(*args)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def eval(data)
|
17
|
+
@args.each { |a|
|
18
|
+
return false unless a.eval(data)
|
19
|
+
}
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def native()
|
24
|
+
@args.map(&:native).unshift('AND')
|
25
|
+
end
|
26
|
+
|
27
|
+
end # And
|
28
|
+
end # Impl
|
29
|
+
end # WAB
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# Matches numeric nodes between a minimum and maximum value. By default
|
6
|
+
# the matches are inclusive of the minimum and maximum. The optional
|
7
|
+
# arguments allow the inclusivity to be changed to exclusive separately
|
8
|
+
# for the minimum and maximum. By default the min_incl and max_incl are
|
9
|
+
# true. If set to false the corresponding limit becomes exclusive. An
|
10
|
+
# error is returned if used with non-numeric minimum or maximum.
|
11
|
+
class Between < PathExpr
|
12
|
+
|
13
|
+
# Creates a new instance with the provided parameters.
|
14
|
+
#
|
15
|
+
# path:: path to the value to compare
|
16
|
+
# min:: minimum
|
17
|
+
# max:: maximum
|
18
|
+
# min_incl:: minimum inclusive flag
|
19
|
+
# max_incl:: maximum inclusive flag
|
20
|
+
def initialize(path, min, max, min_incl=true, max_incl=true)
|
21
|
+
super(path)
|
22
|
+
@min = min
|
23
|
+
@max = max
|
24
|
+
@min_incl = min_incl
|
25
|
+
@max_incl = max_incl
|
26
|
+
end
|
27
|
+
|
28
|
+
def eval(data)
|
29
|
+
value = data.get(@path)
|
30
|
+
return false if (@min_incl ? value < @min : value <= @min)
|
31
|
+
return false if (@max_incl ? @max < value : @max <= value)
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def native()
|
36
|
+
['BETWEEN', @path, @min, @max, @min_incl, @max_incl]
|
37
|
+
end
|
38
|
+
|
39
|
+
end # Between
|
40
|
+
end # Impl
|
41
|
+
end # WAB
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# Matches a node that has the same value as provided at the end of the
|
6
|
+
# path provided. Any type is acceptable.#
|
7
|
+
class Eq < PathExpr
|
8
|
+
|
9
|
+
# Creates a new instance with the provided parameters.
|
10
|
+
#
|
11
|
+
# path:: path to the value to compare
|
12
|
+
# value:: value to compare against
|
13
|
+
def initialize(path, value)
|
14
|
+
super(path)
|
15
|
+
@value = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def eval(data)
|
19
|
+
data.get(@path) == @value
|
20
|
+
end
|
21
|
+
|
22
|
+
def native()
|
23
|
+
['EQ', @path, @value]
|
24
|
+
end
|
25
|
+
|
26
|
+
end # Eq
|
27
|
+
end # Impl
|
28
|
+
end # WAB
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# Matches a node that has a value greater than the provided value. If a
|
6
|
+
# integer or float is provided then both integer and floats are
|
7
|
+
# checked. If the value provided is a time then only time nodes are
|
8
|
+
# checked. Any other type results in an error.
|
9
|
+
class Gt < PathExpr
|
10
|
+
|
11
|
+
# Creates a new instance with the provided parameters.
|
12
|
+
#
|
13
|
+
# path:: path to the value to compare
|
14
|
+
# value:: value to compare against
|
15
|
+
def initialize(path, value)
|
16
|
+
super(path)
|
17
|
+
@value = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def eval(data)
|
21
|
+
data.get(@path) > @value
|
22
|
+
end
|
23
|
+
|
24
|
+
def native()
|
25
|
+
['GT', @path, @value]
|
26
|
+
end
|
27
|
+
|
28
|
+
end # Gt
|
29
|
+
end # Impl
|
30
|
+
end # WAB
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# Matches a node that has a value greater than or equals to the provided
|
6
|
+
# value. If a integer or float is provided then both integer and floats
|
7
|
+
# are checked. If the value provided is a time then only time nodes are
|
8
|
+
# checked. Any other type results in an error.
|
9
|
+
class Gte < PathExpr
|
10
|
+
|
11
|
+
# Creates a new instance with the provided parameters.
|
12
|
+
#
|
13
|
+
# path:: path to the value to compare
|
14
|
+
# value:: value to compare against
|
15
|
+
def initialize(path, value)
|
16
|
+
super(path)
|
17
|
+
@value = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def eval(data)
|
21
|
+
data.get(@path) >= @value
|
22
|
+
end
|
23
|
+
|
24
|
+
def native()
|
25
|
+
['GTE', @path, @value]
|
26
|
+
end
|
27
|
+
|
28
|
+
end # Gte
|
29
|
+
end # Impl
|
30
|
+
end # WAB
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# Used to filters out all nodes that do not have a node at the end of the
|
6
|
+
# provided path.
|
7
|
+
class Has < PathExpr
|
8
|
+
|
9
|
+
# Creates a new instance with the provided parameters.
|
10
|
+
#
|
11
|
+
# path:: path to the value to check
|
12
|
+
def initialize(path)
|
13
|
+
super(path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def eval(data)
|
17
|
+
data.has?(@path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def native()
|
21
|
+
['HAS', @path]
|
22
|
+
end
|
23
|
+
|
24
|
+
end # Has
|
25
|
+
end # Impl
|
26
|
+
end # WAB
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# Match any node that has a value equal to one of the values provided in
|
6
|
+
# the argument list after the path. This can be used with any type.
|
7
|
+
class In < PathExpr
|
8
|
+
|
9
|
+
# Creates a new instance with the provided parameters.
|
10
|
+
#
|
11
|
+
# path:: path to the value to compare
|
12
|
+
# values:: values to compare to
|
13
|
+
def initialize(path, *values)
|
14
|
+
super(path)
|
15
|
+
@values = values
|
16
|
+
end
|
17
|
+
|
18
|
+
def eval(data)
|
19
|
+
@values.include?(data.get(@path))
|
20
|
+
end
|
21
|
+
|
22
|
+
def native()
|
23
|
+
['IN', @path].concat(@values)
|
24
|
+
end
|
25
|
+
|
26
|
+
end # In
|
27
|
+
end # Impl
|
28
|
+
end # WAB
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# Matches a node that has a value less than the provided value. If a
|
6
|
+
# integer or float is provided then both integer and floats are
|
7
|
+
# checked. If the value provided is a time then only time nodes are
|
8
|
+
# checked. Any other type results in an error.
|
9
|
+
class Lt < PathExpr
|
10
|
+
|
11
|
+
# Creates a new instance with the provided parameters.
|
12
|
+
#
|
13
|
+
# path:: path to the value to compare
|
14
|
+
# value:: value to compare against
|
15
|
+
def initialize(path, value)
|
16
|
+
super(path)
|
17
|
+
@value = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def eval(data)
|
21
|
+
data.get(@path) < @value
|
22
|
+
end
|
23
|
+
|
24
|
+
def native()
|
25
|
+
['LT', @path, @value]
|
26
|
+
end
|
27
|
+
|
28
|
+
end # Lt
|
29
|
+
end # Impl
|
30
|
+
end # WAB
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# Matches a node that has a value less than or equal to the provided
|
6
|
+
# value. If a integer or float is provided then both integer and floats
|
7
|
+
# are checked. If the value provided is a time then only time nodes are
|
8
|
+
# checked. Any other type results in an error.
|
9
|
+
class Lte < PathExpr
|
10
|
+
|
11
|
+
# Creates a new instance with the provided parameters.
|
12
|
+
#
|
13
|
+
# path:: path to the value to compare
|
14
|
+
# value:: value to compare against
|
15
|
+
def initialize(path, value)
|
16
|
+
super(path)
|
17
|
+
@value = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def eval(data)
|
21
|
+
data.get(@path) <= @value
|
22
|
+
end
|
23
|
+
|
24
|
+
def native()
|
25
|
+
['LTE', @path, @value]
|
26
|
+
end
|
27
|
+
|
28
|
+
end # Lte
|
29
|
+
end # Impl
|
30
|
+
end # WAB
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# Negates and expression.
|
6
|
+
class Not < Expr
|
7
|
+
|
8
|
+
# Create an NOT expression with the provided argument which must be an
|
9
|
+
# instance of a subclass of the Expr class.
|
10
|
+
#
|
11
|
+
# arg:: argument to the NOT expression
|
12
|
+
def initialize(arg)
|
13
|
+
super()
|
14
|
+
@arg = arg
|
15
|
+
end
|
16
|
+
|
17
|
+
def eval(data)
|
18
|
+
!@arg.eval(data)
|
19
|
+
end
|
20
|
+
|
21
|
+
def native()
|
22
|
+
['NOT', @arg.native]
|
23
|
+
end
|
24
|
+
|
25
|
+
end # Not
|
26
|
+
end # Impl
|
27
|
+
end # WAB
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
# A logical OR expression.
|
6
|
+
class Or < BoolExpr
|
7
|
+
|
8
|
+
# Create an OR expression with the provided arguments which must be
|
9
|
+
# instances of subclasses of the Expr class.
|
10
|
+
#
|
11
|
+
# args:: argument to the OR expression
|
12
|
+
def initialize(*args)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def eval(data)
|
17
|
+
@args.each { |a|
|
18
|
+
return true if a.eval(data)
|
19
|
+
}
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def native()
|
24
|
+
@args.map(&:native).unshift('OR')
|
25
|
+
end
|
26
|
+
|
27
|
+
end # Or
|
28
|
+
end # Impl
|
29
|
+
end # WAB
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
class Regex < PathExpr
|
6
|
+
|
7
|
+
# Creates a new instance with the provided parameters.
|
8
|
+
#
|
9
|
+
# path:: path to the value to compare
|
10
|
+
# rx:: regexp to match against a string value from the path lookup
|
11
|
+
def initialize(path, rx)
|
12
|
+
super(path)
|
13
|
+
@rx = rx.is_a?(Regexp) ? rx : Regexp.new(rx.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def eval(data)
|
17
|
+
value = data.get(@path)
|
18
|
+
return !@rx.match(value).nil? if value.is_a?(String)
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def native()
|
23
|
+
['REGEX', @path, @rx.source]
|
24
|
+
end
|
25
|
+
|
26
|
+
end # Regex
|
27
|
+
end # Impl
|
28
|
+
end # WAB
|
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
require 'webrick'
|
3
|
+
|
4
|
+
module WAB
|
5
|
+
module Impl
|
6
|
+
|
7
|
+
# Handler for requests that fall under the path assigned to the
|
8
|
+
# Controller. This is used only with the WAB::Impl::Shell.
|
9
|
+
class Handler < WEBrick::HTTPServlet::AbstractServlet
|
10
|
+
|
11
|
+
def initialize(server, shell)
|
12
|
+
super(server)
|
13
|
+
@shell = shell
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_GET(req, res)
|
17
|
+
controller, path, query = extract_req(req)
|
18
|
+
log_response('controller.read', path, query) if @shell.logger.info?
|
19
|
+
send_result(controller.read(path, query), res)
|
20
|
+
rescue StandardError => e
|
21
|
+
send_error(e, res)
|
22
|
+
end
|
23
|
+
|
24
|
+
def do_PUT(req, res)
|
25
|
+
controller, path, query, body = extract_req(req)
|
26
|
+
log_response_with_body('controller.create', path, query, body) if @shell.logger.info?
|
27
|
+
send_result(controller.create(path, query, body), res)
|
28
|
+
rescue StandardError => e
|
29
|
+
send_error(e, res)
|
30
|
+
end
|
31
|
+
|
32
|
+
def do_POST(req, res)
|
33
|
+
controller, path, query, body = extract_req(req)
|
34
|
+
log_response_with_body('controller.update', path, query, body) if @shell.logger.info?
|
35
|
+
send_result(controller.update(path, query, body), res)
|
36
|
+
rescue StandardError => e
|
37
|
+
send_error(e, res)
|
38
|
+
end
|
39
|
+
|
40
|
+
def do_DELETE(req, res)
|
41
|
+
controller, path, query = extract_req(req)
|
42
|
+
log_response('controller.delete', path, query) if @shell.logger.info?
|
43
|
+
send_result(controller.delete(path, query), res)
|
44
|
+
rescue StandardError => e
|
45
|
+
send_error(e, res)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def log_response(caller, path, query)
|
51
|
+
@shell.logger.info("#{caller}(#{path.join('/')}#{query})")
|
52
|
+
end
|
53
|
+
|
54
|
+
def log_response_with_body(caller, path, query, body)
|
55
|
+
@shell.logger.info("#{caller}(#{path.join('/')}#{query}, #{body.json})")
|
56
|
+
end
|
57
|
+
|
58
|
+
# Pulls and converts the request path, query, and body. Also returns the
|
59
|
+
# controller.
|
60
|
+
def extract_req(req)
|
61
|
+
path = req.path.split('/')[1..-1]
|
62
|
+
query = {}
|
63
|
+
req.query.each { |k,v| query[k.to_sym] = v }
|
64
|
+
if req.body.nil?
|
65
|
+
body = nil
|
66
|
+
else
|
67
|
+
body = Oj.strict_load(req.body, symbol_keys: true)
|
68
|
+
body = Data.new(body, false)
|
69
|
+
body.detect
|
70
|
+
end
|
71
|
+
[@shell.path_controller(path), path, query, body]
|
72
|
+
end
|
73
|
+
|
74
|
+
# Sends the results from a controller request.
|
75
|
+
def send_result(result, res)
|
76
|
+
result = @shell.data(result) unless result.is_a?(WAB::Data)
|
77
|
+
res.status = 200
|
78
|
+
res['Content-Type'] = 'application/json'
|
79
|
+
@shell.logger.debug("Reply: #{result.json}") if @shell.logger.debug?
|
80
|
+
res.body = result.json
|
81
|
+
end
|
82
|
+
|
83
|
+
# Sends an error from a rescued call.
|
84
|
+
def send_error(e, res)
|
85
|
+
res.status = 500
|
86
|
+
res['Content-Type'] = 'application/json'
|
87
|
+
body = { code: -1, error: "#{e.class}: #{e.message}" }
|
88
|
+
body[:backtrace] = e.backtrace
|
89
|
+
res.body = @shell.data(body).json
|
90
|
+
@shell.logger.warn(%|*-*-* #{e.class}: #{e.message}\n #{e.backtrace.join("\n ")}|)
|
91
|
+
end
|
92
|
+
|
93
|
+
end # Handler
|
94
|
+
end # Impl
|
95
|
+
end # WAB
|