1 // Written in the D programming language. 2 3 /** This module contains stripExt(). 4 5 Authors: 6 Lars Tandle Kyllingstad, 7 $(WEB digitalmars.com, Walter Bright), 8 Grzegorz Adam Hankiewicz, 9 Thomas Kühne, 10 $(WEB erdani.org, Andrei Alexandrescu) 11 Copyright: 12 Copyright (c) 2000-2014, the authors. All rights reserved. 13 License: 14 $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0) 15 Source: 16 $(SARGONSRC src/sargon/_path/_stripext.d) 17 */ 18 module sargon.path.stripext; 19 20 21 import std.range; 22 import std.traits; 23 import std.utf; 24 25 /** Returns slice of $(D path[]) with the extension stripped off. 26 27 stripExt is an algorithm that does not allocate nor throw, it is $(D pure) and $(D @safe). 28 29 Params: 30 path = a $(WEB dlang.org/phobos/std_range.html#.isRandomAccessRange, RandomAccessRange) that can be sliced. 31 32 Returns: 33 a slice of path 34 35 Examples: 36 --- 37 assert (stripExt("file") == "file"); 38 assert (stripExt("file.ext") == "file"); 39 assert (stripExt("file.ext1.ext2") == "file.ext1"); 40 assert (stripExt("file.") == "file"); 41 assert (stripExt(".file") == ".file"); 42 assert (stripExt(".file.ext") == ".file"); 43 assert (stripExt("dir/file.ext") == "dir/file"); 44 45 { 46 import std.internal.scopebuffer; 47 48 char[10] tmpbuf = void; 49 auto buf = ScopeBuffer!char(tmpbuf); 50 scope(exit) buf.free(); 51 52 buf.length = 0; 53 "file.ext".byChar().stripExt().copy(&buf); 54 assert(buf[] == "file"); 55 } 56 --- 57 */ 58 auto stripExt(R)(R path) 59 { 60 static if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || 61 isNarrowString!R) 62 { 63 auto i = extSeparatorPos(path); 64 return (i == -1) ? path : path[0 .. i]; 65 } 66 else 67 { 68 import core.stdc.stdio; 69 70 alias tchar = Unqual!(ElementEncodingType!R); 71 static struct stripExtImpl 72 { 73 this(ref R r) 74 { 75 this.r = r; 76 } 77 78 @property bool empty() 79 { 80 if (haveChar) 81 return false; 82 while (1) 83 { 84 if (i < nLeft) 85 { 86 ch = buf[i]; 87 ++i; 88 lastIsSeparator = isSeparator(ch); 89 assert(lastIsSeparator == false); 90 haveChar = true; 91 return false; 92 } 93 if (r.empty) 94 return true; 95 96 ch = r.front; 97 r.popFront; 98 if (ch != '.' || 99 lastIsSeparator) 100 { 101 lastIsSeparator = isSeparator(ch); 102 haveChar = true; 103 return false; 104 } 105 nLeft = 0; 106 while (1) 107 { 108 buf[nLeft++] = ch; 109 if (r.empty) 110 { 111 nLeft = 0; 112 break; 113 } 114 ch = r.front; 115 if (ch == '.' || 116 isSeparator(ch) || 117 nLeft == buf.length) 118 { 119 break; 120 } 121 r.popFront(); 122 } 123 i = 0; 124 } 125 } 126 127 @property auto front() 128 { 129 return ch; 130 } 131 132 void popFront() 133 { 134 haveChar = false; 135 } 136 137 private: 138 R r; 139 bool lastIsSeparator = true; 140 bool haveChar = false; 141 uint nLeft; 142 uint i; 143 tchar ch; 144 tchar[FILENAME_MAX] buf = void; 145 } 146 return stripExtImpl(path); 147 } 148 } 149 150 151 unittest 152 { 153 import std.algorithm; 154 import std.internal.scopebuffer; 155 156 import sargon.array.asinputrange; 157 158 assert (stripExt("file") == "file"); 159 assert (stripExt("file.ext"w) == "file"); 160 assert (stripExt("file.ext1.ext2"d) == "file.ext1"); 161 assert (stripExt(".foo".dup) == ".foo"); 162 assert (stripExt(".foo.ext"w.dup) == ".foo"); 163 164 assert (stripExt("dir/file"d.dup) == "dir/file"); 165 assert (stripExt("dir/file.ext") == "dir/file"); 166 assert (stripExt("dir/file.ext1.ext2"w) == "dir/file.ext1"); 167 assert (stripExt("dir/.foo"d) == "dir/.foo"); 168 assert (stripExt("dir/.foo.ext".dup) == "dir/.foo"); 169 170 { 171 char[10] tmpbuf = void; 172 auto buf = ScopeBuffer!char(tmpbuf); 173 scope(exit) buf.free(); 174 175 buf.length = 0; 176 "file.ext".byChar().stripExt().copy(&buf); 177 assert(buf[] == "file"); 178 } 179 180 void testrange(string f, string result) 181 { 182 char[50] s; 183 int i; 184 foreach (c; f.asInputRange().stripExt()) 185 { 186 s[i++] = c; 187 } 188 assert(s[0 .. i] == result); 189 } 190 testrange("file", "file"); 191 testrange("file.", "file"); 192 testrange("file.ext", "file"); 193 testrange("file.ext.", "file.ext"); 194 testrange("file.ext1.ext2", "file.ext1"); 195 testrange(".foo", ".foo"); 196 testrange("dir/file", "dir/file"); 197 testrange("dir/file.ext", "dir/file"); 198 testrange("dir/file.ext1.ext2", "dir/file.ext1"); 199 testrange("dir/.foo", "dir/.foo"); 200 testrange("dir/.foo.ext", "dir/.foo"); 201 202 { // various boundary conditions 203 auto r = "f.o".asInputRange.stripExt; 204 assert(!r.empty); 205 assert(!r.empty); 206 r.popFront(); 207 r.popFront(); 208 r.popFront(); 209 assert(r.empty); 210 } 211 212 version(Windows) 213 { 214 assert (stripExt("dir\\file") == "dir\\file"); 215 assert (stripExt("dir\\file.ext") == "dir\\file"); 216 assert (stripExt("dir\\file.ext1.ext2") == "dir\\file.ext1"); 217 assert (stripExt("dir\\.foo") == "dir\\.foo"); 218 assert (stripExt("dir\\.foo.ext") == "dir\\.foo"); 219 220 assert (stripExt("d:file") == "d:file"); 221 assert (stripExt("d:file.ext") == "d:file"); 222 assert (stripExt("d:file.ext1.ext2") == "d:file.ext1"); 223 assert (stripExt("d:.foo") == "d:.foo"); 224 assert (stripExt("d:.foo.ext") == "d:.foo"); 225 } 226 227 static assert (stripExt("file") == "file"); 228 static assert (stripExt("file.ext"w) == "file"); 229 } 230 231 /* Helper function that returns the position of the filename/extension 232 separator dot in path. If not found, returns -1. 233 */ 234 private ptrdiff_t extSeparatorPos(R)(const R path) 235 if (isRandomAccessRange!R && hasSlicing!R && isSomeChar!(ElementType!R) || 236 isNarrowString!R) 237 { 238 auto i = (cast(ptrdiff_t) path.length) - 1; 239 while (i >= 0 && !isSeparator(path[i])) 240 { 241 if (path[i] == '.' && i > 0 && !isSeparator(path[i-1])) return i; 242 --i; 243 } 244 return -1; 245 } 246 247 private bool isSeparator(dchar c) @safe pure nothrow @nogc 248 { 249 version (Windows) 250 return c == ':' || c == '/' || c == '\\'; 251 else 252 return c == '/'; 253 } 254