mutant 0.3.0.rc1 → 0.3.0.rc2

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.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.travis.yml +5 -4
  4. data/Gemfile.devtools +4 -4
  5. data/Guardfile +7 -5
  6. data/README.md +8 -8
  7. data/config/flay.yml +2 -2
  8. data/config/reek.yml +1 -0
  9. data/config/rubocop.yml +0 -4
  10. data/lib/mutant/cli/builder.rb +177 -0
  11. data/lib/mutant/cli/classifier/method.rb +10 -14
  12. data/lib/mutant/cli/classifier/namespace.rb +7 -9
  13. data/lib/mutant/cli/classifier.rb +29 -31
  14. data/lib/mutant/cli.rb +34 -96
  15. data/lib/mutant/color.rb +1 -1
  16. data/lib/mutant/config.rb +9 -3
  17. data/lib/mutant/constants.rb +1 -1
  18. data/lib/mutant/context/scope.rb +2 -2
  19. data/lib/mutant/differ.rb +0 -1
  20. data/lib/mutant/killer/rspec.rb +3 -8
  21. data/lib/mutant/killer.rb +1 -22
  22. data/lib/mutant/matcher/chain.rb +2 -2
  23. data/lib/mutant/matcher/filter.rb +32 -0
  24. data/lib/mutant/matcher/method/instance.rb +2 -2
  25. data/lib/mutant/matcher/method.rb +3 -3
  26. data/lib/mutant/matcher/methods.rb +1 -1
  27. data/lib/mutant/matcher.rb +1 -0
  28. data/lib/mutant/mutation/evil.rb +0 -10
  29. data/lib/mutant/mutation/neutral.rb +0 -23
  30. data/lib/mutant/mutation.rb +13 -23
  31. data/lib/mutant/mutator/node/begin.rb +12 -10
  32. data/lib/mutant/mutator/node/block.rb +1 -0
  33. data/lib/mutant/mutator/node/blockarg.rb +15 -0
  34. data/lib/mutant/mutator/node/case.rb +1 -0
  35. data/lib/mutant/mutator/node/connective/binary.rb +2 -2
  36. data/lib/mutant/mutator/node/const.rb +2 -1
  37. data/lib/mutant/mutator/node/dstr.rb +28 -0
  38. data/lib/mutant/mutator/node/dsym.rb +28 -0
  39. data/lib/mutant/mutator/node/generic.rb +3 -4
  40. data/lib/mutant/mutator/node/if.rb +1 -0
  41. data/lib/mutant/mutator/node/literal/boolean.rb +2 -2
  42. data/lib/mutant/mutator/node/literal/range.rb +8 -6
  43. data/lib/mutant/mutator/node/literal.rb +0 -17
  44. data/lib/mutant/mutator/node/masgn.rb +1 -1
  45. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +5 -4
  46. data/lib/mutant/mutator/node/op_asgn.rb +30 -0
  47. data/lib/mutant/mutator/node/restarg.rb +15 -0
  48. data/lib/mutant/mutator/node/return.rb +1 -2
  49. data/lib/mutant/mutator/node/send.rb +6 -5
  50. data/lib/mutant/mutator/node/super.rb +1 -0
  51. data/lib/mutant/mutator/node/while.rb +1 -0
  52. data/lib/mutant/mutator/node.rb +11 -1
  53. data/lib/mutant/node_helpers.rb +3 -3
  54. data/lib/mutant/predicate/attribute.rb +85 -0
  55. data/lib/mutant/predicate/blacklist.rb +27 -0
  56. data/lib/mutant/predicate/matcher.rb +36 -0
  57. data/lib/mutant/predicate/whitelist.rb +28 -0
  58. data/lib/mutant/predicate.rb +91 -0
  59. data/lib/mutant/reporter/cli/printer/config.rb +3 -3
  60. data/lib/mutant/reporter/cli/printer/killer.rb +1 -1
  61. data/lib/mutant/reporter/cli/printer/mutation.rb +12 -14
  62. data/lib/mutant/reporter/cli/printer/subject.rb +1 -3
  63. data/lib/mutant/reporter/cli/printer.rb +31 -7
  64. data/lib/mutant/runner/mutation.rb +9 -1
  65. data/lib/mutant/runner/subject.rb +9 -1
  66. data/lib/mutant/runner.rb +5 -6
  67. data/lib/mutant/strategy/rspec.rb +4 -4
  68. data/lib/mutant/strategy.rb +2 -0
  69. data/lib/mutant/support/method_object.rb +0 -1
  70. data/lib/mutant/version.rb +1 -2
  71. data/lib/mutant/zombifier.rb +6 -3
  72. data/lib/mutant.rb +12 -4
  73. data/mutant.gemspec +7 -7
  74. data/spec/integration/mutant/rspec_spec.rb +12 -5
  75. data/spec/shared/mutator_behavior.rb +2 -1
  76. data/spec/spec_helper.rb +11 -9
  77. data/spec/unit/mutant/cli/builder/rspec_spec.rb +38 -0
  78. data/spec/unit/mutant/cli/classifier/{method/each_spec.rb → method_spec.rb} +2 -14
  79. data/spec/unit/mutant/cli/classifier/namespace/{flat/each_spec.rb → flat_spec.rb} +1 -1
  80. data/spec/unit/mutant/cli/classifier/namespace/{recursive/each_spec.rb → recursive_spec.rb} +1 -1
  81. data/spec/unit/mutant/cli/classifier_spec.rb +59 -0
  82. data/spec/unit/mutant/{cli/class_methods/new_spec.rb → cli_new_spec.rb} +22 -23
  83. data/spec/unit/mutant/{cli/class_methods/run_spec.rb → cli_run_spec.rb} +8 -8
  84. data/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +6 -6
  85. data/spec/unit/mutant/killer/success_predicate_spec.rb +5 -5
  86. data/spec/unit/mutant/loader/eval/class_methods/run_spec.rb +1 -1
  87. data/spec/unit/mutant/matcher/filter_spec.rb +19 -0
  88. data/spec/unit/mutant/matcher/namespace/each_spec.rb +5 -5
  89. data/spec/unit/mutant/mutation_spec.rb +42 -0
  90. data/spec/unit/mutant/mutator/each_spec.rb +1 -1
  91. data/spec/unit/mutant/mutator/node/and_asgn/mutation_spec.rb +3 -2
  92. data/spec/unit/mutant/mutator/node/block/mutation_spec.rb +7 -2
  93. data/spec/unit/mutant/mutator/node/block_pass/mutation_spec.rb +1 -0
  94. data/spec/unit/mutant/mutator/node/blockarg/mutation_spec.rb +17 -0
  95. data/spec/unit/mutant/mutator/node/case/mutation_spec.rb +5 -1
  96. data/spec/unit/mutant/mutator/node/cbase/mutation_spec.rb +1 -0
  97. data/spec/unit/mutant/mutator/node/const/mutation_spec.rb +3 -2
  98. data/spec/unit/mutant/mutator/node/define/mutation_spec.rb +3 -3
  99. data/spec/unit/mutant/mutator/node/defined_predicate/mutation_spec.rb +6 -2
  100. data/spec/unit/mutant/mutator/node/dstr/mutation_spec.rb +4 -2
  101. data/spec/unit/mutant/mutator/node/dsym/mutation_spec.rb +4 -2
  102. data/spec/unit/mutant/mutator/node/if/mutation_spec.rb +6 -1
  103. data/spec/unit/mutant/mutator/node/literal/fixnum_spec.rb +1 -1
  104. data/spec/unit/mutant/mutator/node/literal/float_spec.rb +1 -1
  105. data/spec/unit/mutant/mutator/node/literal/range_spec.rb +31 -0
  106. data/spec/unit/mutant/mutator/node/literal/regex_spec.rb +4 -2
  107. data/spec/unit/mutant/mutator/node/literal/string_spec.rb +1 -1
  108. data/spec/unit/mutant/mutator/node/literal/symbol_spec.rb +1 -1
  109. data/spec/unit/mutant/mutator/node/masgn/mutation_spec.rb +6 -2
  110. data/spec/unit/mutant/mutator/node/match_current_line/mutation_spec.rb +1 -0
  111. data/spec/unit/mutant/mutator/node/named_value/access/mutation_spec.rb +5 -1
  112. data/spec/unit/mutant/mutator/node/named_value/constant_assignment/mutation_spec.rb +1 -2
  113. data/spec/unit/mutant/mutator/node/named_value/variable_assignment/mutation_spec.rb +5 -5
  114. data/spec/unit/mutant/mutator/node/op_assgn/mutation_spec.rb +3 -2
  115. data/spec/unit/mutant/mutator/node/or_asgn/mutation_spec.rb +3 -2
  116. data/spec/unit/mutant/mutator/node/rescue/mutation_spec.rb +1 -1
  117. data/spec/unit/mutant/mutator/node/restarg/mutation_spec.rb +3 -1
  118. data/spec/unit/mutant/mutator/node/return/mutation_spec.rb +7 -3
  119. data/spec/unit/mutant/mutator/node/send/mutation_spec.rb +37 -2
  120. data/spec/unit/mutant/mutator/node/super/mutation_spec.rb +4 -0
  121. data/spec/unit/mutant/mutator/node/while/mutation_spec.rb +3 -0
  122. data/spec/unit/mutant/predicate_spec.rb +135 -0
  123. data/spec/unit/mutant/runner/config/subjects_spec.rb +11 -11
  124. data/spec/unit/mutant/runner/config/success_predicate_spec.rb +6 -6
  125. data/spec/unit/mutant/runner/mutation/killer_spec.rb +11 -11
  126. data/spec/unit/mutant/runner/subject/success_predicate_spec.rb +9 -9
  127. data/spec/unit/mutant/strategy_spec.rb +21 -0
  128. data/spec/unit/mutant/subject_spec.rb +39 -0
  129. data/test_app/spec/shared/method_filter_parse_behavior.rb +1 -1
  130. metadata +54 -36
  131. data/lib/mutant/cli/classifier/scope.rb +0 -37
  132. data/lib/mutant/mutation/filter/code.rb +0 -49
  133. data/lib/mutant/mutation/filter/regexp.rb +0 -29
  134. data/lib/mutant/mutation/filter/whitelist.rb +0 -52
  135. data/lib/mutant/mutation/filter.rb +0 -78
  136. data/lib/mutant/strategy/static.rb +0 -20
  137. data/spec/unit/mutant/cli/classifier/class_methods/build_spec.rb +0 -48
  138. data/spec/unit/mutant/cli/classifier/scope/each_spec.rb +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bd04beb38330a250653e83799b208a0eb92a8656
