mutant 0.9.14 → 0.10.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e92dbf6cbb84cf8d3d5354fc5994cb1ad7cb1fb1364b1927aa89abeedcd0cef
4
- data.tar.gz: 5c49178b6766424d6ded707a100ebad86cf9c5403a53b2c2c750baf69b4d636f
3
+ metadata.gz: fd38b14761b4836ca3ce54a9bd8ea7a25d1f4dac6506b08ab6f6c64227a0f71d
4
+ data.tar.gz: dbed2538feb3240195e76b8b10895ea01ba7dc827820a6106d00fa4a55da97d1
5
5
  SHA512:
6
- metadata.gz: e786a6ed55abf722515f3b4b9c08200dac35557e48c27430ae5683471ededf67d1fcb17ff4e5f57f1881765d10f3a8b00ef2993f163a1cceb93531d4b75a37e3
7
- data.tar.gz: 2a5840a2ecc0ca7c9c6156e8c4596b21808aaf8418ecf134fff9ada56a6c20e94b5ece60a0ebf0b321f1ed51ff0e5c92d26069bcb9473f61276973c46780eb5d
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)
@@ -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
  )
@@ -1,174 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mutant
4
- # Commandline parser / runner
5
- #
6
- # rubocop:disable Metrics/ClassLength
7
- class CLI
8
- include Concord.new(:world, :config)
9
-
10
- private_class_method :new
11
-
12
- OPTIONS =
13
- %i[
14
- add_environment_options
15
- add_mutation_options
16
- add_filter_options
17
- add_debug_options
18
- ].freeze
19
-
20
- private_constant(*constants(false))
21
-
22
- # Run cli with arguments
23
- #
24
- # @param [World] world
25
- # the outside world
26
- #
27
- # @param [Config] default_config
28
- # the default config
29
- #
30
- # @param [Array<String>]
31
- # the user provided arguments
32
- #
33
- # @return [Boolean]
34
- #
35
- # rubocop:disable Style/Semicolon
36
- #
37
- # ignore :reek:LongParameterList
38
- def self.run(world, default_config, arguments)
39
- License
40
- .apply(world)
41
- .bind { Config.load_config_file(world, default_config) }
42
- .bind { |file_config| apply(world, file_config, arguments) }
43
- .bind { |cli_config| Bootstrap.apply(world, cli_config) }
44
- .bind(&Runner.method(:apply))
45
- .from_right { |error| world.stderr.puts(error); return false }
46
- .success?
47
- end
48
- # rubocop:enable Style/Semicolon
49
-
50
- # Parse arguments into config
51
- #
52
- # @param [World] world
53
- # @param [Config] config
54
- # @param [Array<String>] arguments
55
- #
56
- # @return [Either<OptionParser::ParseError, Config>]
57
- #
58
- # ignore :reek:LongParameterList
59
- def self.apply(world, config, arguments)
60
- Either
61
- .wrap_error(OptionParser::ParseError) { new(world, config).parse(arguments) }
62
- .lmap(&:message)
63
- end
64
-
65
- # Local opt out of option parser defaults
66
- class OptionParser < ::OptionParser
67
- # Kill defaults added by option parser that
68
- # inference with ours under mutation testing.
69
- define_method(:add_officious) {}
70
- end # OptionParser
71
-
72
- # Parse the command-line options
4
+ # Commandline interface
5
+ module CLI
6
+ # Parse command
73
7
  #
