mutant 0.9.10 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (254) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mutant +16 -11
  3. data/lib/mutant.rb +8 -4
  4. data/lib/mutant/bootstrap.rb +14 -1
  5. data/lib/mutant/cli.rb +9 -162
  6. data/lib/mutant/cli/command.rb +196 -0
  7. data/lib/mutant/cli/command/root.rb +13 -0
  8. data/lib/mutant/cli/command/run.rb +151 -0
  9. data/lib/mutant/cli/command/subscription.rb +54 -0
  10. data/lib/mutant/expression.rb +0 -1
  11. data/lib/mutant/isolation.rb +1 -1
  12. data/lib/mutant/isolation/fork.rb +2 -2
  13. data/lib/mutant/isolation/none.rb +1 -1
  14. data/lib/mutant/license.rb +9 -35
  15. data/lib/mutant/license/subscription.rb +31 -9
  16. data/lib/mutant/license/subscription/commercial.rb +2 -4
  17. data/lib/mutant/license/subscription/opensource.rb +7 -7
  18. data/lib/mutant/matcher/config.rb +2 -0
  19. data/lib/mutant/meta/example.rb +16 -4
  20. data/lib/mutant/meta/example/dsl.rb +33 -16
  21. data/lib/mutant/meta/example/verification.rb +70 -28
  22. data/lib/mutant/minitest/coverage.rb +53 -0
  23. data/lib/mutant/mutator/node.rb +2 -2
  24. data/lib/mutant/mutator/node/block_pass.rb +29 -0
  25. data/lib/mutant/mutator/node/{dstr.rb → dynamic_literal.rb} +7 -5
  26. data/lib/mutant/mutator/node/index.rb +4 -4
  27. data/lib/mutant/mutator/node/literal/range.rb +5 -2
  28. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +1 -1
  29. data/lib/mutant/mutator/node/noop.rb +1 -1
  30. data/lib/mutant/mutator/node/op_asgn.rb +15 -1
  31. data/lib/mutant/mutator/node/send.rb +25 -1
  32. data/lib/mutant/mutator/node/send/attribute_assignment.rb +1 -0
  33. data/lib/mutant/reporter/cli/printer/isolation_result.rb +9 -3
  34. data/lib/mutant/subject/method/instance.rb +1 -1
  35. data/lib/mutant/version.rb +1 -1
  36. metadata +18 -336
  37. data/.github/workflows/ci.yml +0 -121
  38. data/.gitignore +0 -38
  39. data/.rspec +0 -5
  40. data/.rubocop.yml +0 -210
  41. data/Changelog.md +0 -87
  42. data/Gemfile +0 -7
  43. data/Gemfile.lock +0 -107
  44. data/Gemfile.shared +0 -10
  45. data/README.md +0 -199
  46. data/Rakefile +0 -5
  47. data/config/devtools.yml +0 -2
  48. data/config/reek.yml +0 -139
  49. data/config/yardstick.yml +0 -2
  50. data/docs/commercial-support.md +0 -14
  51. data/docs/concurrency.md +0 -39
  52. data/docs/incremental.md +0 -76
  53. data/docs/known-problems.md +0 -30
  54. data/docs/limitations.md +0 -50
  55. data/docs/mutant-minitest.md +0 -149
  56. data/docs/mutant-rspec.md +0 -130
  57. data/docs/nomenclature.md +0 -82
  58. data/docs/reading-reports.md +0 -74
  59. data/lib/mutant/mutator/node/dsym.rb +0 -22
  60. data/meta/and.rb +0 -13
  61. data/meta/and_asgn.rb +0 -14
  62. data/meta/array.rb +0 -27
  63. data/meta/begin.rb +0 -20
  64. data/meta/block.rb +0 -199
  65. data/meta/block_pass.rb +0 -8
  66. data/meta/blockarg.rb +0 -10
  67. data/meta/break.rb +0 -9
  68. data/meta/case.rb +0 -217
  69. data/meta/casgn.rb +0 -25
  70. data/meta/cbase.rb +0 -8
  71. data/meta/class.rb +0 -12
  72. data/meta/const.rb +0 -17
  73. data/meta/csend.rb +0 -10
  74. data/meta/cvar.rb +0 -7
  75. data/meta/cvasgn.rb +0 -9
  76. data/meta/date.rb +0 -59
  77. data/meta/def.rb +0 -196
  78. data/meta/defined.rb +0 -9
  79. data/meta/dstr.rb +0 -13
  80. data/meta/dsym.rb +0 -14
  81. data/meta/ensure.rb +0 -8
  82. data/meta/false.rb +0 -7
  83. data/meta/file.rb +0 -5
  84. data/meta/float.rb +0 -37
  85. data/meta/gvar.rb +0 -7
  86. data/meta/gvasgn.rb +0 -9
  87. data/meta/hash.rb +0 -20
  88. data/meta/if.rb +0 -72
  89. data/meta/index.rb +0 -133
  90. data/meta/indexasgn.rb +0 -31
  91. data/meta/int.rb +0 -18
  92. data/meta/ivar.rb +0 -8
  93. data/meta/ivasgn.rb +0 -22
  94. data/meta/kwarg.rb +0 -10
  95. data/meta/kwbegin.rb +0 -8
  96. data/meta/kwoptarg.rb +0 -13
  97. data/meta/lambda.rb +0 -23
  98. data/meta/line.rb +0 -5
  99. data/meta/lvar.rb +0 -16
  100. data/meta/lvasgn.rb +0 -24
  101. data/meta/masgn.rb +0 -7
  102. data/meta/match_current_line.rb +0 -14
  103. data/meta/next.rb +0 -10
  104. data/meta/nil.rb +0 -5
  105. data/meta/nthref.rb +0 -14
  106. data/meta/op_assgn.rb +0 -17
  107. data/meta/or.rb +0 -13
  108. data/meta/or_asgn.rb +0 -50
  109. data/meta/range.rb +0 -65
  110. data/meta/redo.rb +0 -5
  111. data/meta/regexp.rb +0 -80
  112. data/meta/regopt.rb +0 -10
  113. data/meta/rescue.rb +0 -84
  114. data/meta/return.rb +0 -16
  115. data/meta/sclass.rb +0 -12
  116. data/meta/self.rb +0 -7
  117. data/meta/send.rb +0 -600
  118. data/meta/str.rb +0 -7
  119. data/meta/super.rb +0 -27
  120. data/meta/sym.rb +0 -8
  121. data/meta/true.rb +0 -7
  122. data/meta/until.rb +0 -16
  123. data/meta/while.rb +0 -24
  124. data/meta/yield.rb +0 -9
  125. data/mutant-minitest.gemspec +0 -22
  126. data/mutant-rspec.gemspec +0 -22
  127. data/mutant.gemspec +0 -44
  128. data/mutant.sh +0 -12
  129. data/mutant.yml +0 -6
  130. data/spec/integration/mutant/corpus_spec.rb +0 -15
  131. data/spec/integration/mutant/isolation/fork_spec.rb +0 -28
  132. data/spec/integration/mutant/minitest_spec.rb +0 -11
  133. data/spec/integration/mutant/null_spec.rb +0 -16
  134. data/spec/integration/mutant/rspec_spec.rb +0 -15
  135. data/spec/integration/mutant/test_mutator_handles_types_spec.rb +0 -9
  136. data/spec/integrations.yml +0 -63
  137. data/spec/shared/framework_integration_behavior.rb +0 -70
  138. data/spec/shared/method_matcher_behavior.rb +0 -47
  139. data/spec/spec_helper.rb +0 -90
  140. data/spec/support/corpus.rb +0 -318
  141. data/spec/support/file_system.rb +0 -62
  142. data/spec/support/ice_nine_config.rb +0 -10
  143. data/spec/support/ruby_vm.rb +0 -84
  144. data/spec/support/shared_context.rb +0 -169
  145. data/spec/support/xspec.rb +0 -183
  146. data/spec/unit/mutant/ast/find_metaclass_containing_spec.rb +0 -64
  147. data/spec/unit/mutant/ast/meta/optarg_spec.rb +0 -24
  148. data/spec/unit/mutant/ast/meta/send/proc_predicate_spec.rb +0 -30
  149. data/spec/unit/mutant/ast/meta/send/receiver_possible_top_level_const_predicate_spec.rb +0 -39
  150. data/spec/unit/mutant/ast/meta/send_spec.rb +0 -42
  151. data/spec/unit/mutant/ast/named_children_spec.rb +0 -89
  152. data/spec/unit/mutant/ast/sexp_spec.rb +0 -38
  153. data/spec/unit/mutant/ast_spec.rb +0 -57
  154. data/spec/unit/mutant/bootstrap_spec.rb +0 -216
  155. data/spec/unit/mutant/cli_spec.rb +0 -305
  156. data/spec/unit/mutant/clock_monotonic_spec.rb +0 -52
  157. data/spec/unit/mutant/config_spec.rb +0 -126
  158. data/spec/unit/mutant/context_spec.rb +0 -111
  159. data/spec/unit/mutant/env_spec.rb +0 -229
  160. data/spec/unit/mutant/expression/method_spec.rb +0 -62
  161. data/spec/unit/mutant/expression/methods_spec.rb +0 -66
  162. data/spec/unit/mutant/expression/namespace/exact_spec.rb +0 -28
  163. data/spec/unit/mutant/expression/namespace/recursive_spec.rb +0 -66
  164. data/spec/unit/mutant/expression/parser_spec.rb +0 -65
  165. data/spec/unit/mutant/expression_spec.rb +0 -45
  166. data/spec/unit/mutant/integration/rspec_spec.rb +0 -201
  167. data/spec/unit/mutant/integration_spec.rb +0 -150
  168. data/spec/unit/mutant/isolation/fork_spec.rb +0 -309
  169. data/spec/unit/mutant/isolation/none_spec.rb +0 -23
  170. data/spec/unit/mutant/isolation/result_spec.rb +0 -73
  171. data/spec/unit/mutant/license_spec.rb +0 -305
  172. data/spec/unit/mutant/loader_spec.rb +0 -79
  173. data/spec/unit/mutant/matcher/chain_spec.rb +0 -26
  174. data/spec/unit/mutant/matcher/compiler_spec.rb +0 -0
  175. data/spec/unit/mutant/matcher/config_spec.rb +0 -47
  176. data/spec/unit/mutant/matcher/filter_spec.rb +0 -22
  177. data/spec/unit/mutant/matcher/method/instance_spec.rb +0 -164
  178. data/spec/unit/mutant/matcher/method/metaclass_spec.rb +0 -108
  179. data/spec/unit/mutant/matcher/method/singleton_spec.rb +0 -90
  180. data/spec/unit/mutant/matcher/methods/instance_spec.rb +0 -54
  181. data/spec/unit/mutant/matcher/methods/metaclass_spec.rb +0 -62
  182. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +0 -51
  183. data/spec/unit/mutant/matcher/namespace_spec.rb +0 -39
  184. data/spec/unit/mutant/matcher/null_spec.rb +0 -12
  185. data/spec/unit/mutant/matcher/scope_spec.rb +0 -45
  186. data/spec/unit/mutant/matcher/static_spec.rb +0 -13
  187. data/spec/unit/mutant/matcher_spec.rb +0 -102
  188. data/spec/unit/mutant/meta/example/dsl_spec.rb +0 -108
  189. data/spec/unit/mutant/meta/example/verification_spec.rb +0 -154
  190. data/spec/unit/mutant/meta/example_spec.rb +0 -34
  191. data/spec/unit/mutant/mutation_spec.rb +0 -140
  192. data/spec/unit/mutant/mutator/node_spec.rb +0 -47
  193. data/spec/unit/mutant/mutator_spec.rb +0 -21
  194. data/spec/unit/mutant/parallel/driver_spec.rb +0 -126
  195. data/spec/unit/mutant/parallel/source/array_spec.rb +0 -57
  196. data/spec/unit/mutant/parallel/worker_spec.rb +0 -206
  197. data/spec/unit/mutant/parallel_spec.rb +0 -115
  198. data/spec/unit/mutant/parser_spec.rb +0 -26
  199. data/spec/unit/mutant/range_spec.rb +0 -141
  200. data/spec/unit/mutant/registry_spec.rb +0 -74
  201. data/spec/unit/mutant/reporter/cli/printer/config_spec.rb +0 -17
  202. data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +0 -85
  203. data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +0 -45
  204. data/spec/unit/mutant/reporter/cli/printer/isolation_result_spec.rb +0 -132
  205. data/spec/unit/mutant/reporter/cli/printer/mutation_progress_result_spec.rb +0 -25
  206. data/spec/unit/mutant/reporter/cli/printer/mutation_result_spec.rb +0 -153
  207. data/spec/unit/mutant/reporter/cli/printer/status_progressive_spec.rb +0 -45
  208. data/spec/unit/mutant/reporter/cli/printer/subject_progress_spec.rb +0 -36
  209. data/spec/unit/mutant/reporter/cli/printer/subject_result_spec.rb +0 -44
  210. data/spec/unit/mutant/reporter/cli/printer/test_result_spec.rb +0 -16
  211. data/spec/unit/mutant/reporter/cli/printer_spec.rb +0 -163
  212. data/spec/unit/mutant/reporter/cli_spec.rb +0 -137
  213. data/spec/unit/mutant/reporter/null_spec.rb +0 -14
  214. data/spec/unit/mutant/reporter/sequence_spec.rb +0 -31
  215. data/spec/unit/mutant/repository/diff/ranges_spec.rb +0 -180
  216. data/spec/unit/mutant/repository/diff_spec.rb +0 -122
  217. data/spec/unit/mutant/repository/subject_filter_spec.rb +0 -30
  218. data/spec/unit/mutant/require_highjack_spec.rb +0 -73
  219. data/spec/unit/mutant/result/class_methods_spec.rb +0 -51
  220. data/spec/unit/mutant/result/env_spec.rb +0 -161
  221. data/spec/unit/mutant/result/mutation_spec.rb +0 -70
  222. data/spec/unit/mutant/result/subject_spec.rb +0 -111
  223. data/spec/unit/mutant/result/test_spec.rb +0 -14
  224. data/spec/unit/mutant/result_spec.rb +0 -33
  225. data/spec/unit/mutant/runner/sink_spec.rb +0 -174
  226. data/spec/unit/mutant/runner_spec.rb +0 -121
  227. data/spec/unit/mutant/selector/expression_spec.rb +0 -62
  228. data/spec/unit/mutant/selector/null_spec.rb +0 -17
  229. data/spec/unit/mutant/subject/method/instance_spec.rb +0 -276
  230. data/spec/unit/mutant/subject/method/metaclass_spec.rb +0 -63
  231. data/spec/unit/mutant/subject/method/singleton_spec.rb +0 -61
  232. data/spec/unit/mutant/subject_spec.rb +0 -93
  233. data/spec/unit/mutant/transform/array_spec.rb +0 -92
  234. data/spec/unit/mutant/transform/bool_spec.rb +0 -63
  235. data/spec/unit/mutant/transform/error_spec.rb +0 -132
  236. data/spec/unit/mutant/transform/exception_spec.rb +0 -44
  237. data/spec/unit/mutant/transform/hash_spec.rb +0 -236
  238. data/spec/unit/mutant/transform/index_spec.rb +0 -92
  239. data/spec/unit/mutant/transform/named_spec.rb +0 -49
  240. data/spec/unit/mutant/transform/primitive_spec.rb +0 -56
  241. data/spec/unit/mutant/transform/sequence_spec.rb +0 -98
  242. data/spec/unit/mutant/util/one_spec.rb +0 -22
  243. data/spec/unit/mutant/warnings_spec.rb +0 -89
  244. data/spec/unit/mutant/world_spec.rb +0 -63
  245. data/spec/unit/mutant/zombifier_spec.rb +0 -122
  246. data/test_app/.rspec +0 -1
  247. data/test_app/Gemfile.minitest +0 -6
  248. data/test_app/Gemfile.rspec3.8 +0 -7
  249. data/test_app/lib/test_app.rb +0 -114
  250. data/test_app/lib/test_app/literal.rb +0 -35
  251. data/test_app/lib/test_app/metaclasses.rb +0 -108
  252. data/test_app/spec/spec_helper.rb +0 -9
  253. data/test_app/spec/unit/test_app/literal_spec.rb +0 -20
  254. data/test_app/test/unit/test_app/literal_test.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45484c558fad8e5115eb2b4c45712b762eb1d13f12fe7e7a29d9ec0696a16e24