4
- data.tar.gz: 4b6512fbb3f2e31fcbd90411a1ea1c0c9f10c261
3
+ metadata.gz: 5aa8cc60b0b68e67f1c9a253bfce5c7bd7aa7920
4
+ data.tar.gz: 60323d0b1d1d2230a62a3f9b7a28d50eb8014d7d
5
5
  SHA512:
6
- metadata.gz: 8b11f4a25768e953373c1ce9a854051c9ffa28758a139d369568089a520eb90893585242dcc4586c276e112b886157083ac0165fcb0c83e94fcf768a97c613c8
7
- data.tar.gz: 8d358b32f4c915ab85779f43106e607576250fa8f8badf1a94ff23cfc84845e919411bba73a6380b869ce7f8525eeca4174e38a313d864c81c3e636e31eb0ea7
6
+ metadata.gz: 21ce19cfa4bfddf01eb471a3c8685d3e3468c1c5dd1c9504ecc00f6e43f5a096e64573d3ff6ebb13ed0720b224ba51b3c9588a03d127255a43494e4517bdf755
7
+ data.tar.gz: 91157f59a4f81ff1957168ee0300fa4b33869fcd975ed9b3461a5e50fc9e7c7a629d2e1aa6ee83d47c48b21c824b3e8a20a5f7fb2feefd67fe8e1bf6d7e8d605
data/.rspec CHANGED
@@ -1,5 +1,4 @@
1
1
  --color