74
- # @param [Array<String>] arguments
75
- # Command-line options and arguments to be parsed.
76
- #
77
- # @return [Config]
78
- def parse(arguments)
79
- opts = OptionParser.new do |builder|
80
- builder.banner = 'usage: mutant [options] MATCH_EXPRESSION ...'
81
- OPTIONS.each do |name|
82
- __send__(name, builder)
83
- end
84
- end
85
-
86
- parse_match_expressions(opts.parse!(arguments.dup))
87
-
88
- config
89
- end
90
-
91
- private
92
-
93
- def parse_match_expressions(expressions)
94
- expressions.each do |expression|
95
- add_matcher(:match_expressions, config.expression_parser.apply(expression).from_right)
96
- end
97
- end
98
-
99
- # rubocop:disable Metrics/MethodLength
100
- def add_environment_options(opts)
101
- opts.separator('Environment:')
102
- opts.on('--zombie', 'Run mutant zombified') do
103
- with(zombie: true)
104
- end
105
- opts.on('-I', '--include DIRECTORY', 'Add DIRECTORY to $LOAD_PATH') do |directory|
106
- add(:includes, directory)
107
- end
108
- opts.on('-r', '--require NAME', 'Require file with NAME') do |name|
109
- add(:requires, name)
110
- end
111
- opts.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
112
- with(jobs: Integer(number))
113
- end
114
- end
115
- # rubocop:enable Metrics/MethodLength
116
-
117
- def add_mutation_options(opts)
118
- opts.separator(nil)
119
- opts.separator('Options:')
120
-
121
- opts.on('--use INTEGRATION', 'Use INTEGRATION to kill mutations') do |name|
122
- with(integration: name)
123
- end
124
- end
125
-
126
- # rubocop:disable Metrics/MethodLength
127
- def add_filter_options(opts)
128
- opts.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
129
- add_matcher(:ignore_expressions, config.expression_parser.apply(pattern).from_right)
130
- end
131
- opts.on('--start-subject EXPRESSION', 'Start mutation testing at a specific subject') do |pattern|
132
- add_matcher(:start_expressions, config.expression_parser.apply(pattern).from_right)
133
- end
134
- opts.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
135
- add_matcher(
136
- :subject_filters,
137
- Repository::SubjectFilter.new(
138
- Repository::Diff.new(to: revision, world: world)
139
- )
140
- )
141
- end
142
- end
143
- # rubocop:enable Metrics/MethodLength
144
-
145
- # rubocop:disable Metrics/MethodLength
146
- def add_debug_options(opts)
147
- opts.on('--fail-fast', 'Fail fast') do
148
- with(fail_fast: true)
149
- end
150
- opts.on('--version', 'Print mutants version') do
151
- world.stdout.puts("mutant-#{VERSION}")
152
- world.kernel.exit
153
- end
154
- opts.on_tail('-h', '--help', 'Show this message') do
155
- world.stdout.puts(opts.to_s)
156
- world.kernel.exit
157
- end
158
- end
159
- # rubocop:enable Metrics/MethodLength
160
-
161
- def with(attributes)
162
- @config = config.with(attributes)
163
- end
164
-
165
- def add(attribute, value)
166
- with(attribute => config.public_send(attribute) + [value])
167
- end
168
-
169
- def add_matcher(attribute, value)
170
- with(matcher: config.matcher.add(attribute, value))
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) }
171
13
  end
172
14
  end # CLI
