diff --git a/ChangeLog b/ChangeLog index 7212162947..bf648cf9f5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Sun Nov 4 10:19:03 2012 Nobuyoshi Nakada + + * dir.c (file_s_fnmatch): match with expanding braces if FNM_EXTGLOB + is set. [ruby-core:40037] [Feature #5422] + Sat Nov 3 23:38:15 2012 Tadayoshi Funaba * complex.c: modified doc. diff --git a/NEWS b/NEWS index 17b44b18a2..4190fe7a32 100644 --- a/NEWS +++ b/NEWS @@ -31,6 +31,11 @@ with all sufficient information, see the ChangeLog file. * aliased method: * ENV.to_h is a new alias for ENV.to_hash + * File: + * extended method: + * File.fnmatch? now expands braces in the pattern if + File::FNM_EXTGLOB option is given. + * Hash * added method: * added Hash#to_h as explicit conversion method, like Array#to_a. diff --git a/dir.c b/dir.c index 11e3028813..d4b3dd3692 100644 --- a/dir.c +++ b/dir.c @@ -85,6 +85,7 @@ char *strchr(char*,char); #define FNM_PATHNAME 0x02 #define FNM_DOTMATCH 0x04 #define FNM_CASEFOLD 0x08 +#define FNM_EXTGLOB 0x10 #if CASEFOLD_FILESYSTEM #define FNM_SYSCASE FNM_CASEFOLD #else @@ -1912,6 +1913,15 @@ dir_entries(int argc, VALUE *argv, VALUE io) return rb_ensure(rb_Array, dir, dir_close, dir); } +static int +fnmatch_brace(const char *pattern, VALUE val, void *enc) +{ + struct brace_args *arg = (struct brace_args *)val; + VALUE path = arg->value; + + return (fnmatch(pattern, enc, RSTRING_PTR(path), arg->flags) == 0); +} + /* * call-seq: * File.fnmatch( pattern, path, [flags] ) -> (true or false) @@ -2008,9 +2018,21 @@ file_s_fnmatch(int argc, VALUE *argv, VALUE obj) StringValue(pattern); FilePathStringValue(path); - if (fnmatch(RSTRING_PTR(pattern), rb_enc_get(pattern), RSTRING_PTR(path), - flags) == 0) - return Qtrue; + if (flags & FNM_EXTGLOB) { + struct brace_args args; + + args.value = path; + args.flags = flags; + if (ruby_brace_expand(RSTRING_PTR(pattern), flags, fnmatch_brace, + (VALUE)&args, rb_enc_get(pattern)) > 0) + return Qtrue; + } + else { + if (fnmatch(RSTRING_PTR(pattern), rb_enc_get(pattern), RSTRING_PTR(path), + flags) == 0) + return Qtrue; + } + RB_GC_GUARD(pattern); return Qfalse; } @@ -2111,5 +2133,6 @@ Init_Dir(void) rb_file_const("FNM_PATHNAME", INT2FIX(FNM_PATHNAME)); rb_file_const("FNM_DOTMATCH", INT2FIX(FNM_DOTMATCH)); rb_file_const("FNM_CASEFOLD", INT2FIX(FNM_CASEFOLD)); + rb_file_const("FNM_EXTGLOB", INT2FIX(FNM_EXTGLOB)); rb_file_const("FNM_SYSCASE", INT2FIX(FNM_SYSCASE)); } diff --git a/test/ruby/envutil.rb b/test/ruby/envutil.rb index 3e04f55c0c..780196a438 100644 --- a/test/ruby/envutil.rb +++ b/test/ruby/envutil.rb @@ -229,7 +229,7 @@ module Test AssertFile end - class << (AssertFile = Object.new) + class << (AssertFile = Struct.new(:message).new) include Assertions def assert_file_predicate(predicate, *args) if /\Anot_/ =~ predicate @@ -241,9 +241,14 @@ module Test mesg = "Expected file " << args.shift.inspect mesg << mu_pp(args) unless args.empty? mesg << "#{neg} to be #{predicate}" + mesg << " #{message}" if message assert(result, mesg) end alias method_missing assert_file_predicate + + def for(message) + clone.tap {|a| a.message = message} + end end end end diff --git a/test/ruby/test_fnmatch.rb b/test/ruby/test_fnmatch.rb index e5f5ba6a4f..d186638a7b 100644 --- a/test/ruby/test_fnmatch.rb +++ b/test/ruby/test_fnmatch.rb @@ -1,4 +1,5 @@ require 'test/unit' +require_relative 'envutil' class TestFnmatch < Test::Unit::TestCase @@ -103,4 +104,9 @@ class TestFnmatch < Test::Unit::TestCase assert(File.fnmatch('**/foo', 'c:/root/foo', File::FNM_PATHNAME)) end + def test_extglob + feature5422 = '[ruby-core:40037]' + assert_file.for(feature5422).not_fnmatch?( "{.g,t}*", ".gem") + assert_file.for(feature5422).fnmatch?("{.g,t}*", ".gem", File::FNM_EXTGLOB) + end end