2
2
  --format progress
3
- --profile
4
3
  --warnings
5
4
  --order random
data/.travis.yml CHANGED
@@ -2,12 +2,13 @@ language: ruby
2
2
  before_install: gem install bundler
3
3
  bundler_args: --without yard guard benchmarks
4
4
  script: "bundle exec rake ci"
5
+ rvm:
6
+ - 1.9.3
7
+ - 2.0.0
8
+ - ruby-head
9
+ - rbx-19mode
5
10
  matrix:
6
11
  include:
7
- - rvm: 1.9.3
8
- - rvm: 2.0.0
9
- - rvm: ruby-head
10
- - rvm: rbx-19mode
11
12
  - rvm: jruby-19mode
12
13
  env: JRUBY_OPTS="$JRUBY_OPTS --debug"
13
14
  - rvm: jruby-head
data/Gemfile.devtools CHANGED
@@ -18,7 +18,7 @@ group :guard do
18
18
  gem 'guard-mutant', '~> 0.0.1'
19
19
 
20
20
  # file system change event handling
21
- gem 'listen', '~> 1.2.2'
21
+ gem 'listen', '~> 1.3.0'
22
22
  gem 'rb-fchange', '~> 0.0.6', require: false
23
23
  gem 'rb-fsevent', '~> 0.9.3', require: false
