selector 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +9 -0
  4. data/.metrics +9 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +2 -0
  7. data/.travis.yml +20 -0
  8. data/.yardopts +3 -0
  9. data/CHANGELOG.md +5 -0
  10. data/Gemfile +7 -0
  11. data/Guardfile +14 -0
  12. data/LICENSE +21 -0
  13. data/README.md +147 -0
  14. data/Rakefile +27 -0
  15. data/config/metrics/STYLEGUIDE +230 -0
  16. data/config/metrics/cane.yml +5 -0
  17. data/config/metrics/churn.yml +6 -0
  18. data/config/metrics/flay.yml +2 -0
  19. data/config/metrics/metric_fu.yml +14 -0
  20. data/config/metrics/reek.yml +1 -0
  21. data/config/metrics/roodi.yml +24 -0
  22. data/config/metrics/rubocop.yml +72 -0
  23. data/config/metrics/saikuro.yml +3 -0
  24. data/config/metrics/simplecov.yml +6 -0
  25. data/config/metrics/yardstick.yml +37 -0
  26. data/lib/selector.rb +55 -0
  27. data/lib/selector/and.rb +64 -0
  28. data/lib/selector/anything.rb +30 -0
  29. data/lib/selector/array.rb +42 -0
  30. data/lib/selector/collection.rb +37 -0
  31. data/lib/selector/condition.rb +99 -0
  32. data/lib/selector/function.rb +36 -0
  33. data/lib/selector/not.rb +50 -0
  34. data/lib/selector/nothing.rb +30 -0
  35. data/lib/selector/or.rb +54 -0
  36. data/lib/selector/regexp.rb +49 -0
  37. data/lib/selector/version.rb +9 -0
  38. data/selector.gemspec +25 -0
  39. data/spec/integration/blacklist_spec.rb +33 -0
  40. data/spec/integration/composition_spec.rb +34 -0
  41. data/spec/integration/negation_spec.rb +13 -0
  42. data/spec/integration/whitelist_spec.rb +33 -0
  43. data/spec/shared/generator.rb +8 -0
  44. data/spec/spec_helper.rb +12 -0
  45. data/spec/unit/selector/and_spec.rb +100 -0
  46. data/spec/unit/selector/anything_spec.rb +29 -0
  47. data/spec/unit/selector/array_spec.rb +76 -0
  48. data/spec/unit/selector/collection_spec.rb +48 -0
  49. data/spec/unit/selector/condition_spec.rb +135 -0
  50. data/spec/unit/selector/function_spec.rb +46 -0
  51. data/spec/unit/selector/not_spec.rb +61 -0
  52. data/spec/unit/selector/nothing_spec.rb +29 -0
  53. data/spec/unit/selector/or_spec.rb +88 -0
  54. data/spec/unit/selector/regexp_spec.rb +69 -0
  55. data/spec/unit/selector_spec.rb +148 -0
  56. 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
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ module Selector
4
+
5
+ # The semantic version of the gem.
6
+ # @see http://semver.org/ Semantic versioning 2.0
7
+ VERSION = "0.0.1".freeze
8
+
9
+ 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,13 @@
1
+ # encoding: utf-8
2
+
3
+ describe "Negation" do
4
+
5
+ it "works" do
6
+ blacklist = Selector.new except: /bar/
7
+ selector = !blacklist
8
+
9
+ expect(selector[:foo]).to eql(false)
10
+ expect(selector[:bar]).to eql(true)
11
+ end
12
+
13
+ end # describe Negation
@@ -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
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ # Generates the condition object with regexp
4
+ def generate(regexp)
5
+ klass = Class.new(Selector::Condition)
6
+ klass.send(:define_method, :[]) { |value| nil ^ value[regexp] }
7
+ klass.new
8
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require "hexx-suit"
5
+ Hexx::Suit.load_metrics_for(self)
6
+ rescue LoadError
7
+ require "hexx-rspec"
8
+ Hexx::RSpec.load_metrics_for(self)
9
+ end
10
+
11
+ # Loads the code under test
12
+ require "selector"
@@ -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