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