24
24
  gem 'rb-inotify', '~> 0.9.0', require: false
@@ -33,10 +33,10 @@ group :metrics do
33
33
  gem 'coveralls', '~> 0.6.7'
34
34
  gem 'flay', '~> 2.4.0'
35
35
  gem 'flog', '~> 4.1.1'
36
- gem 'reek', '~> 1.3.1', git: 'https://github.com/troessner/reek.git'
37
- gem 'rubocop', '~> 0.10.0', git: 'https://github.com/bbatsov/rubocop.git'
36
+ gem 'reek', '~> 1.3.2'
37
+ gem 'rubocop', '~> 0.11.0'
38
38
  gem 'simplecov', '~> 0.7.1'
39
- gem 'yardstick', '~> 0.9.6', git: 'https://github.com/dkubb/yardstick.git'
39
+ gem 'yardstick', '~> 0.9.7', git: 'https://github.com/dkubb/yardstick.git'
40
40
 
41
41
  platforms :ruby_19, :ruby_20 do
42
42
  gem 'yard-spellcheck', '~> 0.1.5'
data/Guardfile CHANGED
@@ -25,8 +25,10 @@ guard :rspec, cli: File.read('.rspec').split.push('--fail-fast').join(' '), keep
25
25
  watch(%r{\Aspec/(?:unit|integration)/.+_spec\.rb\z})
26
26
  end
27
27
 
