ruva 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +143 -0
- data/Rakefile +2 -0
- data/lib/ruva.rb +73 -0
- data/lib/ruva/expression.rb +17 -0
- data/lib/ruva/expression/equal_greater_less.rb +61 -0
- data/lib/ruva/expression/expression_leaf_spec.rb +52 -0
- data/lib/ruva/expression/identifier_comparable.rb +53 -0
- data/lib/ruva/expression/is_between.rb +49 -0
- data/lib/ruva/expression/matches.rb +42 -0
- data/lib/ruva/expression/ruva_expression.citrus +112 -0
- data/lib/ruva/specification.rb +1 -0
- data/lib/ruva/specification/specification.rb +100 -0
- data/lib/ruva/version.rb +3 -0
- data/ruva.gemspec +23 -0
- data/spec/conditions/simple_condition.ruva +7 -0
- data/spec/expression_spec.rb +85 -0
- data/spec/helpers/expression_helper.rb +7 -0
- data/spec/inline_ruva_spec.rb +73 -0
- data/spec/normalize_indentation_spec.rb +22 -0
- data/spec/ruva_spec.rb +20 -0
- metadata +123 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Samuel Mueller
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# Ruva
|
2
|
+
|
3
|
+
[![Build Status](https://secure.travis-ci.org/ssmm/ruva.png)](http://travis-ci.org/ssmm/ruva)
|
4
|
+
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/ssmm/ruva)
|
5
|
+
|
6
|
+
Ruva is a simple utility to write conditions in a human readable manner.
|
7
|
+
|
8
|
+
Assume you have an object called `person` with an attribute `age = 23`:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
person = OpenStruct.new
|
12
|
+
person.age = 23
|
13
|
+
```
|
14
|
+
|
15
|
+
You write
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
person.if "age is 23" do
|
19
|
+
stuff
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
You can also define more complex conditions:
|
24
|
+
|
25
|
+
any
|
26
|
+
name matches samuel
|
27
|
+
all
|
28
|
+
city matches zurich
|
29
|
+
profession matches /application engineer/
|
30
|
+
age is greater than or equal to 18
|
31
|
+
|
32
|
+
In plain ruby, the above condition translates to
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
(person.name =~ /samuel/) ||
|
36
|
+
(person.city =~ /zurich/ && person.profession =~ /application engineer/ && person.age >= 18)
|
37
|
+
```
|
38
|
+
|
39
|
+
# Details
|
40
|
+
|
41
|
+
## Inline definition
|
42
|
+
|
43
|
+
### One liners
|
44
|
+
|
45
|
+
As in the example above you can define a short condition on one line:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
person.if "name matches daniel or jonas" do
|
49
|
+
stuff
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
You can also define an else statement:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
person.if("name matches daniel or jonas") do
|
57
|
+
stuff
|
58
|
+
end.else do
|
59
|
+
other_stuff
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
Note that you have to put the condition into brackets in this case.
|
64
|
+
|
65
|
+
### Multiline
|
66
|
+
|
67
|
+
You may define larger conditions like this:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
person.if "
|
71
|
+
any
|
72
|
+
name matches lisa or simon or samuel
|
73
|
+
age is between 18 and 28
|
74
|
+
city matches zurich or /st. gallen/
|
75
|
+
" do
|
76
|
+
stuff
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
The above definition says, that the condition is met if any of the three conditions defined under the
|
81
|
+
`any` keyword meet.
|
82
|
+
|
83
|
+
Currently supported are the keywords `all`, `any` and `none`.
|
84
|
+
|
85
|
+
## Separate definition
|
86
|
+
|
87
|
+
You can define the same condition in a file with ".ruva" as extension:
|
88
|
+
|
89
|
+
any
|
90
|
+
name matches lisa or simon or samuel
|
91
|
+
age is between 18 and 28
|
92
|
+
city matches zurich or /st. gallen/
|
93
|
+
|
94
|
+
Then you load it:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
condition = Ruva.read "your_condition"
|
98
|
+
```
|
99
|
+
|
100
|
+
At this point, ruva loads the condition into some kind of hirarchy, according to the
|
101
|
+
[Specification pattern](http://en.wikipedia.org/wiki/Specification_pattern), and you can invoke its
|
102
|
+
`evaluate` method to test any object you want against that condition:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
result = condition.evaluate(your_input)
|
106
|
+
```
|
107
|
+
|
108
|
+
## Expressions
|
109
|
+
|
110
|
+
#### is
|
111
|
+
|
112
|
+
age is 20
|
113
|
+
|
114
|
+
Acts the same as the `==` operator.
|
115
|
+
|
116
|
+
#### is between
|
117
|
+
|
118
|
+
age is between 20 and 30
|
119
|
+
|
120
|
+
Acts the same as [`between?`](http://www.ruby-doc.org/core-1.9.3/Comparable.html#method-i-between-3F).
|
121
|
+
|
122
|
+
#### is greater than (or equal to) / is less than (or equal to)
|
123
|
+
|
124
|
+
age is greater than 18
|
125
|
+
age is less than 18
|
126
|
+
age is greater than or equal to 18
|
127
|
+
age is less than or equal to 18
|
128
|
+
|
129
|
+
Acts the same as the `<, >, <=, >=` operators
|
130
|
+
|
131
|
+
#### matches
|
132
|
+
|
133
|
+
city matches zurich
|
134
|
+
city matches /new york/
|
135
|
+
|
136
|
+
Acts the same as the `=~` operator.
|
137
|
+
|
138
|
+
#### or / and
|
139
|
+
|
140
|
+
You can use `ors` and `ands` where it makes sense. For Example:
|
141
|
+
|
142
|
+
name matches lisa or simon or samuel
|
143
|
+
city matches z and u and r
|
data/Rakefile
ADDED
data/lib/ruva.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'indentation-parser'
|
2
|
+
|
3
|
+
require "ruva/version"
|
4
|
+
require "ruva/expression"
|
5
|
+
|
6
|
+
class Object
|
7
|
+
def if condition
|
8
|
+
raise unless block_given?
|
9
|
+
spec = Ruva.interpret(Ruva.normalize_indentation condition)
|
10
|
+
result = spec.evaluate self
|
11
|
+
yield if result.satisfied
|
12
|
+
Ruva::Else.new result.satisfied
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Ruva
|
17
|
+
|
18
|
+
def self.read file
|
19
|
+
interpret IO.read("#{file}.ruva")
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.interpret text
|
23
|
+
|
24
|
+
parser = IndentationParser.new do |p|
|
25
|
+
p.on /^all$/ do |parent, source, captures|
|
26
|
+
node = AndSpec.new
|
27
|
+
parent.append node
|
28
|
+
node
|
29
|
+
end
|
30
|
+
|
31
|
+
p.on /^any$/ do |parent, source, captures|
|
32
|
+
node = OrSpec.new
|
33
|
+
parent.append node
|
34
|
+
node
|
35
|
+
end
|
36
|
+
|
37
|
+
p.on /^none$/ do |parent, source, captures|
|
38
|
+
node = NotSpec.new
|
39
|
+
parent.append node
|
40
|
+
node
|
41
|
+
end
|
42
|
+
|
43
|
+
p.else do |parent, source|
|
44
|
+
node = RuvaExpression.parse(source)
|
45
|
+
parent.append node
|
46
|
+
node
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
spec = parser.read(text, AndSpec.new)
|
51
|
+
spec.value
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.normalize_indentation str
|
55
|
+
if str.index(/^[ ]*$\n/) == 0
|
56
|
+
str.sub!(/^[ ]*$\n/, "")
|
57
|
+
end
|
58
|
+
captures = /^\A([ ]*)/.match str
|
59
|
+
first_indentation = captures[1]
|
60
|
+
str.gsub!(/^#{first_indentation}/, "")
|
61
|
+
str
|
62
|
+
end
|
63
|
+
|
64
|
+
class Else
|
65
|
+
def initialize satisfied
|
66
|
+
@satisfied = satisfied
|
67
|
+
end
|
68
|
+
def else
|
69
|
+
raise unless block_given?
|
70
|
+
yield unless @satisfied
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'citrus'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'ruva/specification'
|
4
|
+
require 'ruva/expression/expression_leaf_spec'
|
5
|
+
require 'ruva/expression/equal_greater_less'
|
6
|
+
require 'ruva/expression/identifier_comparable'
|
7
|
+
require 'ruva/expression/is_between'
|
8
|
+
require 'ruva/expression/matches'
|
9
|
+
|
10
|
+
Citrus.require 'ruva/expression/ruva_expression'
|
11
|
+
|
12
|
+
module RuvaExpression
|
13
|
+
def self.parse expression
|
14
|
+
result = super expression
|
15
|
+
result.value
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class GreaterSpec < ExpressionLeafSpec
|
2
|
+
def compare (comparable, value) value > comparable end
|
3
|
+
def satisfied_text() "greater than" end
|
4
|
+
def unsatisfied_text() "less than" end
|
5
|
+
end
|
6
|
+
|
7
|
+
class LessSpec < ExpressionLeafSpec
|
8
|
+
def compare (comparable, value) value < comparable end
|
9
|
+
def satisfied_text() "less than" end
|
10
|
+
def unsatisfied_text() "greater than" end
|
11
|
+
end
|
12
|
+
|
13
|
+
class GreaterEqualSpec < ExpressionLeafSpec
|
14
|
+
def compare (comparable, value) value >= comparable end
|
15
|
+
def satisfied_text() "greater than or equal to" end
|
16
|
+
def unsatisfied_text() "less than" end
|
17
|
+
end
|
18
|
+
|
19
|
+
class LessEqualSpec < ExpressionLeafSpec
|
20
|
+
def compare (comparable, value) value <= comparable end
|
21
|
+
def satisfied_text() "less than or equal to" end
|
22
|
+
def unsatisfied_text() "greater than" end
|
23
|
+
end
|
24
|
+
|
25
|
+
module EqualGreaterLess
|
26
|
+
def value
|
27
|
+
spec = create_spec
|
28
|
+
spec.set_validator { |value, *args|
|
29
|
+
compare @comparable, value
|
30
|
+
}
|
31
|
+
spec.set_reporting { |satisfied, value, *args|
|
32
|
+
comparable_string = @comparable.to_s
|
33
|
+
create_report_string "is #{satisfied_text} #{comparable_string}", "is #{unsatisfied_text} #{comparable_string}", satisfied, value
|
34
|
+
}
|
35
|
+
spec
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module Greater include EqualGreaterLess
|
40
|
+
def create_spec
|
41
|
+
GreaterSpec.new(identifier.value, comparable.value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module Less include EqualGreaterLess
|
46
|
+
def create_spec
|
47
|
+
LessSpec.new(identifier.value, comparable.value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module GreaterOrEqual include EqualGreaterLess
|
52
|
+
def create_spec
|
53
|
+
GreaterEqualSpec.new(identifier.value, comparable.value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module LessOrEqual include EqualGreaterLess
|
58
|
+
def create_spec
|
59
|
+
LessEqualSpec.new(identifier.value, comparable.value)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class ExpressionLeafSpec < LeafSpec
|
2
|
+
def get_value *args
|
3
|
+
if (args.size == 1)
|
4
|
+
obj = args[0]
|
5
|
+
if (obj.is_a? Hash)
|
6
|
+
get_hash_value @name.split(".").reverse, obj
|
7
|
+
else
|
8
|
+
get_object_value @name.split(".").reverse, obj
|
9
|
+
end
|
10
|
+
else
|
11
|
+
raise "Unexpected input"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_hash_value key_array, hash
|
16
|
+
if (key_array.size > 1)
|
17
|
+
obj = hash[key_array.pop.to_sym]
|
18
|
+
if (obj.is_a?(Hash))
|
19
|
+
get_hash_value(key_array, obj)
|
20
|
+
else
|
21
|
+
get_object_value(key_array, obj)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
hash[key_array.pop.to_sym]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_object_value key_array, obj
|
29
|
+
if (key_array.size > 1)
|
30
|
+
obj = obj.send key_array.pop
|
31
|
+
if (obj.is_a?(Hash))
|
32
|
+
get_hash_value(key_array, obj)
|
33
|
+
else
|
34
|
+
get_object_value(key_array, obj)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
obj.send key_array.pop
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_typed_value value
|
42
|
+
case value
|
43
|
+
when Numeric then value
|
44
|
+
when String then "'#{value}'"
|
45
|
+
else value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_report_string positive, negative, satisfied, value
|
50
|
+
@name.to_s + " #{create_typed_value value} " + (satisfied ? positive : negative)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Identifier
|
2
|
+
def value
|
3
|
+
to_s.strip
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module Comparable
|
8
|
+
def value
|
9
|
+
to_s
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module LastComparable
|
14
|
+
def value
|
15
|
+
Array.new << comparable
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module IntComparable
|
20
|
+
def value
|
21
|
+
to_i
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module FloatComparable
|
26
|
+
def value
|
27
|
+
to_f
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module DateComparable
|
32
|
+
def value
|
33
|
+
Date.parse to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module RegexComparable
|
38
|
+
def value
|
39
|
+
Regexp.new regex.value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module TextComparable
|
44
|
+
def value
|
45
|
+
return to_s.strip
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module Comparables
|
50
|
+
def value
|
51
|
+
subresult.value << comparable
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
def create_is_spec identifier, comparable
|
2
|
+
spec = ExpressionLeafSpec.new(identifier.value, comparable.value)
|
3
|
+
spec.set_validator { |value, *args|
|
4
|
+
@comparable == value
|
5
|
+
}
|
6
|
+
spec.set_reporting { |satisfied, value, *args|
|
7
|
+
comparable_string = @comparable.to_s
|
8
|
+
create_report_string "is #{comparable_string}", "is not #{comparable_string}", satisfied, value
|
9
|
+
}
|
10
|
+
spec
|
11
|
+
end
|
12
|
+
|
13
|
+
module Is
|
14
|
+
def value
|
15
|
+
create_is_spec identifier, comparable
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ManyIs
|
20
|
+
def value
|
21
|
+
spec = create_spec
|
22
|
+
comparables.value.reverse.each do |comparable|
|
23
|
+
new_spec = create_is_spec identifier, comparable
|
24
|
+
spec.append(new_spec)
|
25
|
+
end
|
26
|
+
spec
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module OrIs include ManyIs
|
31
|
+
def create_spec
|
32
|
+
OrSpec.new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module IsBetween
|
37
|
+
def value
|
38
|
+
comparables = [from.value, to.value]
|
39
|
+
spec = ExpressionLeafSpec.new(identifier.value, comparables)
|
40
|
+
spec.set_validator { |value, *args|
|
41
|
+
value.between? @comparable[0], @comparable[1]
|
42
|
+
}
|
43
|
+
spec.set_reporting { |satisfied, value, *args|
|
44
|
+
comparable_string = "#{@comparable[0]} and #{@comparable[1]}"
|
45
|
+
create_report_string "is between #{comparable_string}", "is not between #{comparable_string}", satisfied, value
|
46
|
+
}
|
47
|
+
spec
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
def create_matches_spec identifier, comparable
|
2
|
+
comparable = comparable.value
|
3
|
+
regex = comparable.is_a?(Regexp) ? comparable : Regexp.new(comparable)
|
4
|
+
spec = ExpressionLeafSpec.new(identifier.value, regex)
|
5
|
+
spec.set_validator { |value, *args|
|
6
|
+
(@comparable =~ value) != nil
|
7
|
+
}
|
8
|
+
spec.set_reporting { |satisfied, value, *args|
|
9
|
+
comparable_string = "/#{@comparable.source}/"
|
10
|
+
create_report_string "matches #{comparable_string}", "does not match #{comparable_string}", satisfied, value
|
11
|
+
}
|
12
|
+
spec
|
13
|
+
end
|
14
|
+
|
15
|
+
module Matches
|
16
|
+
def value
|
17
|
+
create_matches_spec identifier, comparable
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ManyMatches
|
22
|
+
def value
|
23
|
+
spec = create_spec
|
24
|
+
comparables.value.reverse.each do |comparable|
|
25
|
+
new_spec = create_matches_spec identifier, comparable
|
26
|
+
spec.append(new_spec)
|
27
|
+
end
|
28
|
+
spec
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module AndMatches include ManyMatches
|
33
|
+
def create_spec
|
34
|
+
AndSpec.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module OrMatches include ManyMatches
|
39
|
+
def create_spec
|
40
|
+
OrSpec.new
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
grammar RuvaExpression
|
2
|
+
|
3
|
+
rule root
|
4
|
+
(identifier matches comparables:(and_comparables)) <AndMatches>
|
5
|
+
|
|
6
|
+
(identifier matches comparables:(or_comparables)) <OrMatches>
|
7
|
+
|
|
8
|
+
(identifier matches comparable) <Matches>
|
9
|
+
|
|
10
|
+
(identifier is between from:(comparable) and to:(comparable)) <IsBetween>
|
11
|
+
|
|
12
|
+
(identifier is greater than or equal to comparable) <GreaterOrEqual>
|
13
|
+
|
|
14
|
+
(identifier is less than or equal to comparable) <LessOrEqual>
|
15
|
+
|
|
16
|
+
(identifier is greater than comparable) <Greater>
|
17
|
+
|
|
18
|
+
(identifier is less than comparable) <Less>
|
19
|
+
|
|
20
|
+
(identifier is comparables:(or_comparables)) <OrIs>
|
21
|
+
|
|
22
|
+
(identifier is comparable) <Is>
|
23
|
+
end
|
24
|
+
|
25
|
+
rule matches
|
26
|
+
'matches' space
|
27
|
+
end
|
28
|
+
|
29
|
+
rule is
|
30
|
+
'is' space
|
31
|
+
end
|
32
|
+
|
33
|
+
rule between
|
34
|
+
'between' space
|
35
|
+
end
|
36
|
+
|
37
|
+
rule greater
|
38
|
+
'greater' space
|
39
|
+
end
|
40
|
+
|
41
|
+
rule less
|
42
|
+
'less' space
|
43
|
+
end
|
44
|
+
|
45
|
+
rule than
|
46
|
+
'than' space
|
47
|
+
end
|
48
|
+
|
49
|
+
rule equal
|
50
|
+
'equal' space
|
51
|
+
end
|
52
|
+
|
53
|
+
rule to
|
54
|
+
'to' space
|
55
|
+
end
|
56
|
+
|
57
|
+
rule and
|
58
|
+
'and' space
|
59
|
+
end
|
60
|
+
|
61
|
+
rule or
|
62
|
+
'or' space
|
63
|
+
end
|
64
|
+
|
65
|
+
rule identifier
|
66
|
+
([a-zA-Z.]+ space) <Identifier>
|
67
|
+
end
|
68
|
+
|
69
|
+
rule comparable
|
70
|
+
(number_comparable | date_comparable | regex_comparable | text_comparable)
|
71
|
+
end
|
72
|
+
|
73
|
+
rule last_comparable
|
74
|
+
(comparable '') <LastComparable>
|
75
|
+
end
|
76
|
+
|
77
|
+
rule number_comparable
|
78
|
+
(int_comparable | float_comparable)
|
79
|
+
end
|
80
|
+
|
81
|
+
rule int_comparable
|
82
|
+
([0-9]+ space) <IntComparable>
|
83
|
+
end
|
84
|
+
|
85
|
+
rule float_comparable
|
86
|
+
([0-9]* '.' [0-9]+ space) <FloatComparable>
|
87
|
+
end
|
88
|
+
|
89
|
+
rule date_comparable
|
90
|
+
([0-9]2*2 '.' [0-9]2*2 '.' [0-9]4*4 space) <DateComparable>
|
91
|
+
end
|
92
|
+
|
93
|
+
rule regex_comparable
|
94
|
+
('/' regex:((!'/'!'\\' .) | '\\/' | '\\\\')* '/' space) <RegexComparable>
|
95
|
+
end
|
96
|
+
|
97
|
+
rule text_comparable
|
98
|
+
([^ ]+ space) <TextComparable>
|
99
|
+
end
|
100
|
+
|
101
|
+
rule and_comparables
|
102
|
+
(comparable and subresult:(and_comparables | last_comparable)) <Comparables>
|
103
|
+
end
|
104
|
+
|
105
|
+
rule or_comparables
|
106
|
+
(comparable or subresult:(or_comparables | last_comparable)) <Comparables>
|
107
|
+
end
|
108
|
+
|
109
|
+
rule space
|
110
|
+
[ \t]+ | /$/
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'ruva/specification/specification'
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class Spec
|
2
|
+
def initialize
|
3
|
+
@specs = Array.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def append spec
|
7
|
+
@specs << spec
|
8
|
+
spec
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_subreport spec, *args
|
12
|
+
if spec.is_a? Spec
|
13
|
+
spec.evaluate *args
|
14
|
+
else
|
15
|
+
result = spec.evaluate *args
|
16
|
+
message = spec.report result, *args
|
17
|
+
SpecReport.new result, message
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def evaluate *args
|
22
|
+
passed = initial_condition
|
23
|
+
report = SpecReport.new false, name
|
24
|
+
@specs.each do |spec|
|
25
|
+
subreport = create_subreport spec, *args
|
26
|
+
report.add_subreport subreport
|
27
|
+
if condition_change? subreport.satisfied
|
28
|
+
passed = !initial_condition
|
29
|
+
end
|
30
|
+
end
|
31
|
+
report.satisfied = passed
|
32
|
+
report
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class AndSpec < Spec
|
37
|
+
def initial_condition() return true end
|
38
|
+
def name() "all" end
|
39
|
+
def condition_change?(satisfied) !satisfied end
|
40
|
+
end
|
41
|
+
|
42
|
+
class OrSpec < Spec
|
43
|
+
def initial_condition() return false end
|
44
|
+
def name() "any" end
|
45
|
+
def condition_change?(satisfied) satisfied end
|
46
|
+
end
|
47
|
+
|
48
|
+
class NotSpec < Spec
|
49
|
+
def initial_condition() return true end
|
50
|
+
def name() "none" end
|
51
|
+
def condition_change?(satisfied) satisfied end
|
52
|
+
end
|
53
|
+
|
54
|
+
class LeafSpec
|
55
|
+
def initialize name, args
|
56
|
+
@name = name
|
57
|
+
@comparable = args
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_validator &block
|
61
|
+
@validator = block
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_reporting &block
|
65
|
+
@reporting = block
|
66
|
+
end
|
67
|
+
|
68
|
+
def evaluate *args
|
69
|
+
instance_exec(get_value(*args), *args, &@validator)
|
70
|
+
end
|
71
|
+
|
72
|
+
def report satisfied, *args
|
73
|
+
instance_exec(satisfied, get_value(*args), *args, &@reporting)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class SpecReport
|
78
|
+
|
79
|
+
attr_accessor :satisfied, :name, :input
|
80
|
+
|
81
|
+
def initialize satisfied, message
|
82
|
+
@satisfied = satisfied
|
83
|
+
@message = message
|
84
|
+
@subreports = Array.new
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_subreport subreport
|
88
|
+
@subreports << subreport
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s s = ''
|
92
|
+
met = @satisfied ? "[+] " : "[-] "
|
93
|
+
output = s + met + @message
|
94
|
+
output += "\n"
|
95
|
+
@subreports.each do |subreport|
|
96
|
+
output += subreport.to_s(s + ' ')
|
97
|
+
end
|
98
|
+
output
|
99
|
+
end
|
100
|
+
end
|
data/lib/ruva/version.rb
ADDED
data/ruva.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/ruva/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "ruva"
|
6
|
+
gem.version = Ruva::VERSION
|
7
|
+
|
8
|
+
gem.authors = ["Samuel Mueller"]
|
9
|
+
gem.email = ["mueller.samu@gmail.com"]
|
10
|
+
gem.description = %q{Ruva is a simple utility to write conditions in a human readable manner.}
|
11
|
+
gem.summary = gem.description
|
12
|
+
gem.homepage = "https://github.com/ssmm/ruva"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($\)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.add_dependency "citrus", "~> 2.4.1"
|
20
|
+
gem.add_dependency "indentation-parser"
|
21
|
+
|
22
|
+
gem.add_development_dependency "rspec", "~> 2.6"
|
23
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'ruva/expression'
|
2
|
+
require 'helpers/expression_helper'
|
3
|
+
|
4
|
+
RSpec.configure do |c|
|
5
|
+
c.include ExpressionHelper
|
6
|
+
end
|
7
|
+
|
8
|
+
describe Ruva do
|
9
|
+
|
10
|
+
before :all do
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "greater/less/equal expressions" do
|
15
|
+
it "handles numerics" do
|
16
|
+
test_expression "age is 23", {:age => 23}, true
|
17
|
+
test_expression "age is 23", {:age => 24}, false
|
18
|
+
|
19
|
+
test_expression "age is greater than 23", {:age => 24}, true
|
20
|
+
test_expression "age is greater than 23", {:age => 23}, false
|
21
|
+
|
22
|
+
test_expression "age is less than 23", {:age => 22}, true
|
23
|
+
test_expression "age is less than 23", {:age => 23}, false
|
24
|
+
|
25
|
+
test_expression "age is greater than or equal to 23", {:age => 23}, true
|
26
|
+
test_expression "age is greater than or equal to 23", {:age => 22}, false
|
27
|
+
|
28
|
+
test_expression "age is less than or equal to 23", {:age => 23}, true
|
29
|
+
test_expression "age is less than or equal to 23", {:age => 24}, false
|
30
|
+
end
|
31
|
+
|
32
|
+
it "handles strings" do
|
33
|
+
test_expression "name is samuel", {:name => "samuel"}, true
|
34
|
+
test_expression "name is samuel", {:name => "melchior"}, false
|
35
|
+
|
36
|
+
test_expression "name is greater than samuel", {:name => "simon"}, true
|
37
|
+
test_expression "name is greater than samuel", {:name => "samantha"}, false
|
38
|
+
|
39
|
+
test_expression "name is less than samuel", {:name => "lisa"}, true
|
40
|
+
test_expression "name is less than samuel", {:name => "samuel l jackson"}, false
|
41
|
+
|
42
|
+
test_expression "name is greater than or equal to samuel", {:name => "samuel"}, true
|
43
|
+
test_expression "name is greater than or equal to samuel", {:name => "samantha"}, false
|
44
|
+
|
45
|
+
test_expression "name is less than or equal to samuel", {:name => "samuel"}, true
|
46
|
+
test_expression "name is less than or equal to samuel", {:name => "samuel l jackson"}, false
|
47
|
+
end
|
48
|
+
|
49
|
+
it "handes dates" do
|
50
|
+
#TODO!!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "between expressions" do
|
55
|
+
it "handles numerics" do
|
56
|
+
test_expression "age is between 18 and 28", {:age => 23}, true
|
57
|
+
|
58
|
+
test_expression "age is between 18 and 28", {:age => 28}, true
|
59
|
+
test_expression "age is between 18 and 28", {:age => 18}, true
|
60
|
+
|
61
|
+
test_expression "age is between 18 and 28", {:age => 50}, false
|
62
|
+
test_expression "age is between 18 and 28", {:age => 17}, false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "matches expressions" do
|
67
|
+
it "handles strings" do
|
68
|
+
test_expression "name matches sam", {:name => "samuel"}, true
|
69
|
+
test_expression "name matches am", {:name => "samuel"}, true
|
70
|
+
test_expression "name matches uel", {:name => "samuel"}, true
|
71
|
+
|
72
|
+
test_expression "name matches xyz", {:name => "samuel"}, false
|
73
|
+
|
74
|
+
test_expression "name matches /^samuel$/", {:name => "samuel"}, true
|
75
|
+
test_expression "name matches /^samuel$/", {:name => "samuel "}, false
|
76
|
+
|
77
|
+
test_expression "name matches /daniel|jonas/", {:name => "daniel"}, true
|
78
|
+
test_expression "name matches /daniel|jonas/", {:name => "jonas"}, true
|
79
|
+
|
80
|
+
test_expression 'name matches /regex\/with escaping/', {:name => "regex/with escaping"}, true
|
81
|
+
test_expression "name matches /regex\\/with escaping/", {:name => "regex/with escaping"}, true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'ruva'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe Ruva do
|
5
|
+
describe "the 'if' syntax" do
|
6
|
+
|
7
|
+
before :all do
|
8
|
+
@person = OpenStruct.new
|
9
|
+
@person.age = 23
|
10
|
+
@person.city = "zurich"
|
11
|
+
@person.name = "samuel"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "handles one liners" do
|
15
|
+
#person.if "age is 23 or higher" <-- TODO: support this
|
16
|
+
executed = false
|
17
|
+
@person.if "age is 23" do
|
18
|
+
executed = true
|
19
|
+
end
|
20
|
+
executed.should be true
|
21
|
+
|
22
|
+
executed = false
|
23
|
+
@person.if("age is 23") {
|
24
|
+
executed = true
|
25
|
+
}.else {
|
26
|
+
true.should be false
|
27
|
+
}
|
28
|
+
executed.should be true
|
29
|
+
|
30
|
+
executed = false
|
31
|
+
@person.if("age is 23") do
|
32
|
+
executed = true
|
33
|
+
end.else do
|
34
|
+
true.should be false
|
35
|
+
end
|
36
|
+
executed.should be true
|
37
|
+
|
38
|
+
executed = true
|
39
|
+
@person.if("age is 24") {
|
40
|
+
true.should be false
|
41
|
+
}.else {
|
42
|
+
executed = false
|
43
|
+
}
|
44
|
+
executed.should be false
|
45
|
+
end
|
46
|
+
|
47
|
+
it "handles multi line conditions" do
|
48
|
+
|
49
|
+
executed = false
|
50
|
+
@person.if "
|
51
|
+
age is 23
|
52
|
+
city is zurich
|
53
|
+
name is samuel
|
54
|
+
" do
|
55
|
+
executed = true
|
56
|
+
end
|
57
|
+
executed.should be true
|
58
|
+
|
59
|
+
executed = true
|
60
|
+
@person.if("
|
61
|
+
age is 24
|
62
|
+
city is zurich
|
63
|
+
name is samuel
|
64
|
+
") {
|
65
|
+
true.should be false
|
66
|
+
}.else {
|
67
|
+
executed = false
|
68
|
+
}
|
69
|
+
executed.should be false
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'ruva'
|
2
|
+
|
3
|
+
describe Ruva do
|
4
|
+
it "normalizes indentation" do
|
5
|
+
indented_text = "
|
6
|
+
this
|
7
|
+
text
|
8
|
+
is
|
9
|
+
indented
|
10
|
+
"
|
11
|
+
indented_text = Ruva.normalize_indentation indented_text
|
12
|
+
|
13
|
+
expected =
|
14
|
+
"this
|
15
|
+
text
|
16
|
+
is
|
17
|
+
indented
|
18
|
+
"
|
19
|
+
|
20
|
+
indented_text.should eq expected
|
21
|
+
end
|
22
|
+
end
|
data/spec/ruva_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'ruva'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe Ruva do
|
5
|
+
it "reads .ruva files" do
|
6
|
+
|
7
|
+
spec = Ruva.read "spec/conditions/simple_condition"
|
8
|
+
|
9
|
+
input = OpenStruct.new
|
10
|
+
input.age = 23
|
11
|
+
input.name = "samuel"
|
12
|
+
input.profession = "application engineer"
|
13
|
+
input.city = "zurich"
|
14
|
+
|
15
|
+
result = spec.evaluate(input)
|
16
|
+
|
17
|
+
result.satisfied.should be true
|
18
|
+
#puts result
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruva
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Samuel Mueller
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: citrus
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.4.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.4.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: indentation-parser
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.6'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.6'
|
62
|
+
description: Ruva is a simple utility to write conditions in a human readable manner.
|
63
|
+
email:
|
64
|
+
- mueller.samu@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .travis.yml
|
71
|
+
- Gemfile
|
72
|
+
- LICENSE
|
73
|
+
- README.md
|
74
|
+
- Rakefile
|
75
|
+
- lib/ruva.rb
|
76
|
+
- lib/ruva/expression.rb
|
77
|
+
- lib/ruva/expression/equal_greater_less.rb
|
78
|
+
- lib/ruva/expression/expression_leaf_spec.rb
|
79
|
+
- lib/ruva/expression/identifier_comparable.rb
|
80
|
+
- lib/ruva/expression/is_between.rb
|
81
|
+
- lib/ruva/expression/matches.rb
|
82
|
+
- lib/ruva/expression/ruva_expression.citrus
|
83
|
+
- lib/ruva/specification.rb
|
84
|
+
- lib/ruva/specification/specification.rb
|
85
|
+
- lib/ruva/version.rb
|
86
|
+
- ruva.gemspec
|
87
|
+
- spec/conditions/simple_condition.ruva
|
88
|
+
- spec/expression_spec.rb
|
89
|
+
- spec/helpers/expression_helper.rb
|
90
|
+
- spec/inline_ruva_spec.rb
|
91
|
+
- spec/normalize_indentation_spec.rb
|
92
|
+
- spec/ruva_spec.rb
|
93
|
+
homepage: https://github.com/ssmm/ruva
|
94
|
+
licenses: []
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.8.24
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: Ruva is a simple utility to write conditions in a human readable manner.
|
117
|
+
test_files:
|
118
|
+
- spec/conditions/simple_condition.ruva
|
119
|
+
- spec/expression_spec.rb
|
120
|
+
- spec/helpers/expression_helper.rb
|
121
|
+
- spec/inline_ruva_spec.rb
|
122
|
+
- spec/normalize_indentation_spec.rb
|
123
|
+
- spec/ruva_spec.rb
|