Class: Chef::ChefFS::FilePattern

Inherits:
Object
  • Object
show all
Defined in:
lib/chef/chef_fs/file_pattern.rb

Overview

Represents a glob pattern. This class is designed so that it can match arbitrary strings, and tell you about partial matches.

Examples:

  • a*z

    • Matches abcz

    • Does not match ab/cd/ez

    • Does not match xabcz

  • a**z

    • Matches abcz

    • Matches ab/cd/ez

Special characters supported:

  • / (and \ on Windows) - directory separators

  • * - match zero or more characters (but not directory separators)

  • *\* - match zero or more characters, including directory separators

  • ? - match exactly one character (not a directory separator)

Only on Unix:

  • [abc0-9] - match one of the included characters

  • \<character> - escape character: match the given character

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pattern) ⇒ FilePattern

Initialize a new FilePattern with the pattern string.

Raises ArgumentError if empty file pattern is specified



50
51
52
# File 'lib/chef/chef_fs/file_pattern.rb', line 50

def initialize(pattern)
  @pattern = pattern
end

Instance Attribute Details

#patternObject (readonly)

The pattern string.



55
56
57
# File 'lib/chef/chef_fs/file_pattern.rb', line 55

def pattern
  @pattern
end

Class Method Details

.pattern_special_charactersObject



247
248
249
250
251
252
253
254
255
# File 'lib/chef/chef_fs/file_pattern.rb', line 247

def self.pattern_special_characters
  if ChefUtils.windows?
    @pattern_special_characters ||= /(\*\*|\*|\?|[\*\?\.\|\(\)\[\]\{\}\+\\\\\^\$])/
  else
    # Unix also supports character regexes and backslashes
    @pattern_special_characters ||= /(\\.|\[[^\]]+\]|\*\*|\*|\?|[\*\?\.\|\(\)\[\]\{\}\+\\\\\^\$])/
  end
  @pattern_special_characters
end

.pattern_to_regexp(pattern) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/chef/chef_fs/file_pattern.rb', line 261

def self.pattern_to_regexp(pattern)
  regexp = ""
  exact = ""
  has_double_star = false
  pattern.split(pattern_special_characters).each_with_index do |part, index|
    # Odd indexes from the split are symbols.  Even are normal bits.
    if index.even?
      exact << part unless exact.nil?
      regexp << part
    else
      case part
      # **, * and ? happen on both platforms.
      when "**"
        exact = nil
        has_double_star = true
        regexp << ".*"
      when "*"
        exact = nil
        regexp << "[^\\/]*"
      when "?"
        exact = nil
        regexp << "."
      else
        if part[0, 1] == "\\" && part.length == 2
          # backslash escapes are only supported on Unix, and are handled here by leaving the escape on (it means the same thing in a regex)
          exact << part[1, 1] unless exact.nil?
          if regexp_escape_characters.include?(part[1, 1])
            regexp << part
          else
            regexp << part[1, 1]
          end
        elsif part[0, 1] == "[" && part.length > 1
          # [...] happens only on Unix, and is handled here by *not* backslashing (it means the same thing in and out of regex)
          exact = nil
          regexp << part
        else
          exact += part unless exact.nil?
          regexp << "\\#{part}"
        end
      end
    end
  end
  [regexp, exact, has_double_star]
end

.regexp_escape_charactersObject



257
258
259
# File 'lib/chef/chef_fs/file_pattern.rb', line 257

def self.regexp_escape_characters
  [ "[", "\\", "^", "$", ".", "|", "?", "*", "+", "(", ")", "{", "}" ]
end

Instance Method Details

#could_match_children?(path) ⇒ Boolean

Reports whether this pattern could match children of path. If the pattern doesn’t match the path up to this point or if it matches and doesn’t allow further children, this will return false.

Attributes

  • path - a path to check

Examples

abc/def.could_match_children?('abc') == true
abc.could_match_children?('abc') == false
abc/def.could_match_children?('x') == false
a**z.could_match_children?('ab/cd') == true