4
- data.tar.gz: 5354afd8cf7491687dfef3af68157e37cc549a0190d09bb8755c8bcf33d24051
3
+ metadata.gz: fd38b14761b4836ca3ce54a9bd8ea7a25d1f4dac6506b08ab6f6c64227a0f71d
4
+ data.tar.gz: dbed2538feb3240195e76b8b10895ea01ba7dc827820a6106d00fa4a55da97d1
5
5
  SHA512:
6
- metadata.gz: 1546be8318d6135d2834120bcc3052b879d6568560a9ec5dd056448a46cdd240fede609a4336fb5e64937526aa2db64627285c70dc45b09aa0175d6abc471749
7
- data.tar.gz: b8705cb2eb82b286d77ea87a17af903661747cfad61d3e51fcc70b1f906bcf47b07e33305810f1d59cc4f3d2e802b5eae20e0ce4ecf37b686c2fd73e1bc41933
6
+ metadata.gz: c70d6881956ace0d359b13dcfd41ef4eb92dbabcf5ee90d913790b8a09bb1b12877e32c85453d3be97d9fd2a1523a0af307ebf7318550cd0e3413fc8d9ecb765
7
+ data.tar.gz: 27b59ee208993bfc5b128269658fc4465782052613b0641c9647ace34cc10b956c72c8e05a5b8a6f86af77112589295e3289e53192d973500fe43ceed84180fc
data/bin/mutant CHANGED
@@ -8,8 +8,14 @@ end
8
8
 
