Module: CovLoupe::GlobUtils

Defined in:
lib/cov_loupe/paths/glob_utils.rb

Overview

File globbing utilities for filtering coverage data by path patterns.

Uses File.fnmatch? for pure string matching (no filesystem access for glob evaluation). Case-insensitive matching is automatically applied on case-insensitive volumes, detected by testing the volume containing the path being matched.

Constant Summary collapse

GLOB_MATCH_FLAGS =
File::FNM_PATHNAME | File::FNM_EXTGLOB

Class Method Summary collapse

Class Method Details

.absolutize_pattern(pattern, root) ⇒ String

Converts a pattern to absolute path relative to a root. Handles both relative patterns ("lib/.rb") and absolute ones ("/tmp/.rb").

Parameters:

  • pattern (String)

    glob pattern

  • root (String)

    root directory path

Returns:

  • (String)

    absolute pattern



37
38
39
# File 'lib/cov_loupe/paths/glob_utils.rb', line 37

module_function def absolutize_pattern(pattern, root)
  File.expand_path(pattern, root)
end

.filter_by_pattern(items, patterns, key: 'file') ⇒ Array<Hash>

Filters items where a key contains a file path matching the patterns.

Parameters:

  • items (Array<Hash>)

    items to filter

  • patterns (Array<String>)

    absolute glob patterns

  • key (String) (defaults to: 'file')

    key in item hash containing the absolute file path

Returns:

  • (Array<Hash>)

    items whose file path matches at least one pattern



84
85
86
87
88
# File 'lib/cov_loupe/paths/glob_utils.rb', line 84

module_function def filter_by_pattern(items, patterns, key: 'file')
  return items if patterns.nil? || patterns.empty?

  items.select { |item| matches_any_pattern?(item[key], patterns) }
end

.filter_paths(paths, globs, root:) ⇒ Array<String>

Filters an array of absolute file paths by glob patterns. Handles normalization and absolutization of patterns internally.

Parameters:

  • paths (Array<String>)

    absolute file paths to filter

  • globs (Array<String>, String, nil)

    glob patterns (can be relative)

  • root (String)

    root directory for resolving relative patterns

Returns:

  • (Array<String>)

    paths that match at least one pattern (or all if no patterns)



97
98
99
100
101
102
103
# File 'lib/cov_loupe/paths/glob_utils.rb', line 97

module_function def filter_paths(paths, globs, root:)
  patterns = normalize_patterns(globs)
  return paths if patterns.empty?

  absolute_patterns = patterns.map { |p| absolutize_pattern(p, root) }
  paths.select { |path| matches_any_pattern?(path, absolute_patterns) }
end

.fn_normalize_path_separatorsProc

Returns a lambda that normalizes path separators for the current platform. On Windows, returns a lambda that converts backslashes to forward slashes. On Unix, returns a pass-through lambda. The lambda is memoized so platform detection only happens once.

Returns:

  • (Proc)

    lambda that takes a string and returns it normalized



19
20
21
22
23
24
25
# File 'lib/cov_loupe/paths/glob_utils.rb', line 19

module_function def fn_normalize_path_separators
  @fn_normalize_path_separators ||= if CovLoupe.windows?
    ->(str) { str.tr('\\', '/') }
  else
    ->(str) { str }
  end
end

.matches_any_pattern?(abs_path, patterns) ⇒ Boolean

Tests if a file path matches any of the given absolute glob patterns. Uses File.fnmatch? for pure string matching without filesystem access. Normalizes paths to forward slashes on Windows for cross-platform compatibility. Automatically handles case-insensitive filesystems by detecting volume case-sensitivity.

Parameters:

  • abs_path (String)

    absolute file path to test

  • patterns (Array<String>)

    absolute glob patterns

Returns:

  • (Boolean)

    true if the path matches at least one pattern

  • (Boolean)


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/cov_loupe/paths/glob_utils.rb', line 49

module_function def matches_any_pattern?(abs_path, patterns)
  normalizer = fn_normalize_path_separators
  normalized_path = normalizer.call(abs_path)

  # Determine match flags based on volume case-sensitivity
  # Find first existing parent directory to test volume properties
  test_dir = abs_path
  until File.directory?(test_dir)
    parent = File.dirname(test_dir)
    break if parent == test_dir # Reached root (works on Windows and Unix)

    test_dir = parent
  end

  flags = GLOB_MATCH_FLAGS
  begin
    # Add case-insensitive matching for case-insensitive volumes
    flags |= File::FNM_CASEFOLD unless PathUtils.volume_case_sensitive?(test_dir)
  rescue SystemCallError, IOError
    # If we can't detect case sensitivity, assume case-insensitive to be conservative
    flags |= File::FNM_CASEFOLD
  end

  patterns.any? do |pattern|
    normalized_pattern = normalizer.call(pattern)
    File.fnmatch?(normalized_pattern, normalized_path, flags)
  end
end

.normalize_patterns(globs) ⇒ Object



27
28
29
# File 'lib/cov_loupe/paths/glob_utils.rb', line 27

module_function def normalize_patterns(globs)
  Array(globs).compact.map(&:to_s).reject(&:empty?)
end