2016-01-05 21 views
8

Neuere Versionen von Ruby unterstützen die Verwendung von Klammern in Globbing verwenden, wenn Sie die Datei :: FNM_EXTGLOB OptionGlobbing Klammern Ruby 1.9.3

Vom 2.2.0 documentation

jedoch
File.fnmatch('c{at,ub}s', 'cats', File::FNM_EXTGLOB) #=> true # { } is supported on FNM_EXTGLOB 

verwenden, die 1,9 0,3-Dokumentation sagt, dass es nicht in 1.9.3 unterstützt:

File.fnmatch('c{at,ub}s', 'cats')  #=> false # { } isn't supported 

(auch File::FNM_EXTGLOB gab einen Namen Fehler zu verwenden versuchen)

Gibt es eine Möglichkeit zur Verwendung von Klammern in Ruby 1.9.3, z. B. einem Edelstein eines Drittanbieters, mithilfe von Klammern?

Die Saiten, die ich gegen entsprechen will, sind von S3, nicht einem lokalen Dateisystem, so kann ich den Globbing so weit zu tun, wie ich weiß, nicht nur das Betriebssystem fragen.

+1

Da 'File' in der Lage, die Kleckse zu tun, ist das Betriebssystem für sicher in der Lage. Ich wette, dass S3 mounted oder like ist, also versuche bitte '% x | ls c {at, ub} s |', es sollte funktionieren. – mudasobwa

Antwort

0

Das ist ein Spaß Ruby-Übung war! Keine Ahnung, ob diese Lösung ist robust genug für Sie, aber hier geht:

class File 
    class << self 
    def fnmatch_extglob(pattern, path, flags=0) 
     explode_extglob(pattern).any?{|exploded_pattern| 
     fnmatch(exploded_pattern,path,flags) 
     } 
    end 

    def explode_extglob(pattern) 
     if match=pattern.match(/\{([^{}]+)}/) then 
     subpatterns = match[1].split(',',-1) 
     subpatterns.map{|subpattern| explode_extglob(match.pre_match+subpattern+match.post_match)}.flatten 
     else 
     [pattern] 
     end 
    end 
    end 
end 

Bessere Tests erforderlich ist, aber es scheint für einfache Fälle gut zu funktionieren:

[2] pry(main)> File.explode_extglob('c{at,ub}s') 
=> ["cats", "cubs"] 
[3] pry(main)> File.explode_extglob('c{at,ub}{s,}') 
=> ["cats", "cat", "cubs", "cub"] 
[4] pry(main)> File.explode_extglob('{a,b,c}{d,e,f}{g,h,i}') 
=> ["adg", "adh", "adi", "aeg", "aeh", "aei", "afg", "afh", "afi", "bdg", "bdh", "bdi", "beg", "beh", "bei", "bfg", "bfh", "bfi", "cdg", "cdh", "cdi", "ceg", "ceh", "cei", "cfg", "cfh", "cfi"] 
[5] pry(main)> File.explode_extglob('{a,b}c*') 
=> ["ac*", "bc*"] 
[6] pry(main)> File.fnmatch('c{at,ub}s', 'cats') 
=> false 
[7] pry(main)> File.fnmatch_extglob('c{at,ub}s', 'cats') 
=> true 
[8] pry(main)> File.fnmatch_extglob('c{at,ub}s*', 'catsssss') 
=> true 

Getestet mit Ruby 1.9. 3 und Ruby 2.1.5 und 2.2.1.

+1

Das ist eine wirklich gute Lösung für kleine Projekte. Ich hatte eine ähnliche Lösung verwendet (aber nicht ganz so gut) wie Sie, entschied sich jedoch aus Performance-Gründen für große/tiefe Hierarchien mit einer vollständig geparsten Lösung; es machte einen * großen * Unterschied (mehr als eine Größenordnung in vielen Fällen). Beachten Sie, dass wenn Sie ein drittes Argument ('flags = 0') zu Ihrem' fnmatch_extglob' hinzufügen und diese 'flags' an den dritten Parameter von' fnmatch' übergeben, erhalten Sie den gesamten Satz von Flags-Verhalten kostenlos und Sie Ich würde praktisch alle Funktionstests bestehen, die man damit anstellen könnte. –

+0

Danke für den Kommentar. Ich habe die optionalen Flags hinzugefügt. –

1

Ich bin in den Prozess zur Herstellung eines Ruby Backport für Zahnspangen bis Verpackung Globbing Unterstützung. Hier sind die wesentlichen Teile dieser Lösung:

module File::Constants 
    FNM_EXTGLOB = 0x10 
end 

class << File 
    def fnmatch_with_braces_glob(pattern, path, flags =0) 
    regex = glob_convert(pattern, flags) 

    return regex && path.match(regex).to_s == path 
    end 

    def fnmatch_with_braces_glob?(pattern, path, flags =0) 
    return fnmatch_with_braces_glob(pattern, path, flags) 
    end 