Returns:

  • (Boolean)


72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/chef/chef_fs/file_pattern.rb', line 72

def could_match_children?(path)
  return false if path == "" # Empty string is not a path

  argument_is_absolute = Chef::ChefFS::PathUtils.is_absolute?(path)
  return false if is_absolute != argument_is_absolute

  path = path[1, path.length - 1] if argument_is_absolute

  path_parts = Chef::ChefFS::PathUtils.split(path)
  # If the pattern is shorter than the path (or same size), children will be larger than the pattern, and will not match.
  return false if regexp_parts.length <= path_parts.length && !has_double_star
  # If the path doesn't match up to this point, children won't match either.
  return false if path_parts.zip(regexp_parts).any? { |part, regexp| !regexp.nil? && !regexp.match(part) }

  # Otherwise, it's possible we could match: the path matches to this point, and the pattern is longer than the path.
  # TODO There is one edge case where the double star comes after some characters like abc**def--we could check whether the next
  # bit of path starts with abc in that case.
  true
end

#exact_child_name_under(path) ⇒ Object

Returns the immediate child of a path that would be matched if this FilePattern was applied. If more than one child could match, this method returns nil.

Attributes

  • path - The path to look for an exact child name under.

Returns

The next directory in the pattern under the given path. If the directory part could match more than one child, it returns nil.

Examples

abc/def.exact_child_name_under('abc') == 'def'
abc/def/ghi.exact_child_name_under('abc') == 'def'
abc/*/ghi.exact_child_name_under('abc') == nil
abc/*/ghi.exact_child_name_under('abc/def') == 'ghi'
abc/**/ghi.exact_child_name_under('abc/def') == nil

This method assumes could_match_children?(path) is true.



115
116
117
118
119
120
121
# File 'lib/chef/chef_fs/file_pattern.rb', line 115

def exact_child_name_under(path)
  path = path[1, path.length - 1] if Chef::ChefFS::PathUtils.is_absolute?(path)
  dirs_in_path = Chef::ChefFS::PathUtils.split(path).length
  return nil if exact_parts.length <= dirs_in_path

  exact_parts[dirs_in_path]
end

#exact_pathObject

If this pattern represents an exact path, returns the exact path.

abc/def.exact_path == 'abc/def'
abc/*def.exact_path == 'abc/def'
abc/x\\yz.exact_path == 'abc/xyz'


128
129
130
131
132
133
# File 'lib/chef/chef_fs/file_pattern.rb', line 128

def exact_path
  return nil if has_double_star || exact_parts.any?(&:nil?)

  result = Chef::ChefFS::PathUtils.join(*exact_parts)
  is_absolute ? Chef::ChefFS::PathUtils.join("", result) : result
end

#is_absoluteObject

Tell whether this pattern matches absolute, or relative paths



146
147
148
149
# File 'lib/chef/chef_fs/file_pattern.rb', line 146

def is_absolute
  calculate
  @is_absolute
end

#match?(path) ⇒ Boolean

Returns <tt>true+ if this pattern matches the path, <tt>false+ otherwise.

abc/*/def.match?('abc/foo/def') == true
abc/*/def.match?('abc/foo') == false

Returns:

  • (Boolean)


155
156
157
158
159
160
161
# File 'lib/chef/chef_fs/file_pattern.rb', line 155

def match?(path)
  argument_is_absolute = Chef::ChefFS::PathUtils.is_absolute?(path)
  return false if is_absolute != argument_is_absolute

  path = path[1, path.length - 1] if argument_is_absolute
  !!regexp.match(path)
end

#normalized_patternObject

Returns the normalized version of the pattern, with / as the directory separator, and “.” and “..” removed.

This does not presently change things like b to b, but in the future it might.



140
141
142
143
# File 'lib/chef/chef_fs/file_pattern.rb', line 140

def normalized_pattern
  calculate
  @normalized_pattern
end

#to_sObject

Returns the string pattern



164
165
166
# File 'lib/chef/chef_fs/file_pattern.rb', line 164

def to_s
  pattern
end