Class: Roast::Helpers::PathResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/roast/helpers/path_resolver.rb

Overview

Utility class for resolving file paths with directory structure issues

Class Method Summary collapse

Class Method Details

.resolve(path) ⇒ Object

Intelligently resolves a path considering possible directory structure issues



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/roast/helpers/path_resolver.rb', line 9

def resolve(path)
  # Store original path for logging if needed
  original_path = path

  # Early return if the path is nil or empty
  return path if path.nil? || path.empty?

  # First try standard path expansion
  expanded_path = File.expand_path(path)

  # Return early if the file exists at the expanded path
  return expanded_path if File.exist?(expanded_path)

  # Get current directory and possible project root paths
  current_dir = Dir.pwd
  possible_roots = [
    current_dir,
    File.expand_path(File.join(current_dir, "..")),
    File.expand_path(File.join(current_dir, "../..")),
    File.expand_path(File.join(current_dir, "../../..")),
    File.expand_path(File.join(current_dir, "../../../..")),
    File.expand_path(File.join(current_dir, "../../../../..")),
  ]

  # Check for directory name duplications anywhere in the path
  path_parts = expanded_path.split(File::SEPARATOR).reject(&:empty?)

  # Try removing each duplicate segment individually and check if the resulting path exists
  path_parts.each_with_index do |part, i|
    next if i == 0 # Skip the first segment

    # Check if this segment appears earlier in the path
    next unless path_parts[0...i].include?(part)

    # Create a new path without this segment
    test_parts = path_parts.dup
    test_parts.delete_at(i)

    test_path = if original_path.start_with?("/")
      File.join("/", *test_parts)
    else
      File.join(test_parts)
    end

    # If this path exists, return it
    return test_path if File.exist?(test_path)

    # Also try removing all future occurrences of this segment name
    duplicate_indices = []
    path_parts.each_with_index do |segment, idx|
      if idx > 0 && segment == part && idx >= i
        duplicate_indices << idx
      end
    end

    next if duplicate_indices.none?

    filtered_parts = path_parts.dup
    # Remove from end to beginning to keep indices valid
    duplicate_indices.reverse_each { |idx| filtered_parts.delete_at(idx) }

    test_path = if original_path.start_with?("/")
      File.join("/", *filtered_parts)
    else
      File.join(filtered_parts)
    end

    return test_path if File.exist?(test_path)
  end

  # Try detecting all duplicates at once
  seen_segments = {}
  duplicate_indices = []

  path_parts.each_with_index do |part, i|
    if seen_segments[part]
      duplicate_indices << i
    else
      seen_segments[part] = true
    end
  end

  if duplicate_indices.any?
    # Try removing all duplicates
    unique_parts = path_parts.dup
    # Remove from end to beginning to keep indices valid
    duplicate_indices.reverse_each { |i| unique_parts.delete_at(i) }

    test_path = if original_path.start_with?("/")
      File.join("/", *unique_parts)
    else
      File.join(unique_parts)
    end

    return test_path if File.exist?(test_path)
  end

  # Try relative path resolution from various possible roots
  relative_path = path.sub(%r{^\./}, "")
  possible_roots.each do |root|
    # Try the path as-is from this root
    candidate = File.join(root, relative_path)
    return candidate if File.exist?(candidate)

    # Try with a leading slash removed
    if relative_path.start_with?("/")
      candidate = File.join(root, relative_path.sub(%r{^/}, ""))
      return candidate if File.exist?(candidate)
    end
  end

  # Try extracting the path after a potential project root
  if expanded_path.include?("/src/") || expanded_path.include?("/lib/") || expanded_path.include?("/test/")
    # Potential project markers
    markers = ["/src/", "/lib/", "/test/", "/app/", "/config/"]
    markers.each do |marker|
      next unless expanded_path.include?(marker)

      # Get the part after the marker
      parts = expanded_path.split(marker, 2)
      next unless parts.size == 2

      marker_dir = marker.gsub("/", "")
      relative_from_marker = parts[1]

      # Try each possible root with this marker
      possible_roots.each do |root|
        candidate = File.join(root, marker_dir, relative_from_marker)
        return candidate if File.exist?(candidate)
      end
    end
  end

  # Default to the original expanded path if all else fails
  expanded_path
end