Module: SimpleCov::CLI::Clean

Extended by:
Clean, CommandHelpers
Included in:
Clean
Defined in:
lib/simplecov/cli/clean.rb

Overview

simplecov clean [--dry-run]: remove the coverage report directory. --dry-run prints what would be deleted without touching disk.

Constant Summary

Constants included from CommandHelpers

SimpleCov::CLI::CommandHelpers::STATS_ROW_FORMAT

Instance Method Summary collapse

Methods included from CommandHelpers

build_parser, command_name, common_options, error, error_nil, on_help, one?, parse_common, quiet_option, recorded_contexts, stats_row

Instance Method Details

#announce(stdout, opts, message) ⇒ Object



42
43
44
# File 'lib/simplecov/cli/clean.rb', line 42

def announce(stdout, opts, message)
  stdout.puts("simplecov #{command_name}: #{message}") unless opts.fetch(:quiet)
end

#canonical_path(path) ⇒ Object



70
71
72
73
74
# File 'lib/simplecov/cli/clean.rb', line 70

def canonical_path(path)
  File.realpath(path)
rescue SystemCallError
  File.expand_path(path)
end

#descendant_of?(path, possible_ancestor) ⇒ Boolean

Canonical paths only carry a trailing separator on a filesystem root, and roots are refused before this check runs.

Returns:

  • (Boolean)


66
67
68
# File 'lib/simplecov/cli/clean.rb', line 66

def descendant_of?(path, possible_ancestor)
  path.start_with?("#{possible_ancestor}#{File::SEPARATOR}")
end

#entry_count(dir) ⇒ Object

FNM_DOTMATCH so the dotfiles rm_rf will actually delete count too. The glob still yields the directory's own "." entry, which rm_rf does not remove separately.



38
39
40
# File 'lib/simplecov/cli/clean.rb', line 38

def entry_count(dir)
  Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH).count { |path| !path.end_with?("/.") }
end

#parse(args) ⇒ Object



76
77
78
79
80
81
82
83
# File 'lib/simplecov/cli/clean.rb', line 76

def parse(args)
  opts = {dry_run: false, quiet: false}
  build_parser do |o|
    o.on("--dry-run") { opts[:dry_run] = true }
    quiet_option(o, opts)
  end.parse(args)
  opts
end

#protected_pathsObject

The working directory, which a coverage directory must not be or contain. The .simplecov project root needs no entry of its own: Dotfile.find walks up from the working directory, so any target containing that root contains the working directory too.



60
61
62
# File 'lib/simplecov/cli/clean.rb', line 60

def protected_paths
  [canonical_path(Dir.pwd)]
end

#run(args, stdout:, stderr:) ⇒ Object



16
17
18
19
20
21
22
23
24
# File 'lib/simplecov/cli/clean.rb', line 16

def run(args, stdout:, stderr:, **)
  opts = parse(args)
  dir = CLI.coverage_dir
  return error(stderr, "refusing to remove unsafe coverage directory #{dir.inspect}") unless safe_to_remove?(dir)
  return announce(stdout, opts, "#{dir} doesn't exist; nothing to do") || 0 unless File.directory?(dir)

  sweep(dir, opts, stdout)
  0
end

#safe_to_remove?(dir) ⇒ Boolean

A filesystem root is its own dirname, on every platform and every Windows drive. The descendant check only protects roots that happen to contain the cwd, which misses other drives, so roots are refused outright.

Returns:

  • (Boolean)


49
50
51
52
53
54
# File 'lib/simplecov/cli/clean.rb', line 49

def safe_to_remove?(dir)
  target = canonical_path(dir)
  return false if File.dirname(target).eql?(target)

  protected_paths.none? { |path| path.eql?(target) || descendant_of?(path, target) }
end

#sweep(dir, opts, stdout) ⇒ Object



26
27
28
29
30
31
32
33
# File 'lib/simplecov/cli/clean.rb', line 26

def sweep(dir, opts, stdout)
  if opts.fetch(:dry_run)
    announce(stdout, opts, "would remove #{dir} (#{entry_count(dir)} entries)")
  else
    FileUtils.rm_rf(dir)
    announce(stdout, opts, "removed #{dir}")
  end
end