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



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

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

#canonical_path(path) ⇒ Object



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

def canonical_path(path)
  REAL_PATHS.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 count too. The glob still yields the directory's own "." entry, which rm_rf does not remove separately.



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

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}
  build_parser do |o|
    o.on("--dry-run") { opts[:dry_run] = true }
    quiet_option(o, opts)
  end.parse(args)
  opts
end

#protected_paths ⇒ Object

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.



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

def protected_paths
  [canonical_path(Dir.pwd)]
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 = 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)


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

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



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

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