173
- # rubocop:enable Metrics/ClassLength
174
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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module CLI
5
+ class Command
6
+ class Root < self
7
+ SUBCOMMANDS = [Run, Subscription].freeze
8
+ SHORT_DESCRIPTION = 'mutation testing engine main command'
9
+ NAME = 'mutant'
10
+ end
11
+ end # Command
12
+ end # CLI
13
+ end # Mutant
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module CLI
5
+ class Command
6
+ # rubocop:disable Metrics/ClassLength
7
+ class Run < self
8
+ NAME = 'run'
9
+ SHORT_DESCRIPTION = 'Run code analysis'
10
+
11
+ OPTIONS =
12
+ %i[
13
+ add_environment_options
14
+ add_runner_options
15
+ add_integration_options
16
+ add_matcher_options
17
+ ].freeze
18
+
19
+ SLEEP = 40
20
+
21
+ UNLICENSED = <<~MESSAGE.lines.freeze
22
+ Soft fail, continuing in #{SLEEP} seconds
23
+ Next major version will enforce the license
24
+ See https://github.com/mbj/mutant#licensing
25
+ MESSAGE
26
+
27
+ # Test if command needs to be executed in zombie environment
28
+ #
29
+ # @return [Bool]
30
+ def zombie?
31
+ config.zombie
32
+ end
33
+
34
+ private
35
+
36
+ def execute
37
+ soft_fail(License.apply(world))
38
+ .bind { Config.load_config_file(world, config) }
39
+ .bind { |cli_config| Bootstrap.apply(world, cli_config) }
40
+ .bind(&Runner.public_method(:apply))
41
+ .from_right { |error| world.stderr.puts(error); return false }
42
+ .success?
43
+ end
44
+
45
+ def soft_fail(result)
46
+ result.either(
47
+ lambda do |message|
48
+ stderr = world.stderr
49
+ stderr.puts(message)
50
+ UNLICENSED.each { |line| stderr.puts(unlicensed(line)) }
51
+ world.kernel.sleep(SLEEP)
52
+ Either::Right.new(nil)
53
+ end,
54
+ ->(_subscription) { Either::Right.new(nil) }
55
+ )
56
+ end
57
+
58
+ def unlicensed(message)
59
+ "[Mutant-License-Error]: #{message}"
60
+ end
61
+
62
+ def parse_remaining_arguments(arguments)
63
+ traverse(config.expression_parser.public_method(:apply), arguments)
64
+ .fmap do |match_expressions|
65
+ matcher(match_expressions: match_expressions)
66
+ self
67
+ end
68
+ end
69
+
70
+ def traverse(action, values)
71
+ Either::Right.new(
72
+ values.map do |value|
73
+ action.call(value).from_right do |error|
74
+ return Either::Left.new(error)
75
+ end
76
+ end
77
+ )
78
+ end
79
+
80
+ def set(**attributes)
81
+ @config = config.with(attributes)
82
+ end
83
+
84
+ def matcher(**attributes)
85
+ set(matcher: config.matcher.with(attributes))
86
+ end
87
+
88
+ def add(attribute, value)
89
+ set(attribute => config.public_send(attribute) + [value])
90
+ end
91
+
92
+ def add_matcher(attribute, value)
93
+ set(matcher: config.matcher.add(attribute, value))
94
+ end
95
+
96
+ def add_environment_options(parser)
97
+ parser.separator('Environment:')
98
+ parser.on('--zombie', 'Run mutant zombified') do
99
+ set(zombie: true)
100
+ end
101
+ parser.on('-I', '--include DIRECTORY', 'Add DIRECTORY to $LOAD_PATH') do |directory|
102
+ add(:includes, directory)
103
+ end
104
+ parser.on('-r', '--require NAME', 'Require file with NAME') do |name|
105
+ add(:requires, name)
106
+ end
107
+ end
108
+
109
+ def add_integration_options(parser)
110
+ parser.separator('Integration:')
111
+
112
+ parser.on('--use INTEGRATION', 'Use INTEGRATION to kill mutations') do |name|
113
+ set(integration: name)
114
+ end
115
+ end
116
+
117
+ # rubocop:disable Metrics/MethodLength
118
+ def add_matcher_options(parser)
119
+ parser.separator('Matcher:')
120
+
121
+ parser.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
122
+ add_matcher(:ignore_expressions, config.expression_parser.apply(pattern).from_right)
123
+ end
124
+ parser.on('--start-subject EXPRESSION', 'Start mutation testing at a specific subject') do |pattern|
125
+ add_matcher(:start_expressions, config.expression_parser.apply(pattern).from_right)
126
+ end
127
+ parser.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
128
+ add_matcher(
129
+ :subject_filters,
130
+ Repository::SubjectFilter.new(
131
+ Repository::Diff.new(to: revision, world: world)
132
+ )
133
+ )
134
+ end
135
+ end
136
+
137
+ def add_runner_options(parser)
138
+ parser.separator('Runner:')
139
+
140
+ parser.on('--fail-fast', 'Fail fast') do
141
+ set(fail_fast: true)
142
+ end
143
+ parser.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
144
+ set(jobs: Integer(number))
145
+ end
146
+ end
147
+ end # Run
148
+ # rubocop:enable Metrics/ClassLength
149
+ end # Command
150
+ end # CLI
151
+ end # Mutant
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module CLI
5
+ class Command
6
+ class Subscription < self
7
+ NAME = 'subscription'
8
+ SHORT_DESCRIPTION = 'Subscription subcommands'
9
+
10
+ private
11
+
12
+ def license
13
+ License.apply(world)
14
+ end
15
+
16
+ class Test < self
17
+ NAME = 'test'
18
+ SUBCOMMANDS = [].freeze
19
+ SHORT_DESCRIPTION = 'Silently validates subscription, exits accordingly'
20
+
21
+ private
22
+
23
+ def execute
24
+ license.right?
25
+ end
26
+ end # Test
27
+
28
+ class Show < self
29
+ NAME = 'show'
30
+ SUBCOMMANDS = [].freeze
31
+ SHORT_DESCRIPTION = 'Show subscription status'
32
+
33
+ private
34
+
35
+ def execute
36
+ license.either(method(:unlicensed), method(:licensed))
37
+ end
38
+
39
+ def licensed(subscription)
40
+ world.stdout.puts(subscription.description)
41
+ true
42
+ end
43
+
44
+ def unlicensed(message)
45
+ world.stderr.puts(message)
46
+ false
47
+ end
48
+ end # Show
49
+
50
+ SUBCOMMANDS = [Show, Test].freeze
51
+ end # Subscription
52
+ end # Command
53
+ end # CLI
54
+ end # Mutant
@@ -59,6 +59,5 @@ module Mutant
59
59
  names = anima.attribute_names
