selector 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +2 -0
- data/.gitignore +9 -0
- data/.metrics +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +20 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +7 -0
- data/Guardfile +14 -0
- data/LICENSE +21 -0
- data/README.md +147 -0
- data/Rakefile +27 -0
- data/config/metrics/STYLEGUIDE +230 -0
- data/config/metrics/cane.yml +5 -0
- data/config/metrics/churn.yml +6 -0
- data/config/metrics/flay.yml +2 -0
- data/config/metrics/metric_fu.yml +14 -0
- data/config/metrics/reek.yml +1 -0
- data/config/metrics/roodi.yml +24 -0
- data/config/metrics/rubocop.yml +72 -0
- data/config/metrics/saikuro.yml +3 -0
- data/config/metrics/simplecov.yml +6 -0
- data/config/metrics/yardstick.yml +37 -0
- data/lib/selector.rb +55 -0
- data/lib/selector/and.rb +64 -0
- data/lib/selector/anything.rb +30 -0
- data/lib/selector/array.rb +42 -0
- data/lib/selector/collection.rb +37 -0
- data/lib/selector/condition.rb +99 -0
- data/lib/selector/function.rb +36 -0
- data/lib/selector/not.rb +50 -0
- data/lib/selector/nothing.rb +30 -0
- data/lib/selector/or.rb +54 -0
- data/lib/selector/regexp.rb +49 -0
- data/lib/selector/version.rb +9 -0
- data/selector.gemspec +25 -0
- data/spec/integration/blacklist_spec.rb +33 -0
- data/spec/integration/composition_spec.rb +34 -0
- data/spec/integration/negation_spec.rb +13 -0
- data/spec/integration/whitelist_spec.rb +33 -0
- data/spec/shared/generator.rb +8 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/unit/selector/and_spec.rb +100 -0
- data/spec/unit/selector/anything_spec.rb +29 -0
- data/spec/unit/selector/array_spec.rb +76 -0
- data/spec/unit/selector/collection_spec.rb +48 -0
- data/spec/unit/selector/condition_spec.rb +135 -0
- data/spec/unit/selector/function_spec.rb +46 -0
- data/spec/unit/selector/not_spec.rb +61 -0
- data/spec/unit/selector/nothing_spec.rb +29 -0
- data/spec/unit/selector/or_spec.rb +88 -0
- data/spec/unit/selector/regexp_spec.rb +69 -0
- data/spec/unit/selector_spec.rb +148 -0
- metadata +145 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Selector
|
4
|
+
|
5
|
+
# The condition checks if the function returns truthy result for a value
|
6
|
+
#
|
7
|
+
# @example (see #[])
|
8
|
+
#
|
9
|
+
class Function < Condition
|
10
|
+
|
11
|
+
# Initializes the condition with a function
|
12
|
+
#
|
13
|
+
# @param [#call] function
|
14
|
+
#
|
15
|
+
def initialize(_)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Checks if the function returns truthy for value
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# condition = Selector::Function.new -> v { v[/foo/] }
|
23
|
+
# condition[:foo] # => true
|
24
|
+
# condition[:bar] # => false
|
25
|
+
#
|
26
|
+
# @param (see Selector::Condition#[])
|
27
|
+
#
|
28
|
+
# @return (see Selector::Condition#[])
|
29
|
+
#
|
30
|
+
def [](value)
|
31
|
+
attribute.call(value) ? true : false
|
32
|
+
end
|
33
|
+
|
34
|
+
end # class Function
|
35
|
+
|
36
|
+
end # module Selector
|
data/lib/selector/not.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Selector
|
4
|
+
|
5
|
+
# The inversion of another condition
|
6
|
+
#
|
7
|
+
class Not < Condition
|
8
|
+
|
9
|
+
# @private
|
10
|
+
def self.new(source)
|
11
|
+
return NOTHING if source.equal? ANYTHING
|
12
|
+
return ANYTHING if source.equal? NOTHING
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the value doesn't satisfy inverted condition
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# condition = Selector.new(only: [:foo])
|
20
|
+
# inversion[:foo] # => true
|
21
|
+
# inversion[:bar] # => false
|
22
|
+
#
|
23
|
+
# inversion = Not.new(condition)
|
24
|
+
# inversion[:foo] # => false
|
25
|
+
# inversion[:bar] # => true
|
26
|
+
#
|
27
|
+
# @param (see Selector::Condition#[])
|
28
|
+
#
|
29
|
+
# @return (see Selector::Condition#[])
|
30
|
+
#
|
31
|
+
def [](value)
|
32
|
+
!attribute[value]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the inverted condition (avoids double negation)
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# condition = Selector.new(only: [:foo])
|
39
|
+
# inversion = Not.new(condition)
|
40
|
+
# !inversion == condition # => true
|
41
|
+
#
|
42
|
+
# @return (see Selector::Condition#!)
|
43
|
+
#
|
44
|
+
def !
|
45
|
+
attribute
|
46
|
+
end
|
47
|
+
|
48
|
+
end # class Not
|
49
|
+
|
50
|
+
end # module Selector
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Selector
|
4
|
+
|
5
|
+
# The condition that accepts any value
|
6
|
+
#
|
7
|
+
# @example (see #[])
|
8
|
+
#
|
9
|
+
class Nothing < Condition
|
10
|
+
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
# @!method [](value)
|
14
|
+
# Returns false
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# condition = Selector::Nothing.instance # singleton
|
18
|
+
# condition[:foo] # => false
|
19
|
+
#
|
20
|
+
# @param (see Selector::Condition#[])
|
21
|
+
#
|
22
|
+
# @return [false]
|
23
|
+
#
|
24
|
+
def [](_value)
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
end # class Nothing
|
29
|
+
|
30
|
+
end # module Selector
|
data/lib/selector/or.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Selector
|
4
|
+
|
5
|
+
# The composition of several conditions. Requires any of them to be satisfied
|
6
|
+
#
|
7
|
+
# @example (see #[])
|
8
|
+
#
|
9
|
+
class Or < Condition
|
10
|
+
|
11
|
+
# @private
|
12
|
+
def self.new(*attributes)
|
13
|
+
attrs = attributes.uniq - [NOTHING]
|
14
|
+
|
15
|
+
return NOTHING if attrs.empty?
|
16
|
+
return attrs.first if attrs.count.equal?(1)
|
17
|
+
return ANYTHING if attrs.include? ANYTHING
|
18
|
+
return ANYTHING if (attrs & attrs.map(&:!)).any?
|
19
|
+
|
20
|
+
super(*attrs)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Checks if value satisfies any of composed conditions
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# left = Selector.new only: /foo/
|
27
|
+
# right = Selector.new only: /bar/
|
28
|
+
# composition = Selector::Or.new(left, right)
|
29
|
+
#
|
30
|
+
# composition[:foo] # => true
|
31
|
+
# composition[:bar] # => true
|
32
|
+
# composition[:baz] # => false
|
33
|
+
#
|
34
|
+
# @param (see Selector::Composition#[])
|
35
|
+
#
|
36
|
+
# @return (see Selector::Composition#[])
|
37
|
+
#
|
38
|
+
def [](value)
|
39
|
+
attributes.detect { |part| part[value] } ? true : false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Adds the other condition to the composition (avoids nesting)
|
43
|
+
#
|
44
|
+
# @param (see Selector::Composition#|)
|
45
|
+
#
|
46
|
+
# @return (see Selector::Composition#|)
|
47
|
+
#
|
48
|
+
def |(other)
|
49
|
+
Or.new(*attributes, other)
|
50
|
+
end
|
51
|
+
|
52
|
+
end # class Or
|
53
|
+
|
54
|
+
end # module Selector
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Selector
|
4
|
+
|
5
|
+
# The condition checks if a value matches the regexp
|
6
|
+
#
|
7
|
+
# @example (see #[])
|
8
|
+
#
|
9
|
+
class Regexp < Condition
|
10
|
+
|
11
|
+
# Initializes the condition with the regexp
|
12
|
+
#
|
13
|
+
# @param [::Regexp] regexp
|
14
|
+
#
|
15
|
+
def initialize(_)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Checks if the stringified value matches the regexp
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# condition = Selector::Regexp.new /1/
|
23
|
+
# condition[11] # => true
|
24
|
+
# condition[22] # => false
|
25
|
+
#
|
26
|
+
# @param (see Selector::Condition#[])
|
27
|
+
#
|
28
|
+
# @return (see Selector::Condition#[])
|
29
|
+
#
|
30
|
+
def [](value)
|
31
|
+
value.to_s[attribute] ? true : false
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates an OR composition
|
35
|
+
#
|
36
|
+
# If other value is a regexp, then creates modified regexp to avoid nesting
|
37
|
+
#
|
38
|
+
# @param (see Selector::Composition#|)
|
39
|
+
#
|
40
|
+
# @return (see Selector::Composition#|)
|
41
|
+
#
|
42
|
+
def |(other)
|
43
|
+
return super unless other.instance_of? Regexp
|
44
|
+
Regexp.new(/(#{attribute})|(#{other.attribute})/)
|
45
|
+
end
|
46
|
+
|
47
|
+
end # class Regexp < Condition
|
48
|
+
|
49
|
+
end # module Selector
|
data/selector.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "selector/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
|
6
|
+
gem.name = "selector"
|
7
|
+
gem.version = Selector::VERSION.dup
|
8
|
+
gem.author = "Andrew Kozin"
|
9
|
+
gem.email = "andrew.kozin@gmail.com"
|
10
|
+
gem.homepage = "https://github.com/nepalez/selector"
|
11
|
+
gem.summary = "Composable multi-type conditions."
|
12
|
+
gem.license = "MIT"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
15
|
+
gem.test_files = Dir["spec/**/*.rb"]
|
16
|
+
gem.extra_rdoc_files = Dir["README.md", "LICENSE"]
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.required_ruby_version = "~> 1.9.3"
|
20
|
+
|
21
|
+
gem.add_runtime_dependency "ice_nine", "~> 0.11"
|
22
|
+
|
23
|
+
gem.add_development_dependency "hexx-rspec", "~> 0.5"
|
24
|
+
|
25
|
+
end # Gem::Specification
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe "Blacklist" do
|
4
|
+
|
5
|
+
it "works with array" do
|
6
|
+
selector = Selector.new except: [:foo, :qux]
|
7
|
+
|
8
|
+
expect(selector[:foo]).to eql(false)
|
9
|
+
expect(selector[:bar]).to eql(true)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "works with regexp" do
|
13
|
+
selector = Selector.new except: /foo/
|
14
|
+
|
15
|
+
expect(selector[:foobar]).to eql(false)
|
16
|
+
expect(selector[:bar]).to eql(true)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "works with range" do
|
20
|
+
selector = Selector.new except: 1..3
|
21
|
+
|
22
|
+
expect(selector[2.4]).to eql(false)
|
23
|
+
expect(selector[0]).to eql(true)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "works with proc" do
|
27
|
+
selector = Selector.new except: -> value { value.is_a? Hash }
|
28
|
+
|
29
|
+
expect(selector[foo: :FOO]).to eql(false)
|
30
|
+
expect(selector[:foo]).to eql(true)
|
31
|
+
end
|
32
|
+
|
33
|
+
end # describe Whitelist
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe "Composition" do
|
4
|
+
|
5
|
+
it "& works" do
|
6
|
+
blacklist = Selector.new except: /bar/
|
7
|
+
whitelist = Selector.new only: /foo/
|
8
|
+
selector = whitelist & blacklist
|
9
|
+
|
10
|
+
expect(selector[:foobaz]).to eql(true)
|
11
|
+
expect(selector[:foobar]).to eql(false)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "- works" do
|
15
|
+
whitelist = Selector.new only: /foo/
|
16
|
+
blacklist = Selector.new except: /bar/
|
17
|
+
selector = whitelist - blacklist
|
18
|
+
|
19
|
+
expect(selector[:foobar]).to eql(true)
|
20
|
+
expect(selector[:bar]).to eql(false)
|
21
|
+
expect(selector[:foo]).to eql(false)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "| works" do
|
25
|
+
whitelist = Selector.new only: 4..8
|
26
|
+
blacklist = Selector.new except: 1..5
|
27
|
+
selector = whitelist | blacklist
|
28
|
+
|
29
|
+
expect(selector[0.5]).to eql(true)
|
30
|
+
expect(selector[5.5]).to eql(true)
|
31
|
+
expect(selector[2.5]).to eql(false)
|
32
|
+
end
|
33
|
+
|
34
|
+
end # describe Composition
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe "Whitelist" do
|
4
|
+
|
5
|
+
it "works with array" do
|
6
|
+
selector = Selector.new only: [:foo, :qux]
|
7
|
+
|
8
|
+
expect(selector[:foo]).to eql(true)
|
9
|
+
expect(selector[:bar]).to eql(false)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "works with regexp" do
|
13
|
+
selector = Selector.new only: /foo/
|
14
|
+
|
15
|
+
expect(selector[:foobar]).to eql(true)
|
16
|
+
expect(selector[:bar]).to eql(false)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "works with range" do
|
20
|
+
selector = Selector.new only: 1..3
|
21
|
+
|
22
|
+
expect(selector[2.4]).to eql(true)
|
23
|
+
expect(selector[0]).to eql(false)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "works with proc" do
|
27
|
+
selector = Selector.new only: -> value { value.is_a? Hash }
|
28
|
+
|
29
|
+
expect(selector[foo: :FOO]).to eql(true)
|
30
|
+
expect(selector[:foo]).to eql(false)
|
31
|
+
end
|
32
|
+
|
33
|
+
end # describe Whitelist
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "shared/generator"
|
4
|
+
|
5
|
+
module Selector
|
6
|
+
|
7
|
+
describe Selector::And do
|
8
|
+
|
9
|
+
let(:foo) { generate(/foo/) }
|
10
|
+
let(:bar) { generate(/bar/) }
|
11
|
+
let(:baz) { generate(/baz/) }
|
12
|
+
|
13
|
+
let(:composition) { described_class.new(foo, bar) }
|
14
|
+
|
15
|
+
describe ".new" do
|
16
|
+
|
17
|
+
subject { composition }
|
18
|
+
|
19
|
+
it { is_expected.to be_kind_of Condition }
|
20
|
+
it { is_expected.to be_frozen }
|
21
|
+
|
22
|
+
it "returns ANYTHING when possible" do
|
23
|
+
subject = described_class.new ANYTHING
|
24
|
+
expect(subject).to eql ANYTHING
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns NOTHING if exists" do
|
28
|
+
subject = described_class.new foo, bar, NOTHING
|
29
|
+
expect(subject).to eql NOTHING
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns NOTHING when possible" do
|
33
|
+
subject = described_class.new foo, !foo
|
34
|
+
expect(subject).to eql NOTHING
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns the only attribute" do
|
38
|
+
subject = described_class.new foo, ANYTHING
|
39
|
+
expect(subject).to eql foo
|
40
|
+
end
|
41
|
+
|
42
|
+
it "ignores duplication" do
|
43
|
+
subject = described_class.new foo, bar, foo
|
44
|
+
expect(subject.attributes).to eql [foo, bar]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "ignores ANYTHING" do
|
48
|
+
subject = described_class.new foo, bar, ANYTHING
|
49
|
+
expect(subject.attributes).to eql [foo, bar]
|
50
|
+
end
|
51
|
+
|
52
|
+
end # describe .new
|
53
|
+
|
54
|
+
describe "#[]" do
|
55
|
+
|
56
|
+
subject { composition[value] }
|
57
|
+
|
58
|
+
context "when all conditions are satisfied" do
|
59
|
+
|
60
|
+
let(:value) { "foobar" }
|
61
|
+
it { is_expected.to eql(true) }
|
62
|
+
|
63
|
+
end # context
|
64
|
+
|
65
|
+
context "when any condition isn't satisfied" do
|
66
|
+
|
67
|
+
let(:value) { "foo" }
|
68
|
+
it { is_expected.to eql(false) }
|
69
|
+
|
70
|
+
end # context
|
71
|
+
|
72
|
+
end # describe #[]
|
73
|
+
|
74
|
+
describe "#&" do
|
75
|
+
|
76
|
+
subject { composition & baz }
|
77
|
+
|
78
|
+
it { is_expected.to be_kind_of(described_class) }
|
79
|
+
|
80
|
+
it "updates conditions (avoids nesting)" do
|
81
|
+
expect(subject.attributes).to eql [foo, bar, baz]
|
82
|
+
end
|
83
|
+
|
84
|
+
end # describe #&
|
85
|
+
|
86
|
+
describe "#-" do
|
87
|
+
|
88
|
+
subject { composition - baz }
|
89
|
+
|
90
|
+
it { is_expected.to be_kind_of(described_class) }
|
91
|
+
|
92
|
+
it "updates conditions (avoids nesting)" do
|
93
|
+
expect(subject.attributes).to contain_exactly(foo, bar, !baz)
|
94
|
+
end
|
95
|
+
|
96
|
+
end # describe #-
|
97
|
+
|
98
|
+
end # describe Selector::And
|
99
|
+
|
100
|
+
end # module Selector
|