9
9
  require 'mutant'
10
10
 
11
- namespace =
12
- if ARGV.include?('--zombie')
11
+ command = Mutant::CLI.parse(
12
+ arguments: ARGV,
13
+ config: Mutant::Config::DEFAULT,
14
+ world: Mutant::WORLD
15
+ )
16
+
17
+ status =
18
+ if command.zombie?
13
19
  $stderr.puts('Running mutant zombified!')
14
20
  Mutant::Zombifier.call(
15
21
  namespace: :Zombie,
@@ -31,15 +37,14 @@ namespace =
31
37
  concord
32
38
  ]
33
39
  )
34
- Zombie::Mutant
40
+
41
+ Zombie::Mutant::CLI.parse(
42
+ arguments: ARGV,
43
+ config: Zombie::Mutant::Config::DEFAULT,
44
+ world: Zombie::Mutant::WORLD
45
+ ).call
35
46
  else
36
- Mutant
47
+ command.call
37
48
  end
38
49
 
39
- Kernel.exit(
40
- namespace::CLI.run(
41
- namespace::WORLD,
42
- namespace::Config::DEFAULT,
43
- ARGV
44
- )
45
- )
50
+ Kernel.exit(status)
@@ -90,8 +90,7 @@ require 'mutant/mutator/node/arguments'
90
90
  require 'mutant/mutator/node/begin'
