Module: SimpleCov::CLI::Clean

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

Overview

simplecov clean [--dry-run] — remove the coverage report directory (or whatever SimpleCov.coverage_dir resolves to). The --dry-run flag prints what would be deleted without touching disk, for when you're not sure what's in there.

Constant Summary

Constants included from CommandHelpers

SimpleCov::CLI::CommandHelpers::STATS_ROW_FORMAT

Class Method Summary collapse

Methods included from CommandHelpers

command_name, common_options, error, parse_common, stats_row

Class Method Details

.announce(stdout, opts, message) ⇒ Object



45
46
47
# File 'lib/simplecov/cli/clean.rb', line 45

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

.canonical_path(path) ⇒ Object



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

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)


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

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 (.resultset.json, .last_run.json) count too; the glob still yields the directory's own "." entry, which rm_rf does not remove separately.



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

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

.parse(args) ⇒ Object



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

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

.protected_pathsObject



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

def protected_paths
  project_root = SimpleCov::CLI::Dotfile.find
  [Dir.pwd, (File.dirname(project_root) if project_root)].compact.map { |path| canonical_path(path) }.uniq
end

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



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

def run(args, stdout:, stderr:, **)
  opts = parse(args)
  dir = SimpleCov::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

Returns:

  • (Boolean)


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

def safe_to_remove?(dir)
  target = canonical_path(dir)
  # A filesystem root is its own dirname ("/" everywhere, drive
  # roots like "D:/" on Windows). The descendant check below only
  # protects roots that happen to contain the cwd or project root,
  # which misses other drives, so refuse roots outright.
  return false if File.dirname(target) == target

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

.sweep(dir, opts, stdout) ⇒ Object



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

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