60
60
  new(Hash[names.zip(names.map(&match.method(:[])))])
61
61
  end
62
-
63
62
  end # Expression
64
63
  end # Mutant
@@ -4,53 +4,27 @@ module Mutant
4
4
  module License
5
5
  NAME = 'mutant-license'
6
6
  VERSION = '~> 0.1.0'
7
- SLEEP = 40
8
-
9
- UNLICENSED =
10
- IceNine.deep_freeze(
11
- [
12
- "Soft fail, continuing in #{SLEEP} seconds",
13
- 'Next major version will enforce the license',
14
- 'See https://github.com/mbj/mutant#licensing'
15
- ]
16
- )
17
7
 
8
+ # Load license
9
+ #
10
+ # @param [World] world
11
+ #
12
+ # @return [Either<String,Subscription>]
13
+ #
14
+ # @api private
18
15
  def self.apply(world)
19
- soft_fail(world, license_result(world))
20
- end
21
-
22
- def self.license_result(world)
23
16
  load_mutant_license(world)
24
17
  .fmap { license_path(world) }
25
- .fmap { |path| Subscription.from_json(world.json.load(path)) }
26
- .bind { |sub| sub.apply(world) }
18
+ .bind { |path| Subscription.load(world, world.json.load(path)) }
27
19
  end
28
- private_class_method :license_result
29
-
30
- # ignore :reek:NestedIterators
31
- def self.soft_fail(world, result)
32
- result.lmap do |message|
33
- stderr = world.stderr
34
- stderr.puts(message)
35
- UNLICENSED.each { |line| stderr.puts(unlicensed(line)) }
36
- world.kernel.sleep(SLEEP)
37
- end
38
-
39
- Either::Right.new(true)
40
- end
41
- private_class_method :soft_fail
42
20
 
43
21
  def self.load_mutant_license(world)
44
22
  Either
45
23
  .wrap_error(LoadError) { world.gem_method.call(NAME, VERSION) }
46
24
  .lmap(&:message)
47
25
  .lmap(&method(:check_for_rubygems_mutant_license))
48
- .lmap(&method(:unlicensed))
49
- end
50
-
51
- def self.unlicensed(message)
52
- "[Mutant-License-Error]: #{message}"
53
26
  end