private 
    def glob_convert(pattern, flags) 
    brace_exp = (flags & File::FNM_EXTGLOB) != 0 
    pathnames = (flags & File::FNM_PATHNAME) != 0 
    dot_match = (flags & File::FNM_DOTMATCH) != 0 
    no_escape = (flags & File::FNM_NOESCAPE) != 0 
    casefold = (flags & File::FNM_CASEFOLD) != 0 
    syscase = (flags & File::FNM_SYSCASE) != 0 
    special_chars = ".*?\\[\\]{},.+()|$^\\\\" + (pathnames ? "/" : "") 
    special_chars_regex = Regexp.new("[#{special_chars}]") 

    if pattern.length == 0 || !pattern.index(special_chars_regex) 
     return Regexp.new(pattern, casefold || syscase ? Regexp::IGNORECASE : 0) 
    end 

    # Convert glob to regexp and escape regexp characters 
    length = pattern.length 
    start = 0 
    brace_depth = 0 
    new_pattern = "" 
    char = "/" 

    loop do 
     path_start = !dot_match && char[-1] == "/" 

     index = pattern.index(special_chars_regex, start) 

     if index 
     new_pattern += pattern[start...index] if index > start 
     char = pattern[index] 

     snippet = case char 
     when "?" then path_start ? (pathnames ? "[^./]" : "[^.]") : (pathnames ? "[^/]" : ".") 
     when "." then "\\." 
     when "{" then (brace_exp && (brace_depth += 1) >= 1) ? "(?:" : "{" 
     when "}" then (brace_exp && (brace_depth -= 1) >= 0) ? ")" : "}" 
     when "," then (brace_exp && brace_depth >= 0) ? "|" : "," 
     when "/" then "/" 
     when "\\" 
      if !no_escape && index < length 
      next_char = pattern[index += 1] 
      special_chars.include?(next_char) ? "\\#{next_char}" : next_char 
      else 
      "\\\\" 
      end 
     when "*" 
      if index+1 < length && pattern[index+1] == "*" 
      char += "*" 
      if pathnames && index+2 < length && pattern[index+2] == "/" 
       char += "/" 
       index += 2 
       "(?:(?:#{path_start ? '[^.]' : ''}[^\/]*?\\#{File::SEPARATOR})(?:#{!dot_match ? '[^.]' : ''}[^\/]*?\\#{File::SEPARATOR})*?)?" 
      else 
       index += 1 
       "(?:#{path_start ? '[^.]' : ''}(?:[^\\#{File::SEPARATOR}]*?\\#{File::SEPARATOR}?)*?)?" 
      end 
      else 
      path_start ? (pathnames ? "(?:[^./][^/]*?)?" : "(?:[^.].*?)?") : (pathnames ? "[^/]*?" : ".*?") 
      end 
     when "[" 
      # Handle character set inclusion/exclusion 
      start_index = index 
      end_index = pattern.index(']', start_index+1) 
      while end_index && pattern[end_index-1] == "\\" 
      end_index = pattern.index(']', end_index+1) 
      end 
      if end_index 
      index = end_index 
      char_set = pattern[start_index..end_index] 
      char_set.delete!('/') if pathnames 
      char_set[1] = '^' if char_set[1] == '!' 
      (char_set == "[]" || char_set == "[^]") ? "" : char_set 
      else 
      "\\[" 
      end 
     else 
      "\\#{char}" 
     end 

     new_pattern += snippet 
     else 
     if start < length 
      snippet = pattern[start..-1] 
      new_pattern += snippet 
     end 
     end 

     break if !index 
     start = index + 1 
    end 

    begin 
     return Regexp.new("\\A#{new_pattern}\\z", casefold || syscase ? Regexp::IGNORECASE : 0) 
    rescue 
     return nil 
    end 
    end 
end 

Diese Lösung berücksichtigt die verschiedenen Flaggen für die File::fnmatch Funktion und verwendet das glob Muster eines geeigneten Regexp zu bauen, um die Funktionen zu entsprechen.

File.fnmatch('c{at,ub}s', 'cats', File::FNM_EXTGLOB) 
#=> true 
File.fnmatch('file{*.doc,*.pdf}', 'filename.doc') 
#=> false 
File.fnmatch('file{*.doc,*.pdf}', 'filename.doc', File::FNM_EXTGLOB) 
#=> true 
File.fnmatch('f*l?{[a-z].doc,[0-9].pdf}', 'filex.doc', File::FNM_EXTGLOB) 
#=> true 
File.fnmatch('**/.{pro,}f?l*', 'home/.profile', File::FNM_EXTGLOB | File::FNM_DOTMATCH) 
#=> true 

Die fnmatch_with_braces_glob (und ? Variante) anstelle von fnmatch werden gepatcht, so dass Ruby 2.0.0-konformer Code wird mit früheren Versionen von Ruby arbeiten, wie: Mit dieser Lösung können diese Tests erfolgreich ausgeführt werden Gut. Aus Gründen der Übersichtlichkeit enthält der oben gezeigte Code keine Leistungsverbesserungen, Argumentprüfung oder Erkennung und Patch-In-Code der Backports-Funktionen. Diese werden natürlich in die eigentliche Einreichung des Projekts einbezogen.

Ich teste noch einige Grenzfälle und stark Optimierung der Leistung; es sollte bereit sein, sehr bald einzureichen. Sobald es in einer offiziellen Backports-Version verfügbar ist, aktualisiere ich den Status hier.

Beachten Sie, dass Dir::glob Unterstützung zur gleichen Zeit kommen wird, wie gut.