91
91
  require 'mutant/mutator/node/binary'
92
92
  require 'mutant/mutator/node/const'
93
- require 'mutant/mutator/node/dstr'
94
- require 'mutant/mutator/node/dsym'
93
+ require 'mutant/mutator/node/dynamic_literal'
95
94
  require 'mutant/mutator/node/kwbegin'
96
95
  require 'mutant/mutator/node/named_value/access'
97
96
  require 'mutant/mutator/node/named_value/constant_assignment'
@@ -120,6 +119,7 @@ require 'mutant/mutator/node/nthref'
120
119
  require 'mutant/mutator/node/masgn'
121
120
  require 'mutant/mutator/node/return'
122
121
  require 'mutant/mutator/node/block'
122
+ require 'mutant/mutator/node/block_pass'
123
123
  require 'mutant/mutator/node/if'
124
124
  require 'mutant/mutator/node/case'
125
125
  require 'mutant/mutator/node/splat'
@@ -165,6 +165,10 @@ require 'mutant/selector/expression'
165
165
  require 'mutant/selector/null'
166
166
  require 'mutant/config'
167
167
  require 'mutant/cli'
168
+ require 'mutant/cli/command'
169
+ require 'mutant/cli/command/run'
170
+ require 'mutant/cli/command/subscription'
171
+ require 'mutant/cli/command/root'
168
172
  require 'mutant/runner'
