1 module dfmt.globmatch_editorconfig; 2 3 import std.path : CaseSensitive; 4 import std.range : isForwardRange, ElementEncodingType; 5 import std.traits : isSomeChar, isSomeString; 6 import std.range.primitives : empty, save, front, popFront; 7 import std.traits : Unqual; 8 import std.conv : to; 9 import std.path : filenameCharCmp, isDirSeparator; 10 11 // From std.path with changes: 12 // * changes meaning to match all characters except '/' 13 // ** added to take over the old meaning of * 14 bool globMatchEditorConfig(CaseSensitive cs = CaseSensitive.osDefault, C, Range)( 15 Range path, const(C)[] pattern) @safe pure nothrow 16 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) 17 && isSomeChar!C && is(Unqual!C == Unqual!(ElementEncodingType!Range))) 18 in 19 { 20 // Verify that pattern[] is valid 21 import std.algorithm : balancedParens; 22 23 assert(balancedParens(pattern, '[', ']', 0)); 24 assert(balancedParens(pattern, '{', '}', 0)); 25 } 26 body 27 { 28 alias RC = Unqual!(ElementEncodingType!Range); 29 30 static if (RC.sizeof == 1 && isSomeString!Range) 31 { 32 import std.utf : byChar; 33 34 return globMatchEditorConfig!cs(path.byChar, pattern); 35 } 36 else static if (RC.sizeof == 2 && isSomeString!Range) 37 { 38 import std.utf : byWchar; 39 40 return globMatchEditorConfig!cs(path.byWchar, pattern); 41 } 42 else 43 { 44 C[] pattmp; 45 foreach (ref pi; 0 .. pattern.length) 46 { 47 const pc = pattern[pi]; 48 switch (pc) 49 { 50 case '*': 51 if (pi < pattern.length - 1 && pattern[pi + 1] == '*') 52 { 53 if (pi + 2 == pattern.length) 54 return true; 55 for (; !path.empty; path.popFront()) 56 { 57 auto p = path.save; 58 if (globMatchEditorConfig!(cs, C)(p, pattern[pi + 2 .. pattern.length])) 59 return true; 60 } 61 return false; 62 } 63 else 64 { 65 if (pi + 1 == pattern.length) 66 return true; 67 for (; !path.empty; path.popFront()) 68 { 69 auto p = path.save; 70 //if (p[0].to!dchar.isDirSeparator() && !pattern[pi+1].isDirSeparator()) 71 // return false; 72 if (globMatchEditorConfig!(cs, C)(p, pattern[pi + 1 .. pattern.length])) 73 return true; 74 if (p[0].to!dchar.isDirSeparator()) 75 return false; 76 } 77 return false; 78 } 79 case '?': 80 if (path.empty) 81 return false; 82 path.popFront(); 83 break; 84 85 case '[': 86 if (path.empty) 87 return false; 88 auto nc = path.front; 89 path.popFront(); 90 auto not = false; 91 ++pi; 92 if (pattern[pi] == '!') 93 { 94 not = true; 95 ++pi; 96 } 97 auto anymatch = false; 98 while (1) 99 { 100 const pc2 = pattern[pi]; 101 if (pc2 == ']') 102 break; 103 if (!anymatch && (filenameCharCmp!cs(nc, pc2) == 0)) 104 anymatch = true; 105 ++pi; 106 } 107 if (anymatch == not) 108 return false; 109 break; 110 111 case '{': 112 // find end of {} section 113 auto piRemain = pi; 114 for (; piRemain < pattern.length && pattern[piRemain] != '}'; ++piRemain) 115 { 116 } 117 118 if (piRemain < pattern.length) 119 ++piRemain; 120 ++pi; 121 122 while (pi < pattern.length) 123 { 124 const pi0 = pi; 125 C pc3 = pattern[pi]; 126 // find end of current alternative 127 for (; pi < pattern.length && pc3 != '}' && pc3 != ','; ++pi) 128 { 129 pc3 = pattern[pi]; 130 } 131 132 auto p = path.save; 133 if (pi0 == pi) 134 { 135 if (globMatchEditorConfig!(cs, C)(p, pattern[piRemain .. $])) 136 { 137 return true; 138 } 139 ++pi; 140 } 141 else 142 { 143 /* Match for: 144 * pattern[pi0..pi-1] ~ pattern[piRemain..$] 145 */ 146 if (pattmp.ptr == null) // Allocate this only once per function invocation. 147 // Should do it with malloc/free, but that would make it impure. 148 pattmp = new C[pattern.length]; 149 150 const len1 = pi - 1 - pi0; 151 pattmp[0 .. len1] = pattern[pi0 .. pi - 1]; 152 153 const len2 = pattern.length - piRemain; 154 pattmp[len1 .. len1 + len2] = pattern[piRemain .. $]; 155 156 if (globMatchEditorConfig!(cs, C)(p, pattmp[0 .. len1 + len2])) 157 { 158 return true; 159 } 160 } 161 if (pc3 == '}') 162 { 163 break; 164 } 165 } 166 return false; 167 168 default: 169 if (path.empty) 170 return false; 171 if (filenameCharCmp!cs(pc, path.front) != 0) 172 return false; 173 path.popFront(); 174 break; 175 } 176 } 177 return path.empty; 178 } 179 } 180 181 unittest 182 { 183 assert(globMatchEditorConfig!(CaseSensitive.no)("foo", "Foo")); 184 assert(!globMatchEditorConfig!(CaseSensitive.yes)("foo", "Foo")); 185 186 assert(globMatchEditorConfig("foo", "*")); 187 assert(globMatchEditorConfig("foo.bar"w, "*"w)); 188 assert(globMatchEditorConfig("foo.bar"d, "*.*"d)); 189 assert(globMatchEditorConfig("foo.bar", "foo*")); 190 assert(globMatchEditorConfig("foo.bar"w, "f*bar"w)); 191 assert(globMatchEditorConfig("foo.bar"d, "f*b*r"d)); 192 assert(globMatchEditorConfig("foo.bar", "f???bar")); 193 assert(globMatchEditorConfig("foo.bar"w, "[fg]???bar"w)); 194 assert(globMatchEditorConfig("foo.bar"d, "[!gh]*bar"d)); 195 196 assert(!globMatchEditorConfig("foo", "bar")); 197 assert(!globMatchEditorConfig("foo"w, "*.*"w)); 198 assert(!globMatchEditorConfig("foo.bar"d, "f*baz"d)); 199 assert(!globMatchEditorConfig("foo.bar", "f*b*x")); 200 assert(!globMatchEditorConfig("foo.bar", "[gh]???bar")); 201 assert(!globMatchEditorConfig("foo.bar"w, "[!fg]*bar"w)); 202 assert(!globMatchEditorConfig("foo.bar"d, "[fg]???baz"d)); 203 assert(!globMatchEditorConfig("foo.di", "*.d")); // test issue 6634: triggered bad assertion 204 205 assert(globMatchEditorConfig("foo.bar", "{foo,bif}.bar")); 206 assert(globMatchEditorConfig("bif.bar"w, "{foo,bif}.bar"w)); 207 208 assert(globMatchEditorConfig("bar.foo"d, "bar.{foo,bif}"d)); 209 assert(globMatchEditorConfig("bar.bif", "bar.{foo,bif}")); 210 211 assert(globMatchEditorConfig("bar.fooz"w, "bar.{foo,bif}z"w)); 212 assert(globMatchEditorConfig("bar.bifz"d, "bar.{foo,bif}z"d)); 213 214 assert(globMatchEditorConfig("bar.foo", "bar.{biz,,baz}foo")); 215 assert(globMatchEditorConfig("bar.foo"w, "bar.{biz,}foo"w)); 216 assert(globMatchEditorConfig("bar.foo"d, "bar.{,biz}foo"d)); 217 assert(globMatchEditorConfig("bar.foo", "bar.{}foo")); 218 219 assert(globMatchEditorConfig("bar.foo"w, "bar.{ar,,fo}o"w)); 220 assert(globMatchEditorConfig("bar.foo"d, "bar.{,ar,fo}o"d)); 221 assert(globMatchEditorConfig("bar.o", "bar.{,ar,fo}o")); 222 223 assert(!globMatchEditorConfig("foo", "foo?")); 224 assert(!globMatchEditorConfig("foo", "foo[]")); 225 assert(!globMatchEditorConfig("foo", "foob")); 226 assert(!globMatchEditorConfig("foo", "foo{b}")); 227 228 assert(globMatchEditorConfig(`foo/foo\bar`, "f**b**r")); 229 assert(globMatchEditorConfig("foo", "**")); 230 assert(globMatchEditorConfig("foo/bar", "foo/bar")); 231 assert(globMatchEditorConfig("foo/bar", "foo/*")); 232 assert(globMatchEditorConfig("foo/bar", "*/bar")); 233 assert(globMatchEditorConfig("/foo/bar/gluu/sar.png", "**/sar.png")); 234 assert(globMatchEditorConfig("/foo/bar/gluu/sar.png", "**/*.png")); 235 assert(!globMatchEditorConfig("/foo/bar/gluu/sar.png", "*/sar.png")); 236 assert(!globMatchEditorConfig("/foo/bar/gluu/sar.png", "*/*.png")); 237 238 static assert(globMatchEditorConfig("foo.bar", "[!gh]*bar")); 239 }