2016-01-05 16 views
8

các phiên bản gần đây của Ruby hỗ trợ việc sử dụng các dấu ngoặc trong globbing, nếu bạn sử dụng tùy chọn File :: FNM_EXTGLOBglobbing sử dụng niềng răng trên Ruby 1.9.3

Từ 2.2.0 documentation

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

Tuy nhiên, 1.9 .3 tài liệu nói nó không được hỗ trợ trong 1.9.3:

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

(cũng có thể, cố gắng sử dụng File::FNM_EXTGLOB đã đưa ra một lỗi tên)

Có cách nào để sử dụng dấu ngoặc trong Ruby 1.9.3, chẳng hạn như đá quý bên thứ ba không?

Các chuỗi tôi muốn đối sánh với từ S3, không phải là hệ thống tệp cục bộ, vì vậy tôi không thể yêu cầu hệ điều hành thực hiện thao tác theo như tôi biết.

+1

Vì 'Tệp' có thể thực hiện thao tác trên, hệ điều hành có thể đảm bảo. Tôi đặt cược S3 được gắn hoặc thích, vì vậy hãy thử '% x | ls c {at, ub} s |', nó sẽ hoạt động. – mudasobwa

Trả lời

0

Đó là một bài tập vui vẻ của Ruby! Không có ý tưởng nếu giải pháp này là đủ mạnh mẽ cho bạn, nhưng ở đây đi:

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 

Better thử nghiệm là cần thiết, nhưng có vẻ như để làm việc tốt đối với trường hợp đơn giản:

[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 

Tested với Ruby 1.9. 3 và Ruby 2.1.5 và 2.2.1.

+1

Đó là một giải pháp thực sự tốt cho các dự án nhỏ. Tôi đã sử dụng một giải pháp rất giống với (nhưng không hoàn toàn tốt như) của bạn, nhưng đã chọn tham gia một giải pháp được phân tích cú pháp hoàn toàn vì lý do hiệu suất trên các cấu trúc phân cấp lớn/sâu; nó tạo ra sự khác biệt * rất lớn * (nhiều hơn một bậc độ lớn trong nhiều trường hợp). Lưu ý rằng nếu bạn thêm một đối số thứ 3 ('flags = 0') vào' fnmatch_extglob' của bạn và chuyển các 'flags' đó tới tham số thứ 3 của' fnmatch', bạn sẽ nhận được toàn bộ các hành vi cờ miễn phí, và bạn 'd khá nhiều vượt qua bất kỳ thử nghiệm chức năng mà bạn có thể ném vào nó. –

+0

Cảm ơn nhận xét. Tôi đã thêm các cờ tùy chọn. –

1

Tôi đang trong quá trình đóng gói Ruby Backport cho niềng răng hỗ trợ globbing. Dưới đây là những phần thiết yếu của giải pháp mà:

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 

Giải pháp này sẽ đưa vào tài khoản của những lá cờ khác nhau có sẵn cho File::fnmatch chức năng, và sử dụng các mô hình glob để xây dựng một phù hợp Regexp để phù hợp với các tính năng. Với giải pháp này, các xét nghiệm này có thể chạy thành công:

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 

Các fnmatch_with_braces_glob (và ? biến thể) sẽ được vá ở vị trí của fnmatch, vì vậy rằng Ruby đang 2.0.0-compliant sẽ làm việc với các phiên bản của Ruby trước đó, như tốt. Vì lý do rõ ràng, mã được hiển thị ở trên không bao gồm một số cải tiến về hiệu suất, kiểm tra đối số hoặc mã phát hiện và tính năng của Backports; những điều này rõ ràng sẽ được bao gồm trong việc đệ trình thực tế cho dự án.

Tôi vẫn đang thử nghiệm một số trường hợp cạnh và tối ưu hóa hiệu suất cao; nó sẽ sẵn sàng để gửi rất sớm. Khi nó có sẵn trong bản phát hành Backports chính thức, tôi sẽ cập nhật trạng thái tại đây.

Lưu ý rằng hỗ trợ Dir::glob cũng sẽ xuất hiện cùng một lúc.

Các vấn đề liên quan