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