169
173
  require 'mutant/runner/sink'
170
174
  require 'mutant/result'
@@ -211,8 +215,8 @@ module Mutant
211
215
  open3: Open3,
212
216
  pathname: Pathname,
213
217
  process: Process,
214
- stderr: STDERR,
215
- stdout: STDOUT,
218
+ stderr: $stderr,
219
+ stdout: $stdout,
216
220
  thread: Thread,
217
221
  warnings: Warnings.new(Warning)
218
222
  )
@@ -36,7 +36,7 @@ module Mutant
36
36
  .tap(&method(:infect))
37
37
  .with(matchable_scopes: matchable_scopes(world, config))
38
38
 
39
- subjects = Matcher.from_config(env.config.matcher).call(env)
39
+ subjects = start_subject(env, Matcher.from_config(env.config.matcher).call(env))
40
40
 
41
41
  Integration.setup(env).fmap do |integration|
42
42
  env.with(
@@ -49,6 +49,19 @@ module Mutant
49
49
  end
50
50
  # rubocop:enable Metrics/MethodLength
51
51
 
52
+ def self.start_subject(env, subjects)
53
+ start_expressions = env.config.matcher.start_expressions
54
+
55
+ return subjects if start_expressions.empty?
56
+
57
+ subjects.drop_while do |subject|
58
+ start_expressions.none? do |expression|
59
+ expression.prefix?(subject.expression)
60
+ end
61
+ end
62
+ end
63
+ private_class_method :start_subject
64
+
52
65
  def self.infect(env)
53
66
  config, world = env.config, env.world
54
67
 
@@ -1,168 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mutant
4
- # Commandline parser / runner
5
- class CLI
6
- include Concord.new(:world, :config)
7
-
8
- private_class_method :new
9
-
10
- OPTIONS =
11
- %i[
12
- add_environment_options
13
- add_mutation_options
14
- add_filter_options
15
- add_debug_options
16
- ].freeze
17
-
18
- private_constant(*constants(false))
19
-
20
- # Run cli with arguments
21
- #
22
- # @param [World] world
23
- # the outside world
24
- #
25
- # @param [Config] default_config
26
- # the default config
27
- #
28
- # @param [Array<String>]
29
- # the user provided arguments
30
- #
31
- # @return [Boolean]
32
- #
33
- # rubocop:disable Style/Semicolon
34
- #
35
- # ignore :reek:LongParameterList
36
- def self.run(world, default_config, arguments)
37
- License
38
- .apply(world)
39
- .bind { Config.load_config_file(world, default_config) }
40
- .bind { |file_config| apply(world, file_config, arguments) }
41
- .bind { |cli_config| Bootstrap.apply(world, cli_config) }
42
- .bind(&Runner.method(:apply))
43
- .from_right { |error| world.stderr.puts(error); return false }
44
- .success?
45
- end
46
- # rubocop:enable Style/Semicolon
47
-
48
- # Parse arguments into config
49
- #
50
- # @param [World] world
51
- # @param [Config] config
52
- # @param [Array<String>] arguments
53
- #
54
- # @return [Either<OptionParser::ParseError, Config>]
55
- #
56
- # ignore :reek:LongParameterList
57
- def self.apply(world, config, arguments)
58
- Either
59
- .wrap_error(OptionParser::ParseError) { new(world, config).parse(arguments) }
60
- .lmap(&:message)
61
- end
62
-
63
- # Local opt out of option parser defaults
64
- class OptionParser < ::OptionParser
65
- # Kill defaults added by option parser that
66
- # inference with ours under mutation testing.
67
- define_method(:add_officious) {}
68
- end # OptionParser
69
-
70
- # Parse the command-line options
71
- #
72
- # @param [Array<String>] arguments
73
- # Command-line options and arguments to be parsed.
74
- #
75
- # @return [Config]
76
- def parse(arguments)
77
- opts = OptionParser.new do |builder|
78
- builder.banner = 'usage: mutant [options] MATCH_EXPRESSION ...'
79
- OPTIONS.each do |name|
80
- __send__(name, builder)
81
- end
82
- end
83
-
84
- parse_match_expressions(opts.parse!(arguments.dup))
85
-
86
- config
87
- end
88
-
89
- private
90
-
91
- def parse_match_expressions(expressions)
92
- expressions.each do |expression|
93
- add_matcher(:match_expressions, config.expression_parser.apply(expression).from_right)
94
- end
95
- end
96
-
97
- # rubocop:disable Metrics/MethodLength
98
- def add_environment_options(opts)
99
- opts.separator('Environment:')
100
- opts.on('--zombie', 'Run mutant zombified') do
101
- with(zombie: true)
102
- end
103
- opts.on('-I', '--include DIRECTORY', 'Add DIRECTORY to $LOAD_PATH') do |directory|
104
- add(:includes, directory)
105
- end
106
- opts.on('-r', '--require NAME', 'Require file with NAME') do |name|
107
- add(:requires, name)
108
- end
109
- opts.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
110
- with(jobs: Integer(number))
111
- end
112
- end
113
- # rubocop:enable Metrics/MethodLength
114
-
115
- def add_mutation_options(opts)
116
- opts.separator(nil)
117
- opts.separator('Options:')
118
-
119
- opts.on('--use INTEGRATION', 'Use INTEGRATION to kill mutations') do |name|
120
- with(integration: name)
121
- end
122
- end
123
-
124
- # rubocop:disable Metrics/MethodLength
125
- def add_filter_options(opts)
126
- opts.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
127
- add_matcher(:ignore_expressions, config.expression_parser.apply(pattern).from_right)
128
- end
129
- opts.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
130
- add_matcher(
131
- :subject_filters,
132
- Repository::SubjectFilter.new(
133
- Repository::Diff.new(to: revision, world: world)
134
- )
135
- )
136
- end
137
- end
138
- # rubocop:enable Metrics/MethodLength
139
-
140
- # rubocop:disable Metrics/MethodLength
141
- def add_debug_options(opts)
142
- opts.on('--fail-fast', 'Fail fast') do
143
- with(fail_fast: true)
144
- end
145
- opts.on('--version', 'Print mutants version') do
146
- world.stdout.puts("mutant-#{VERSION}")
147
- world.kernel.exit
148
- end
149
- opts.on_tail('-h', '--help', 'Show this message') do
150
- world.stdout.puts(opts.to_s)
151
- world.kernel.exit
152
- end
153
- end
154
- # rubocop:enable Metrics/MethodLength
155
-
156
- def with(attributes)
157
- @config = config.with(attributes)
158
- end
159
-
160
- def add(attribute, value)
161
- with(attribute => config.public_send(attribute) + [value])
162
- end
163
-
164
- def add_matcher(attribute, value)
165
- with(matcher: config.matcher.add(attribute, value))
4
+ # Commandline interface
5
+ module CLI
6
+ # Parse command
7
+ #
8
+ # @return [Command]
9
+ def self.parse(world:, **attributes)
10
+ Command::Root
11
+ .parse(world: world, **attributes)
12
+ .from_right { |message| Command::FailParse.new(world, message) }
166
13
  end
167
14
  end # CLI
168
15
  end # Mutant
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module CLI
5
+ # rubocop:disable Metrics/ClassLength
6
+ class Command
7
+ include AbstractType, Anima.new(:world, :config, :main, :parent, :arguments)
8
+
9
+ include Equalizer.new(:parent, :arguments)
10
+
11
+ OPTIONS = [].freeze
12
+ SUBCOMMANDS = [].freeze
13
+
14
+ # Local opt out of option parser defaults
15
+ class OptionParser < ::OptionParser
16
+ # Kill defaults added by option parser that
17
+ # inference with ours under mutation testing.
18
+ define_method(:add_officious) {}
19
+ end # OptionParser
20
+
21
+ class FailParse < self
22
+ include Concord.new(:world, :message)
23
+
24
+ def call
25
+ world.stderr.puts(message)
26
+ false
27
+ end
28
+ end
29
+
30
+ # Parse command
31
+ #
32
+ # @return [Command]
33
+ def self.parse(**attributes)
34
+ new(main: nil, parent: nil, **attributes).__send__(:parse)
35
+ end
36
+
37
+ # Command name
38
+ #
39
+ # @return [String]
40
+ def self.command_name
41
+ self::NAME
42
+ end
43
+
44
+ # Command short description
45
+ #
46
+ # @return [String]
47
+ def self.short_description
48
+ self::SHORT_DESCRIPTION
49
+ end
50
+
51
+ # Execute the command, invoke its side effects
52
+ #
53
+ # @return [Bool]
54
+ def call
55
+ main ? main.call : execute
56
+ end
57
+
58
+ # Commands full name
59
+ #
60
+ # @return [String]
61
+ def full_name
62
+ [*parent&.full_name, self.class.command_name].join(' ')
63
+ end
64
+
65
+ # Test if command needs to be executed in zombie environment
66
+ #
67
+ # @return [Bool]
68
+ def zombie?
69
+ instance_of?(Run)
70
+ end
71
+
72
+ private
73
+
74
+ def subcommands
75
+ self.class::SUBCOMMANDS
76
+ end
77
+
78
+ def parser
79
+ OptionParser.new do |parser|
80
+ parser.banner = "usage: #{banner}"
81
+
82
+ add_summary(parser)
83
+ add_global_options(parser)
84
+ add_subcommands(parser)
85
+
86
+ self.class::OPTIONS.each do |method_name|
87
+ 2.times { parser.separator(nil) }
88
+ __send__(method_name, parser)
89
+ end
90
+ end
91
+ end
92
+
93
+ def capture_main(&block)
94
+ @main = block
95
+ end
96
+
97
+ def banner
98
+ if subcommands.any?
99
+ "#{full_name} <#{subcommands.map(&:command_name).join('|')}> [options]"
100
+ else
101
+ "#{full_name} [options]"
102
+ end
103
+ end
104
+
105
+ def parse
106
+ Either
107
+ .wrap_error(OptionParser::InvalidOption) { parser.order(arguments) }
108
+ .lmap { |error| "#{full_name}: #{error}" }
109
+ .bind(&method(:parse_remaining))
110
+ end
111
+
112
+ def add_summary(parser)
113
+ parser.separator(nil)
114
+ parser.separator("Summary: #{self.class.short_description}")
115
+ parser.separator(nil)
116
+ end
117
+
118
+ def add_global_options(parser)
119
+ parser.separator('Global Options:')
120
+ parser.separator(nil)
121
+
122
+ parser.on('--help', 'Print help') do
123
+ capture_main { world.stdout.puts(parser.help); true }
124
+ end
125
+
126
+ parser.on('--version', 'Print mutants version') do
127
+ capture_main { world.stdout.puts("mutant-#{VERSION}"); true }
128
+ end
129
+ end
130
+
131
+ def add_subcommands(parser)
132
+ return unless subcommands.any?
133
+
134
+ parser.separator(nil)
135
+ parser.separator('Available subcommands:')
136
+ parser.separator(nil)
137
+ parser.separator(format_subcommands)
138
+ end
139
+
140
+ def parse_remaining(remaining)
141
+ return Either::Right.new(self) if main
142
+
143
+ if subcommands.any?
144
+ parse_subcommand(remaining)
145
+ else
146
+ parse_remaining_arguments(remaining)
147
+ end
148
+ end
149
+
150
+ def parse_remaining_arguments(remaining)
151
+ if remaining.any?
152
+ Either::Left.new("#{full_name}: Does not expect extra arguments")
153
+ else
154
+ Either::Right.new(self)
155
+ end
156
+ end
157
+
158
+ def parse_subcommand(arguments)
159
+ command_name, *arguments = arguments
160
+
161
+ if command_name.nil?
162
+ Either::Left.new(
163
+ "Missing required subcommand!\n\n#{parser}"
164
+ )
165
+ else
166
+ find_command(command_name).bind do |command|
167
+ command.parse(**to_h, parent: self, arguments: arguments)
168
+ end
169
+ end
170
+ end
171
+
172
+ def format_subcommands
173
+ commands = subcommands.map do |subcommand|
174
+ [subcommand.command_name, subcommand.short_description]
175
+ end.to_h
176
+
177
+ width = commands.each_key.map(&:length).max
178
+
179
+ commands.each_key.map do |name|
180
+ '%-*s - %s' % [width, name, commands.fetch(name)] # rubocop:disable Style/FormatStringToken
181
+ end
182
+ end
183
+
184
+ def find_command(name)
185
+ subcommand = subcommands.detect { |command| command.command_name.eql?(name) }
186
+
187
+ if subcommand
188
+ Either::Right.new(subcommand)
189
+ else
190
+ Either::Left.new("#{full_name}: Cannot find subcommand #{name.inspect}")
191
+ end
192
+ end
193
+ end # Command
194
+ # rubocop:enable Metrics/ClassLength
195
+ end # CLI
196
+ end # Mutant