Class: Pod::Sandbox::PathList

Inherits:
Object
  • Object
show all
Defined in:
lib/cocoapods/sandbox/path_list.rb

Overview

Note:

A PathList once it has generated the list of the paths this is updated only if explicitly requested by calling #read_file_system

The PathList class is designed to perform multiple glob matches against a given directory. Basically, it generates a list of all the children paths and matches the globs patterns against them, resulting in just one access to the file system.

Instance Attribute Summary collapse

Globbing collapse

Private helpers collapse

Instance Method Summary collapse

Constructor Details

#initialize(root) ⇒ PathList

Initialize a new instance

Parameters:

  • root (Pathname)

    @see #root



25
26
27
28
29
# File 'lib/cocoapods/sandbox/path_list.rb', line 25

def initialize(root)
  root_dir = root.to_s.unicode_normalize(:nfkc)
  @root = Pathname.new(root_dir)
  @glob_cache = {}
end

Instance Attribute Details

#rootPathname (readonly)

Returns The root of the list whose files and directories are used to perform the matching operations.

Returns:

  • (Pathname)

    The root of the list whose files and directories are used to perform the matching operations.



19
20
21
# File 'lib/cocoapods/sandbox/path_list.rb', line 19

def root
  @root
end

Instance Method Details

#dir_glob_equivalent_patterns(pattern) ⇒ Array<String> (private)

The expansion provides support for:

  • Literals

    dir_glob_equivalent_patterns('file1,file2.h,m') => ["file1.h", "file1.m", "file2.h", "file2.m"]

  • Matching the direct children of a directory with **

    dir_glob_equivalent_patterns('Classes//file.m') => ["Classes//file.m", "Classes/file.m"]

Parameters:

  • pattern (String)

    A Dir#glob like pattern.

Returns:

  • (Array<String>)

    An array of patterns converted from a Dir.glob pattern to patterns that File.fnmatch can handle. This is used by the #relative_glob method to emulate Dir.glob.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/cocoapods/sandbox/path_list.rb', line 197

def dir_glob_equivalent_patterns(pattern)
  pattern = pattern.gsub('/**/', '{/**/,/}')
  values_by_set = {}
  pattern.scan(/\{[^}]*\}/) do |set|
    values = set.gsub(/[{}]/, '').split(',')
    values_by_set[set] = values
  end

  if values_by_set.empty?
    [pattern]
  else
    patterns = [pattern]
    values_by_set.each do |set, values|
      patterns = patterns.flat_map do |old_pattern|
        values.map do |value|
          old_pattern.gsub(set, value)
        end
      end
    end
    patterns
  end
end

#directory?(sub_path) ⇒ Boolean (private)

Returns Wether a path is a directory. The result of this method computed without accessing the file system and is case insensitive.

Parameters:

  • sub_path (String, Pathname)

    The path that could be a directory.

Returns:

  • (Boolean)

    Wether a path is a directory. The result of this method computed without accessing the file system and is case insensitive.



173
174
175
176
# File 'lib/cocoapods/sandbox/path_list.rb', line 173

def directory?(sub_path)
  sub_path = sub_path.to_s.downcase.sub(/\/$/, '')
  dirs.any? { |dir| dir.downcase == sub_path }
end

#dirsArray<String>

Returns The list of absolute the path of all the directories contained in #root.

Returns:

  • (Array<String>)

    The list of absolute the path of all the directories contained in #root.



42
43
44
45
# File 'lib/cocoapods/sandbox/path_list.rb', line 42

def dirs
  read_file_system unless @dirs
  @dirs
end

#escape_path_for_glob(path) ⇒ Pathname (private)

Note:

See CocoaPods/CocoaPods#862.

Escapes the glob metacharacters from a given path so it can used in Dir#glob and similar methods.

Parameters:

  • path (String, Pathname)

    The path to escape.

Returns:

  • (Pathname)

    The escaped path.



230
231
232
233
234
235
236
237
# File 'lib/cocoapods/sandbox/path_list.rb', line 230