27
+ private_class_method :load_mutant_license
54
28
 
55
29
  def self.check_for_rubygems_mutant_license(message)
56
30
  if message.include?('already activated mutant-license-0.0.0')
@@ -3,8 +3,15 @@
3
3
  module Mutant
4
4
  module License
5
5
  class Subscription
6
+ include Concord.new(:licensed)
6
7
 
7
- MESSAGE_FORMAT = <<~'MESSAGE'
8
+ FORMAT = <<~'MESSAGE'
9
+ %<subscription_name>s subscription:
10
+ Licensed:
11
+ %<licensed>s
12
+ MESSAGE
13
+
14
+ FAILURE_FORMAT = <<~'MESSAGE'
8
15
  Can not validate %<subscription_name>s license.
9
16
  Licensed:
10
17
  %<expected>s
@@ -12,31 +19,46 @@ module Mutant
12
19
  %<actual>s
13
20
  MESSAGE
14
21
 
15
- def self.from_json(value)
22
+ # Load value into subscription
23
+ #
24
+ # @param [Object] value
25
+ #
26
+ # @return [Subscription]
27
+ def self.load(world, value)
16
28
  {
17
29
  'com' => Commercial,
18
30
  'oss' => Opensource
19
- }.fetch(value.fetch('type')).from_json(value.fetch('contents'))
31
+ }.fetch(value.fetch('type'))
32
+ .from_json(value.fetch('contents'))
33
+ .apply(world)
34
+ end
35
+
36
+ # Subscription self description
37
+ #
38
+ # @return [String]
39
+ def description
40
+ FORMAT % {
41
+ licensed: licensed.join("\n"),
42
+ subscription_name: subscription_name
43
+ }
20
44
  end
21
45
 
22
46
  private
23
47
 
24
48
  def failure(expected, actual)
25
- Either::Left.new(message(expected, actual))
49
+ Either::Left.new(failure_message(expected, actual))
26
50
  end
27
51
 
28
- # ignore :reek:UtilityFunction
29
52
  def success
30
- # masked by soft fail
31
- Either::Right.new(nil)
53
+ Either::Right.new(self)
32
54
  end
33
55
 
34
56
  def subscription_name
35
57
  self.class.name.split('::').last.downcase
36
58
  end
37
59
 