28
- guard :rubocop, cli: %w[--config config/rubocop.yml] do
29
- watch(%r{.+\.(?:rb|rake)\z})
30
- watch(%r{\Aconfig/rubocop\.yml\z}) { |m| File.dirname(m[0]) }
31
- watch(%r{(?:.+/)?\.rubocop\.yml\z}) { |m| File.dirname(m[0]) }
32
- end
28
+ # Deactivated for now. Somehow it disables the rspec guard.
29
+ #
30
+ # guard :rubocop, cli: %w[--config config/rubocop.yml] do
31
+ # watch(%r{.+\.(?:rb|rake)\z})
32
+ # watch(%r{\Aconfig/rubocop\.yml\z}) { |m| File.dirname(m[0]) }
33
+ # watch(%r{(?:.+/)?\.rubocop\.yml\z}) { |m| File.dirname(m[0]) }
34
+ # end
data/README.md CHANGED
@@ -62,14 +62,14 @@ Currently mutant covers the majority of ruby's complex nodes that often occur in
62
62
  Some stats from the [axiom](https://github.com/dkubb/axiom) library:
63
63
 
64
64
  ```
65
- Subjects: 417 # Amount of subjects being mutated (currently only methods)
66
- Mutations: 5442 # Amount of mutations mutant generated (~13 mutations per method)
67
- Kills: 5385 # Amount of successfully killed mutations
68
- Runtime: 1898.11s # Total runtime
69
- Killtime: 1884.17s # Time spend killing mutations
70
- Overhead: 0.73%
71
- Coverage: 98.95% # Coverage score
72
- Alive: 57 # Amount of alive mutations.
65
+ Subjects: 424 # Amount of subjects being mutated (currently only methods)
66
+ Mutations: 6760 # Amount of mutations mutant generated (~13 mutations per method)
67
+ Kills: 6664 # Amount of successfully killed mutations
68
+ Runtime: 5123.13s # Total runtime
69
+ Killtime: 5092.63s # Time spend killing mutations
70
+ Overhead: 0.60%
71
+ Coverage: 98.58% # Coverage score
72
+ Alive: 96 # Amount of alive mutations.
73
73
  ```
74
74
 
75
75
 
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
- threshold: 16
3
- total_score: 738
2
+ threshold: 18
3
+ total_score: 748
data/config/reek.yml CHANGED
@@ -73,6 +73,7 @@ TooManyMethods:
73
73
  enabled: true
74
74
  exclude:
75
75
  - Mutant::CLI
76
+ - Mutant::Mutator::Node
76
77
  - Mutant::Reporter::CLI
77
78
  max_methods: 10
78
79
  TooManyStatements:
data/config/rubocop.yml CHANGED
@@ -80,10 +80,6 @@ ConstantName:
80
80
  TrivialAccessors:
81
81
  Enabled: false
82
82
 
83
- # I like 1.8 syntax:
84
- HashSyntax:
85
- Enabled: false
86
-
87
83
  # And also have a differend opinion here
88
84
  AndOr:
89
85
  Enabled: false
@@ -0,0 +1,177 @@
1
+ # encoding: utf-8
2
+
3
+ module Mutant
4
+ class CLI
5
+ # Abstract base class for strategy builders
6
+ class Builder
7
+ include AbstractType
8
+
9
+ # Return cache
10
+ #
11
+ # @return [Cache]
12
+ #
13
+ # @api private
14
+ #
15
+ attr_reader :cache
16
+ private :cache
17
+
18
+ # Return parser
19
+ #
20
+ # @return [OptionParser]
21
+ #
22
+ # @api private
23
+ #
24
+ attr_reader :parser
25
+ private :parser
26
+
27
+ # Initialize builder
28
+ #
29
+ # @param [OptionParser] parser
30
+ #
31
+ # @api privateo
32
+ #
33
+ def initialize(cache, parser)
34
+ @cache, @parser = cache, parser
35
+ add_options
36
+ end
37
+
38
+ # Add cli options
39
+ #
40
+ # @param [OptionParser]
41
+ #
42
+ # @return [self]
43
+ #
44
+ # @api private
45
+ #
46
+ abstract_method :add_options
47
+
48
+ # Return build output
49
+ #
50
+ # @return [Object]
51
+ #
52
+ # @api private
53
+ #
54
+ abstract_method :output
55
+
56
+ # Rspec strategy builder
57
+ class Rspec < self
58
+
59
+ # Initialize object
60
+ #
61
+ # @return [undefined]
62
+ #
63
+ # @api private
64
+ #
65
+ def initialize(*)
66
+ @level = 0
67
+ @rspec = false
68
+ super
69
+ end
70
+
71
+ # Return strategy
72
+ #
73
+ # @return [Strategy::Rspec]
74
+ #
75
+ # @api private
76
+ #
77
+ def output
78
+ unless @rspec
79
+ raise Error, 'No strategy given'
80
+ end
81
+
82
+ Strategy::Rspec.new(@level)
83
+ end
84
+
85
+ private
86
+
87
+ # Set rspec level
88
+ #
89
+ # @return [self]
90
+ #
91
+ # @api private
92
+ #
93
+ def set_level(level)
94
+ @level = level
95
+ self
96
+ end
97
+
98
+ # Add cli options
99
+ #
100
+ # @param [OptionParser] parser
101
+ #
102
+ # @return [undefined]
103
+ #
104
+ # @api private
105
+ #
106
+ def add_options
107
+ parser.on('--rspec', 'kills mutations with rspec') do
108
+ @rspec = true
109
+ end
110
+
111
+ parser.on('--rspec-level LEVEL', 'set rspec expansion level') do |level|
112
+ @level = level.to_i
113
+ end
114
+ end
115
+
116
+ end # Rspec
117
+
118
+ # Abstract predicate builder
119
+ class Predicate < self
120
+
121
+ class Subject < self
122
+
123
+ # Initialize object
124
+ #
125
+ # @api private
126
+ #
127
+ # @return [undefined]
128
+ #
129
+ def initialize(*)
130
+ super
131
+ @predicates = []
132
+ end
133
+
134
+ # Return predicate
135
+ #
136
+ # @api private
137
+ #
138
+ def output
139
+ if @predicates.empty?
140
+ Mutant::Predicate::CONTRADICTION
141
+ else
142
+ Mutant::Predicate::Whitelist.new(@predicates)
143
+ end
144
+ end
145
+
146
+ private
147
+
148
+ # Add cli options
149
+ #
150
+ # @return [undefined]
151
+ #
152
+ # @api private
153
+ #
154
+ def add_options
155
+ parser.on('--ignore-subject MATCHER', 'ignores subjects that matches MATCHER') do |pattern|
156
+ add_pattern(pattern)
157
+ end
158
+ end
159
+
160
+ # Add matcher to predicates
161
+ #
162
+ # @param [String] pattern
163
+ #
164
+ # @api private
165
+ #
166
+ def add_pattern(pattern)
167
+ matcher = Classifier.run(@cache, pattern)
168
+ @predicates << Mutant::Predicate::Matcher.new(matcher)
169
+ end
170
+
171
+ end
172
+
173
+ end # Predicate
174
+
175
+ end # Builder
176
+ end # CLI
177
+ end # Mutant
@@ -6,7 +6,6 @@ module Mutant
6
6
 
7
7
  # Explicit method classifier
8
8
  class Method < self
9
- register
10
9
 
11
10
  TABLE = {
12
11
  '.' => Matcher::Methods::Singleton,
@@ -15,18 +14,13 @@ module Mutant
15
14
 
16
15
  REGEXP = /
17
16
  \A
18
- (#{SCOPE_PATTERN})
19
- ([.#])
20
- (#{METHOD_NAME_PATTERN})
17
+ (?<scope_name>#{SCOPE_PATTERN})
18
+ (?<scope_symbol>[.#])
19
+ (?<method_name>#{METHOD_NAME_PATTERN})
21
20
  \z
22
21
  /x.freeze
23
22
 
24
- # Positions of captured regexp groups
25
- SCOPE_NAME_POSITION = 1
26
- SCOPE_SYMBOL_POSITION = 2
27
- METHOD_NAME_POSITION = 3
28
-
29
- private
23
+ register(REGEXP)
30
24
 
31
25
  # Return method matcher
32
26
  #
@@ -39,6 +33,8 @@ module Mutant
39
33
  end
40
34
  memoize :matcher
41
35
 
36
+ private
37
+
42
38
  # Return method
43
39
  #
44
40
  # @return [Method, UnboundMethod]
@@ -50,7 +46,7 @@ module Mutant
50
46
  method.name == method_name
51
47
  end or raise NameError, "Cannot find method #{identifier}"
52
48
  end
53
- memoize :method, :freezer => :noop
49
+ memoize :method, freezer: :noop
54
50
 
55
51
  # Return scope name
56
52
  #
@@ -59,7 +55,7 @@ module Mutant
59
55
  # @api private
60
56
  #
61
57
  def scope_name
62
- match[SCOPE_NAME_POSITION]
58
+ match[__method__]
63
59
  end
64
60
 
65
61
  # Return scope
@@ -79,7 +75,7 @@ module Mutant
79
75
  # @api private
80
76
  #
81
77
  def method_name
82
- match[METHOD_NAME_POSITION].to_sym
78
+ match[__method__].to_sym
83
79
  end
84
80
 
85
81
  # Return scope symbol
@@ -89,7 +85,7 @@ module Mutant
89
85
  # @api private
90
86
  #
91
87
  def scope_symbol
92
- match[SCOPE_SYMBOL_POSITION]
88
+ match[__method__]
93
89
  end
94
90
 
95
91
  # Return matcher class
@@ -7,8 +7,6 @@ module Mutant
7
7
  # Namespace classifier
8
8
  class Namespace < self
9
9
 
10
- private
11
-
12
10
  # Return matcher
13
11
  #
14
12
  # @return [Matcher]
@@ -19,6 +17,8 @@ module Mutant
19
17
  self.class::MATCHER.new(cache, namespace)
20
18
  end
21
19
 
20
+ private
21
+
22
22
  # Return namespace
23
23
  #
24
24
  # @return [Class, Module]
@@ -26,23 +26,21 @@ module Mutant
26
26
  # @api private
27
27
  #
28
28
  def namespace
29
- Classifier.constant_lookup(match[1].to_s)
29
+ Classifier.constant_lookup(match[__method__].to_s)
30
30
  end
31
31
 
32
32
  # Recursive namespace classifier
33
33
  class Recursive < self
34
- REGEXP = /\A(#{SCOPE_PATTERN})\*\z/.freeze
34
+ REGEXP = /\A(?<namespace>#{SCOPE_PATTERN})\*\z/.freeze
35
35
  MATCHER = Matcher::Namespace
36
-
37
- register
36
+ register(REGEXP)
38
37
  end # Recursive
39
38
 
40
39
  # Recursive namespace classifier
41
40
  class Flat < self
42
- REGEXP = /\A(#{SCOPE_PATTERN})\z/.freeze
41
+ REGEXP = /\A(?<namespace>#{SCOPE_PATTERN})\z/.freeze
43
42
  MATCHER = Matcher::Scope
44
-
45
- register
43
+ register(REGEXP)
46
44
  end # Flat
47
45
 
48
46
  end # Namespace
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  class CLI
5
5
  # A classifier for input strings
6
- class Classifier < Matcher
6
+ class Classifier
7
7
  include AbstractType, Adamantium::Flat, Concord.new(:cache, :match)
8
8
 
9
9
  include Equalizer.new(:identifier)
@@ -22,7 +22,7 @@ module Mutant
22
22
  (?:#{SCOPE_OPERATOR}#{SCOPE_NAME_PATTERN})*
23
23
  /x.freeze
24
24
 
25
- REGISTRY = []
25
+ REGISTRY = {}
26
26
 
27
27
  # Register classifier
28
28
  #
@@ -30,8 +30,8 @@ module Mutant
30
30
  #
31
31
  # @api private
32
32
  #
33
- def self.register
34
- REGISTRY << self
33
+ def self.register(regexp)
34
+ REGISTRY[regexp] = self
35
35
  end
36
36
  private_class_method :register
37
37
 
@@ -54,47 +54,45 @@ module Mutant
54
54
 
55
55
  # Return matchers for input
56
56
  #
57
- # @return [Classifier]
57
+ # @param [Cache] cache
58
+ # @param [String] pattern
59
+ #
60
+ # @return [Matcher]
58
61
  # if a classifier handles the input
59
62
  #
60
- # @return [nil]
63
+ # @raise [RuntimeError]
61
64
  # otherwise
62
65
  #
63
66
  # @api private
64
67
  #
65
- def self.build(*arguments)
66
- classifiers = REGISTRY.map do |descendant|
67
- descendant.run(*arguments)
68
- end.compact
69
-
70
- raise if classifiers.length > 1
71
-
72
- classifiers.first
68
+ def self.run(cache, pattern)
69
+ matches = find(pattern)
70
+ case matches.length
71
+ when 0
72
+ raise Error, "No matcher handles: #{pattern.inspect}"
73
+ when 1
74
+ klass, match = matches.first
75
+ klass.new(cache, match).matcher
76
+ else
77
+ raise Error, "More than one matcher found for: #{pattern.inspect}"
78
+ end
73
79
  end
74
80
 
75
- # Run classifier
76
- #
77
- # @param [Cache] cache
81
+ # Find classifiers
78
82
  #
79
83
  # @param [String] input
80
84
  #
81
- # @return [Classifier]
82
- # if input is handled by classifier
83
- #
84
- # @return [nil]
85
- # otherwise
86
- #
87
85
  # @api private
88
86
  #
89
- def self.run(cache, input)
90
- match = self::REGEXP.match(input)
91
- return unless match
92
-
93
- new(cache, match)
87
+ def self.find(input)
88
+ REGISTRY.each_with_object([]) do |(regexp, klass), matches|
89
+ match = regexp.match(input)
90
+ if match
91
+ matches << [klass, match]
92
+ end
93
+ end
94
94
  end
95
-
96
- # No protected_class_method in ruby :(
97
- class << self; protected :run; end
95
+ private_class_method :find
98
96
 
99
97
  # Enumerate subjects
100
98
  #