Module: Mutineer::ProjectPath

Defined in:
lib/mutineer/project_path.rb

Overview

Realpath-based path normalization against a project root. Shared by the coverage cache (map keys) and mutant ids (the path hashed into each id), so lib/x.rb, ./lib/x.rb, an absolute path and a path through a symlinked root all resolve to the same key. Realpaths on both sides also absorb the macOS /var vs /private/var alias.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.absolute(path, root) ⇒ String

Expands path against root, resolved to its real path when it exists.

Parameters:

  • path (String) —

    relative (to root) or absolute path.

  • root (String) —

    project root.

Returns:

  • (String) —

    absolute path.



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/mutineer/project_path.rb', line 32

def absolute(path, root)
  # Join, don't expand: File.expand_path collapses `..` textually, before any
  # symlink is followed, so `link/../x.rb` would name the wrong file. The file
  # system resolves `..` physically in File.realpath.
  # A leading `~` names the home directory (as File.expand_path reads it), so
  # expand it rather than joining it under the root.
  raw = if File.absolute_path?(path) then path
        elsif path.start_with?("~") then File.expand_path(path)
        else File.join(File.expand_path(root), path)
        end
  File.exist?(raw) ? File.realpath(raw) : File.expand_path(raw)
end

.relative(path, root) ⇒ String

Path of path relative to the real project root. A path outside the root comes back as its absolute real path.

Parameters:

  • path (String) —

    relative (to root) or absolute path.

  • root (String) —

    project root.

Returns:

  • (String) —

    root-relative path, or an absolute path when outside root.



18
19
20
21
22
23
24
25
# File 'lib/mutineer/project_path.rb', line 18

def relative(path, root)
  abs = absolute(path, root)
  real_root = root_real(root)
  prefix = real_root.end_with?("/") ? real_root : "#{real_root}/"
  return abs unless abs.start_with?(prefix)

  abs.delete_prefix(prefix)
end

.root_real(root) ⇒ String

Canonical project root (/var vs /private/var). Any file system error (missing, unreadable, a symlink loop) falls back to the expanded path, so a caller such as the CLI's run-root warning never crashes on it.

Parameters:

  • root (String) —

    project root.

Returns:

  • (String) —

    realpath of the root when it exists, else its expanded path.



51
52
53
54
55
# File 'lib/mutineer/project_path.rb', line 51

def root_real(root)
  File.realpath(File.expand_path(root))
rescue SystemCallError
  File.expand_path(root)
end

Instance Method Details

#absolute(path, root) ⇒ String (private)

Expands path against root, resolved to its real path when it exists.

Parameters:

  • path (String) —

    relative (to root) or absolute path.

  • root (String) —

    project root.

Returns:

  • (String) —

    absolute path.



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/mutineer/project_path.rb', line 32

def absolute(path, root)
  # Join, don't expand: File.expand_path collapses `..` textually, before any
  # symlink is followed, so `link/../x.rb` would name the wrong file. The file
  # system resolves `..` physically in File.realpath.
  # A leading `~` names the home directory (as File.expand_path reads it), so
  # expand it rather than joining it under the root.
  raw = if File.absolute_path?(path) then path
        elsif path.start_with?("~") then File.expand_path(path)
        else File.join(File.expand_path(root), path)
        end
  File.exist?(raw) ? File.realpath(raw) : File.expand_path(raw)
end

#relative(path, root) ⇒ String (private)

Path of path relative to the real project root. A path outside the root comes back as its absolute real path.

Parameters:

  • path (String) —

    relative (to root) or absolute path.

  • root (String) —

    project root.

Returns:

  • (String) —

    root-relative path, or an absolute path when outside root.



18
19
20
21
22
23
24
25
# File 'lib/mutineer/project_path.rb', line 18

def relative(path, root)
  abs = absolute(path, root)
  real_root = root_real(root)
  prefix = real_root.end_with?("/") ? real_root : "#{real_root}/"
  return abs unless abs.start_with?(prefix)

  abs.delete_prefix(prefix)
end

#root_real(root) ⇒ String (private)

Canonical project root (/var vs /private/var). Any file system error (missing, unreadable, a symlink loop) falls back to the expanded path, so a caller such as the CLI's run-root warning never crashes on it.

Parameters:

  • root (String) —

    project root.

Returns:

  • (String) —

    realpath of the root when it exists, else its expanded path.



51
52
53
54
55
# File 'lib/mutineer/project_path.rb', line 51

def root_real(root)
  File.realpath(File.expand_path(root))
rescue SystemCallError
  File.expand_path(root)
end