38
- def message(expected, actual)
39
- MESSAGE_FORMAT % {
60
+ def failure_message(expected, actual)
61
+ FAILURE_FORMAT % {
40
62
  actual: actual.any? ? actual.map(&:to_s).join("\n") : '[none]',
41
63
  expected: expected.map(&:to_s).join("\n"),
42
64
  subscription_name: subscription_name
@@ -4,8 +4,6 @@ module Mutant
4
4
  module License
5
5
  class Subscription
6
6
  class Commercial < self
7
- include Concord.new(:authors)
8
-
9
7
  class Author
10
8
  include Concord.new(:email)
11
9
 
@@ -20,10 +18,10 @@ module Mutant
20
18
  def apply(world)
21
19
  candidates = candidates(world)
22
20
 
23
- if (authors & candidates).any?
21
+ if (licensed & candidates).any?
24
22
  success
25
23
  else
26
- failure(authors, candidates)
24
+ failure(licensed, candidates)
27
25
  end
28
26
  end
29
27
 
@@ -4,8 +4,6 @@ module Mutant
4
4
  module License
5
5
  class Subscription
6
6
  class Opensource < self
7
- include Concord.new(:repositories)
8
-
9
7
  class Repository
10
8
  include Concord.new(:host, :path)
11
9
 
@@ -43,10 +41,12 @@ module Mutant
43
41
  private_class_method :parse_url
44
42
  end
45
43
 
46
- private_constant(*constants(false))
47
-
48
44
  def self.from_json(value)
49
- new(value.fetch('repositories').map(&Repository.method(:parse)))
45
+ new(
46
+ value
47
+ .fetch('repositories')
48
+ .map(&Repository.public_method(:parse))
49
+ )
50
50
  end
51
51
 
52
52
  def apply(world)
@@ -59,10 +59,10 @@ module Mutant
59
59
  private
60
60
 
61
61
  def check_subscription(actual)
62
- if (repositories.to_set & actual).any?
62
+ if (licensed.to_set & actual).any?
63
63
  success
64
64
  else
65
- failure(repositories, actual)
65
+ failure(licensed, actual)
66
66
  end
67
67
  end
68
68
 
@@ -18,7 +18,7 @@ module Mutant
18
18
  }
19
19
 
20
20
  MAP = IceNine.deep_freeze(
21
- Hash[map.map { |type, prefix| [type, [prefix, /^#{::Regexp.escape(prefix)}/]] }]
21
+ map.transform_values { |prefix| [prefix, /^#{::Regexp.escape(prefix)}/] }
22
22
  )
23
23
 
24
24
  handle(*MAP.keys)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.9.14'
5
+ VERSION = '0.10.0'
6
6
  end # Mutant
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mutant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.14
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Schirp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-16 00:00:00.000000000 Z
11
+ date: 2020-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: abstract_type
@@ -184,14 +184,14 @@ dependencies:
184
184
  requirements:
185
185
  - - "~>"
186
186
  - !ruby/object:Gem::Version
187
- version: 0.5.2
187
+ version: 0.5.3
188
188
  type: :runtime
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
- version: 0.5.2
194
+ version: 0.5.3
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: variable
197
197
  requirement: !ruby/object:Gem::Requirement
@@ -254,28 +254,28 @@ dependencies:
254
254
  requirements:
255
255
  - - "~>"
256
256
  - !ruby/object:Gem::Version
257
- version: 1.2.0
257
+ version: 1.3.0
258
258
  type: :development
259
259
  prerelease: false
260
260
  version_requirements: !ruby/object:Gem::Requirement
261
261
  requirements:
262
262
  - - "~>"
263
263
  - !ruby/object:Gem::Version
264
- version: 1.2.0
264
+ version: 1.3.0
265
265
  - !ruby/object:Gem::Dependency
266
266
  name: rubocop
267
267
  requirement: !ruby/object:Gem::Requirement
268
268
  requirements:
269
269
  - - "~>"
270
270
  - !ruby/object:Gem::Version
271
- version: 0.79.0
271
+ version: '1.0'
272
272
  type: :development
273
273
  prerelease: false
274
274
  version_requirements: !ruby/object:Gem::Requirement
275
275
  requirements:
276
276
  - - "~>"
277
277
  - !ruby/object:Gem::Version
278
- version: 0.79.0
278
+ version: '1.0'
279
279
  description: Mutation Testing for Ruby.
280
280
  email:
281
281
  - mbj@schirp-dso.com
@@ -303,6 +303,10 @@ files:
303
303
  - lib/mutant/ast/types.rb
304
304
  - lib/mutant/bootstrap.rb
305
305
  - lib/mutant/cli.rb
306
+ - lib/mutant/cli/command.rb
307
+ - lib/mutant/cli/command/root.rb
308
+ - lib/mutant/cli/command/run.rb
309
+ - lib/mutant/cli/command/subscription.rb
306
310
  - lib/mutant/config.rb
307
311
  - lib/mutant/context.rb
308
312
  - lib/mutant/env.rb
@@ -460,14 +464,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
460
464
  requirements:
461
465
  - - ">="
462
466
  - !ruby/object:Gem::Version
463
- version: '0'
467
+ version: '2.5'
464
468
  required_rubygems_version: !ruby/object:Gem::Requirement
465
469
  requirements:
466
470
  - - ">="
467
471
  - !ruby/object:Gem::Version
468
472
  version: '0'
469
473
  requirements: []
470
- rubygems_version: 3.1.4
474
+ rubygems_version: 3.2.0.rc.1
471
475
  signing_key:
472
476
  specification_version: 4
473
477
  summary: ''