def escape_path_for_glob(path)
  result = path.to_s
  characters_to_escape = ['[', ']', '{', '}', '?', '*']
  characters_to_escape.each do |character|
    result.gsub!(character, "\\#{character}")
  end
  Pathname.new(result)
end

#filesArray<String>

Returns The list of absolute the path of all the files contained in #root.

Returns:

  • (Array<String>)

    The list of absolute the path of all the files contained in #root.



34
35
36
37
# File 'lib/cocoapods/sandbox/path_list.rb', line 34

def files
  read_file_system unless @files
  @files
end

#glob(patterns, options = {}) ⇒ Array<Pathname>

Similar to #glob but returns the absolute paths.

Parameters:

  • patterns (String, Array<String>)

    @see #relative_glob

  • options (Hash) (defaults to: {})

    @see #relative_glob

Returns:

  • (Array<Pathname>)


93
94
95
96
# File 'lib/cocoapods/sandbox/path_list.rb', line 93

def glob(patterns, options = {})
  cache_key = options.merge(:patterns => patterns)
  @glob_cache[cache_key] ||= relative_glob(patterns, options).map { |p| root.join(p) }
end

#read_file_systemvoid

This method returns an undefined value.

Returns Reads the file system and populates the files and paths lists.



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
# File 'lib/cocoapods/sandbox/path_list.rb', line 50

def read_file_system
  unless root.exist?
    raise Informative, "Attempt to read non existent folder `#{root}`."
  end
  dirs = []
  files = []
  root_length = root.cleanpath.to_s.length + File::SEPARATOR.length
  escaped_root = escape_path_for_glob(root)
  Dir.glob(escaped_root + '**/*', File::FNM_DOTMATCH).each do |f|
    directory = File.directory?(f)
    # Ignore `.` and `..` directories
    next if directory && f =~ /\.\.?$/

    f = f.slice(root_length, f.length - root_length)
    next if f.nil?

    (directory ? dirs : files) << f
  end

  dirs.sort_by!(&:upcase)
  files.sort_by!(&:upcase)

  @dirs = dirs
  @files = files
  @glob_cache = {}
end

#relative_glob(patterns, options = {}) ⇒ Array<Pathname>

The list of relative paths that are case insensitively matched by a given pattern. This method emulates Dir#glob with the File::FNM_CASEFOLD option.

Parameters:

  • patterns (String, Array<String>)

    A single Dir#glob like pattern, or a list of patterns.

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :dir_pattern (String)

    An optional pattern to append to a pattern, if it is the path to a directory.

  • :exclude_patterns (Array<String>)

    Exclude specific paths given by those patterns.

  • :include_dirs (Array<String>)

    Additional paths to take into account for matching.

Returns:

  • (Array<Pathname>)


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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/cocoapods/sandbox/path_list.rb', line 119

def relative_glob(patterns, options = {})
  return [] if patterns.empty?

  dir_pattern = options[:dir_pattern]
  exclude_patterns = options[:exclude_patterns]
  include_dirs = options[:include_dirs]

  if include_dirs
    full_list = files + dirs
  else
    full_list = files
  end
  patterns_array = Array(patterns)
  exact_matches = (full_list & patterns_array).to_set

  unless patterns_array.empty?
    list = patterns_array.flat_map do |pattern|
      if exact_matches.include?(pattern)
        pattern
      else
        if directory?(pattern) && dir_pattern
          pattern += '/' unless pattern.end_with?('/')
          pattern += dir_pattern
        end
        expanded_patterns = dir_glob_equivalent_patterns(pattern)
        full_list.select do |path|
          expanded_patterns.any? do |p|
            File.fnmatch(p, path, File::FNM_CASEFOLD | File::FNM_PATHNAME)
          end
        end
      end
    end
  end

  list = list.map { |path| Pathname.new(path) }
  if exclude_patterns
    exclude_options = { :dir_pattern => '**/*', :include_dirs => include_dirs }
    list -= relative_glob(exclude_patterns, exclude_options)
  end
  list
end