de 0.0.1
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.
- data/.gitignore +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README +28 -0
- data/Rakefile +1 -0
- data/de.gemspec +26 -0
- data/lib/de/boolean/operand.rb +46 -0
- data/lib/de/boolean/operator.rb +134 -0
- data/lib/de/boolean.rb +2 -0
- data/lib/de/de.rb +210 -0
- data/lib/de/error.rb +6 -0
- data/lib/de/sunspot_solr/operand.rb +273 -0
- data/lib/de/sunspot_solr/operator.rb +131 -0
- data/lib/de/sunspot_solr/search.rb +179 -0
- data/lib/de/sunspot_solr.rb +9 -0
- data/lib/de/symmetric_operator.rb +31 -0
- data/lib/de/version.rb +3 -0
- data/lib/de.rb +3 -0
- data/test/de_boolean_and_test.rb +51 -0
- data/test/de_boolean_not_test.rb +39 -0
- data/test/de_boolean_operand_test.rb +30 -0
- data/test/de_boolean_operator_test.rb +34 -0
- data/test/de_boolean_or_test.rb +51 -0
- data/test/de_expression_test.rb +35 -0
- data/test/de_operand_test.rb +29 -0
- data/test/de_operator_test.rb +48 -0
- data/test/de_sunspot_solr_and_test.rb +113 -0
- data/test/de_sunspot_solr_not_test.rb +49 -0
- data/test/de_sunspot_solr_or_test.rb +61 -0
- data/test/de_sunspot_solr_search_test.rb +251 -0
- data/test/de_sunspot_solr_sunspot_operand_test.rb +509 -0
- metadata +145 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.8.7@de
|
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
De (Dynamic Expression) module provides means to build and evaluate
|
3
|
+
dynamic expression of arbitrary complecity and operands/operators nature.
|
4
|
+
|
5
|
+
Expression is considered as tree consisted of operands and operators.
|
6
|
+
Operater must have child nodes. Operand is terminal node.
|
7
|
+
|
8
|
+
Tree example:
|
9
|
+
(a + b) * c + d
|
10
|
+
|
11
|
+
- +
|
12
|
+
--- *
|
13
|
+
----- +
|
14
|
+
------- a
|
15
|
+
------- b
|
16
|
+
--- d
|
17
|
+
|
18
|
+
Here "+" and "*" are operators,
|
19
|
+
"a", "b" and "d" are operands
|
20
|
+
|
21
|
+
Expression tree structure is built with the help of <rubytree> gem
|
22
|
+
(https://rubygems.org/gems/rubytree)
|
23
|
+
|
24
|
+
In addition to basic classes of Expression, Operand and Operator
|
25
|
+
module provides some extensions for different nature expressions:
|
26
|
+
- Boolean expressions
|
27
|
+
- Sunspot Solr search expressions
|
28
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/de.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "de/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "de"
|
7
|
+
s.version = De::VERSION
|
8
|
+
s.authors = ["Olga Gorun"]
|
9
|
+
s.email = ["ogorun@quicklizard.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Dynamic Expression}
|
12
|
+
s.description = %q{De (Dynamic Expression) module provides means to build and evaluate
|
13
|
+
dynamic expression of arbitrary complecity and operands/operators nature}
|
14
|
+
|
15
|
+
s.rubyforge_project = "de"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency 'rubytree', '~> 0.8.1'
|
23
|
+
s.add_dependency 'activesupport', '~> 2.3.10'
|
24
|
+
s.add_development_dependency 'rack-test'
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#
|
2
|
+
# De::Boolean::Operand class
|
3
|
+
#
|
4
|
+
# De::Boolean module provides means for dynamic logical expression building/validation/evaluation
|
5
|
+
# It is built as extension of De module
|
6
|
+
#
|
7
|
+
# Boolean expression example:
|
8
|
+
#
|
9
|
+
# true AND (false OR true)
|
10
|
+
#
|
11
|
+
|
12
|
+
module De
|
13
|
+
module Boolean
|
14
|
+
|
15
|
+
# Marker module included to all module classes
|
16
|
+
# to be able to check that class is inside module
|
17
|
+
module Bn; end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Class representing Boolean operand
|
21
|
+
#
|
22
|
+
class Operand < De::Operand
|
23
|
+
include Bn
|
24
|
+
|
25
|
+
#
|
26
|
+
# Checks content is boolean value
|
27
|
+
#
|
28
|
+
# === Output
|
29
|
+
#
|
30
|
+
# true|false
|
31
|
+
#
|
32
|
+
def valid?
|
33
|
+
content.is_a?(TrueClass) || content.is_a?(FalseClass)
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Returns value stored in content field if it is valid
|
38
|
+
# or raises exception
|
39
|
+
#
|
40
|
+
def evaluate
|
41
|
+
super
|
42
|
+
content
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
#
|
2
|
+
# De::Boolean::Operator class
|
3
|
+
#
|
4
|
+
# De::Boolean module provides means for dynamic logical expression building/validation/evaluation
|
5
|
+
# It is built as extension of De module
|
6
|
+
#
|
7
|
+
# Boolean expression example:
|
8
|
+
#
|
9
|
+
# true AND (false OR true)
|
10
|
+
#
|
11
|
+
#
|
12
|
+
|
13
|
+
require 'de/symmetric_operator'
|
14
|
+
|
15
|
+
module De
|
16
|
+
module Boolean
|
17
|
+
|
18
|
+
# Marker module included to all module classes
|
19
|
+
# to be able to check that class is inside module
|
20
|
+
module Bn; end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Class representing Boolean operator
|
24
|
+
# Base abstract class for concrete operators AND, OR, NOT
|
25
|
+
#
|
26
|
+
class BooleanOperator < Operator
|
27
|
+
include Bn
|
28
|
+
include De::SymmetricOperator
|
29
|
+
|
30
|
+
#
|
31
|
+
# Constructor. Prevents direct BooleanOperator class objects creation
|
32
|
+
#
|
33
|
+
# === Input
|
34
|
+
#
|
35
|
+
# name<String>
|
36
|
+
# operands<Array>:: (optional) array of Operand objects.
|
37
|
+
# If given they are added as children to current operator
|
38
|
+
#
|
39
|
+
def initialize(name, operands = nil)
|
40
|
+
raise Error::AbstractClassObjectCreationError if instance_of? BooleanOperator
|
41
|
+
super(name, operands)
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Adds boolean operator or operand as a child to boolean operator
|
46
|
+
#
|
47
|
+
def <<(obj)
|
48
|
+
raise Error::TypeError unless obj.is_a?(De::Boolean::Bn)
|
49
|
+
super(obj)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Class representing boolean AND operator
|
56
|
+
#
|
57
|
+
class And < BooleanOperator
|
58
|
+
|
59
|
+
#
|
60
|
+
# Creates And object.
|
61
|
+
# Name includes word 'AND' with random number to avoid TreeNode problem
|
62
|
+
# when trying to add children with the same name to a node
|
63
|
+
#
|
64
|
+
# === Input
|
65
|
+
#
|
66
|
+
# operands<Array>:: (optional) array of Operand objects.
|
67
|
+
# If given they are added as children to current operator
|
68
|
+
#
|
69
|
+
def initialize(operands = nil)
|
70
|
+
super("AND-#{rand(1000)}", operands)
|
71
|
+
end
|
72
|
+
|
73
|
+
def evaluate
|
74
|
+
super
|
75
|
+
children.inject(true) {|result, el| result && el.evaluate}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Class representing boolean OR operator
|
81
|
+
#
|
82
|
+
class Or < BooleanOperator
|
83
|
+
|
84
|
+
#
|
85
|
+
# Creates Or object.
|
86
|
+
# Name includes word 'AND' with random number to avoid TreeNode problem
|
87
|
+
# when trying to add children with the same name to a node
|
88
|
+
#
|
89
|
+
# === Input
|
90
|
+
#
|
91
|
+
# operands<Array>:: (optional) array of Operand objects.
|
92
|
+
# If given they are added as children to current operator
|
93
|
+
#
|
94
|
+
def initialize(operands = nil)
|
95
|
+
super("OR-#{rand(1000)}", operands)
|
96
|
+
end
|
97
|
+
|
98
|
+
def evaluate
|
99
|
+
super
|
100
|
+
children.inject(false) {|result, el| result || el.evaluate}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Class representing boolean NOT operator
|
106
|
+
#
|
107
|
+
class Not < BooleanOperator
|
108
|
+
|
109
|
+
#
|
110
|
+
# Creates Not object.
|
111
|
+
# Name includes word 'AND' with random number to avoid TreeNode problem
|
112
|
+
# when trying to add children with the same name to a node
|
113
|
+
#
|
114
|
+
# === Input
|
115
|
+
#
|
116
|
+
# operand<De::Boolean::Operand>:: (optional) Operand object.
|
117
|
+
# If given it is added as child to current operator
|
118
|
+
#
|
119
|
+
def initialize(operand = nil)
|
120
|
+
super("NOT-#{rand(1000)}", operand ? [operand] : nil)
|
121
|
+
end
|
122
|
+
|
123
|
+
def <<(obj)
|
124
|
+
raise Error::ArgumentNumerError if has_children?
|
125
|
+
super(obj)
|
126
|
+
end
|
127
|
+
|
128
|
+
def evaluate
|
129
|
+
super
|
130
|
+
!first_child.evaluate
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
data/lib/de/boolean.rb
ADDED
data/lib/de/de.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
#
|
2
|
+
# De (Dynamic Expression) module provides means to build and evaluate
|
3
|
+
# dynamic expression of arbitrary complecity and operands/operators nature.
|
4
|
+
#
|
5
|
+
# Expression is considered as tree consisted of operands and operators.
|
6
|
+
# Operater must have child nodes. Operand is terminal node.
|
7
|
+
#
|
8
|
+
# Tree example:
|
9
|
+
# (a + b) * c + d
|
10
|
+
#
|
11
|
+
# - +
|
12
|
+
# --- *
|
13
|
+
# ----- +
|
14
|
+
# ------- a
|
15
|
+
# ------- b
|
16
|
+
# --- d
|
17
|
+
#
|
18
|
+
# Here "+" and "*" are operators,
|
19
|
+
# "a", "b" and "d" are operands
|
20
|
+
#
|
21
|
+
# Expression tree structure is built with the help of <rubytree> gem
|
22
|
+
# (https://rubygems.org/gems/rubytree)
|
23
|
+
#
|
24
|
+
# In addition to basic classes of Expression, Operand and Operator
|
25
|
+
# module provides some extensions for different nature expressions:
|
26
|
+
# - Boolean expressions
|
27
|
+
# - Sunspot Solr search expressions
|
28
|
+
#
|
29
|
+
|
30
|
+
require 'tree'
|
31
|
+
require 'de/error'
|
32
|
+
|
33
|
+
module De
|
34
|
+
|
35
|
+
#
|
36
|
+
# Base class of De module
|
37
|
+
# Represents tree of dynamic expression consisted of operators and operands
|
38
|
+
# Provides means for expression tree building, validation and evaluation
|
39
|
+
#
|
40
|
+
class Expression < Tree::TreeNode
|
41
|
+
|
42
|
+
#
|
43
|
+
# Adds operator or operand as a child to Expression
|
44
|
+
#
|
45
|
+
# === Input
|
46
|
+
#
|
47
|
+
# obj<Operator|Operand>:: object to be added as child one
|
48
|
+
#
|
49
|
+
# === Output
|
50
|
+
#
|
51
|
+
# <Operator|Operand>:: child object
|
52
|
+
#
|
53
|
+
def <<(obj)
|
54
|
+
raise Error::TypeError unless (obj.is_a?(Operator) or obj.is_a?(Operand))
|
55
|
+
super(obj)
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Checks expression validity
|
60
|
+
# Must be overriden by extending class
|
61
|
+
#
|
62
|
+
# === Input
|
63
|
+
#
|
64
|
+
# no input
|
65
|
+
#
|
66
|
+
# === Output
|
67
|
+
#
|
68
|
+
# true|false
|
69
|
+
#
|
70
|
+
def valid?
|
71
|
+
raise Error::MethodShouldBeOverridenByExtendingClassError
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Evaluates expression in case it is valid
|
76
|
+
# otherwise raises exception
|
77
|
+
# If expression valid returns true
|
78
|
+
# Must be overriden in extending class to do some usefull evaluation
|
79
|
+
#
|
80
|
+
# === Input
|
81
|
+
#
|
82
|
+
# no input
|
83
|
+
#
|
84
|
+
# === Output
|
85
|
+
#
|
86
|
+
# true
|
87
|
+
#
|
88
|
+
def evaluate
|
89
|
+
raise Error::InvalidExpressionError unless valid?
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# Override eql? and hash for consistent behaviour in hashes and arrays
|
95
|
+
#
|
96
|
+
|
97
|
+
def eql?(obj)
|
98
|
+
self == obj
|
99
|
+
end
|
100
|
+
|
101
|
+
def hash
|
102
|
+
@content.hash
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Class representing operator concept -
|
109
|
+
# kind of expression that has children
|
110
|
+
#
|
111
|
+
# Class can be considered abstract.
|
112
|
+
# Its object creation is prevented by constructor.
|
113
|
+
# Extending classes must be implemented
|
114
|
+
# in order to work with this concept
|
115
|
+
#
|
116
|
+
class Operator < Expression
|
117
|
+
|
118
|
+
#
|
119
|
+
# Constructor. Prevents this class objects direct creation
|
120
|
+
#
|
121
|
+
# === Input
|
122
|
+
#
|
123
|
+
# name<String>
|
124
|
+
# operands<Array>:: (optional) array of Operand objects.
|
125
|
+
# If given they are added as children to current operator
|
126
|
+
#
|
127
|
+
def initialize(name, operands = nil)
|
128
|
+
raise Error::AbstractClassObjectCreationError if instance_of? Operator
|
129
|
+
super(name)
|
130
|
+
|
131
|
+
unless operands.nil?
|
132
|
+
raise Error::TypeError unless operands.is_a?(Array)
|
133
|
+
operands.each { |operand| self << operand }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Checks expression validity
|
139
|
+
# Operator is valid if it has children and every one from them is valid
|
140
|
+
#
|
141
|
+
# === Input
|
142
|
+
#
|
143
|
+
# no input
|
144
|
+
#
|
145
|
+
# === Output
|
146
|
+
#
|
147
|
+
# true|false
|
148
|
+
#
|
149
|
+
def valid?
|
150
|
+
has_children? and children.inject(true) { |result, el| result and el.valid? }
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# Equal operator override
|
155
|
+
# This basic implementation checks objects class, children number equality
|
156
|
+
# and children equality. Operands order is important
|
157
|
+
#
|
158
|
+
def ==(obj)
|
159
|
+
self.class.name == obj.class.name && children == obj.children
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Define hash function to get equal results for operators from the same class and with the equal children
|
164
|
+
#
|
165
|
+
def hash
|
166
|
+
[self.class.name, children.map { |el| el.hash}].hash
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
# Class representing operand concept -
|
172
|
+
# kind of expression without children.
|
173
|
+
#
|
174
|
+
# Class can be considered abstract.
|
175
|
+
# Its object creation is prevented by constructor.
|
176
|
+
# Extending classes must be implemented
|
177
|
+
# in order to work with this concept
|
178
|
+
#
|
179
|
+
class Operand < Expression
|
180
|
+
#
|
181
|
+
# Constructor. Prevents this class objects direct creation
|
182
|
+
#
|
183
|
+
# === Input
|
184
|
+
#
|
185
|
+
# name<String>
|
186
|
+
# content<Object>:: arbitrary content. Its interpritation is done by extending classes
|
187
|
+
#
|
188
|
+
def initialize(name, content)
|
189
|
+
raise Error::AbstractClassObjectCreationError if instance_of? Operand
|
190
|
+
super(name, content)
|
191
|
+
end
|
192
|
+
|
193
|
+
#
|
194
|
+
# Adding children to operand is prevented (raises exception)
|
195
|
+
#
|
196
|
+
def <<(obj)
|
197
|
+
raise Error::TypeError
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# Evaluator must be overriden by extending classes (raises exception)
|
202
|
+
#
|
203
|
+
def evaluate
|
204
|
+
raise Error::MethodShouldBeOverridenByExtendingClassError if instance_of? Operand
|
205
|
+
super
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
data/lib/de/error.rb
ADDED