2016-01-05 44 views
8

Najnowsze wersje Ruby obsługują szelki w globbing, jeśli należy wybrać opcję File :: FNM_EXTGLOBglobowania użyciu szelki na Ruby 1.9.3

Z 2.2.0 documentation

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

Jednakże, 1.9 0,3 dokumentacja mówi, że nie jest obsługiwana w 1.9.3:

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

(również próbuje użyć File::FNM_EXTGLOB dał błąd nazwy)

Czy istnieje sposób na glob przy użyciu nawiasów klamrowych w Rubim 1.9.3, takich jak klejnot innej firmy?

Ciągi, które chcę dopasować, pochodzą z S3, a nie z lokalnego systemu plików, więc nie mogę po prostu poprosić systemu operacyjnego, aby wykonał globowanie, o ile wiem.

+1

Ponieważ 'Plik' jest w stanie wykonać globbing, system operacyjny jest na pewno. Założę się, że S3 jest zamontowany lub podobny, więc proszę spróbuj '% x | ls c {at, ub} s |', to powinno działać. – mudasobwa

Odpowiedz

0

To było zabawne ćwiczenie Ruby! Nie mam pojęcia, czy to rozwiązanie jest wystarczająco wytrzymałe dla ciebie, ale tu idzie: potrzebna jest

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 

Lepsze badania, ale wydaje się działać dobrze dla prostych przypadkach:

[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 

Testowane z Ruby 1.9. 3 i Ruby 2.1.5 i 2.2.1.

+1

To naprawdę dobre rozwiązanie dla małych projektów. Użyłem rozwiązania bardzo podobnego do (ale nie tak dobrego jak twoje), ale zdecydowałem się na rozwiązanie w pełni parsowane ze względów wydajnościowych na dużych/głębokich hierarchiach; to spowodowało * ogromną różnicę (więcej niż rząd wielkości w wielu przypadkach). Zauważ, że jeśli dodasz trzeci argument ('flags = 0') do twojego' fnmatch_extglob' i przekażesz te 'flags' do trzeciego parametru 'fnmatch', otrzymasz cały zestaw flag za darmo, a ty prawie zdałyby wszelkie testy funkcjonalne, które mógłbyś na niego rzucić. –

+0

Dzięki za komentarz. Dodałem opcjonalne flagi. –

1

Jestem w trakcie pakowania do Ruby Backport do obsługi globalizacji szelek. Oto podstawowe elementy tego rozwiązania:

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 

To rozwiązanie bierze pod uwagę różne flagi dostępne dla funkcji File::fnmatch i wykorzystuje wzór glob zbudować odpowiedni Regexp dopasować cechy. Dzięki takiemu rozwiązaniu, badania te mogą być uruchamiane pomyślnie:

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 

fnmatch_with_braces_glob (i ? wariant) zostaną poprawione w miejscu fnmatch tak, że Ruby 2.0.0 kod zgodny będzie działać z wcześniejszymi wersjami Ruby, jak dobrze. Ze względu na przejrzystość powyższy kod nie obejmuje poprawy wydajności, sprawdzania argumentów ani wykrywania funkcji Backport i kodu poprawek; będą one oczywiście uwzględnione w faktycznym przedłożeniu projektu.

Wciąż testuję niektóre przypadki skrajne i silnie optymalizuję wydajność; powinien być gotowy do złożenia bardzo szybko. Gdy będzie dostępny w oficjalnej wersji Backports, zaktualizuję status tutaj.

Należy pamiętać, że obsługa Dir::glob pojawi się w tym samym czasie.