1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // severely outdated case-insensitive filesystem interface
19 module iv
.file
/*is aliced*/;
22 private import std
.stdio
: File
;
23 private import iv
.strex
: indexOf
; // rdmd sux!
27 * get path to real disk file, do case-insensitive search if necessary.
28 * assuming that disk names are in koi8-u.
33 * allOk = success flag
36 * found path (full or partial if `allOk` is not null)
37 * null path (if there is no such path and `allOk` is null)
40 * it shouldn't, but expandTilde() is not nothrow, for example
42 string
getPathCI (string path
, bool* allOk
=null) @trusted {
43 import std
.file
: dirEntries
, exists
, SpanMode
;
44 import std
.path
: baseName
, buildNormalizedPath
, buildPath
, CaseSensitive
;
45 import std
.path
: dirName
, expandTilde
, filenameCmp
, isRooted
, pathSplitter
;
47 if (allOk
!is null) *allOk
= false;
48 path
= path
.expandTilde
;
52 if (allOk
!is null) *allOk
= true;
56 // alas, traverse dirs
58 foreach (/*auto*/ d
; path
.dirName
.expandTilde
.buildNormalizedPath
.pathSplitter
) {
59 if (dir
.length
== 0 && d
.isRooted
) {
62 string realDir
= findNameCI(dir
, d
, true);
63 if (realDir
is null) return (allOk
!is null ? dir
: null);
64 dir
= buildPath(dir
, realDir
);
68 // now try the last part, filename
69 string pn
= path
.baseName
;
70 foreach (string fn
; dirEntries((dir
.length
> 0 ? dir
: "."), SpanMode
.shallow
)) {
71 string n
= fn
.baseName
;
72 if (filenameCmp
!(CaseSensitive
.no
)(n
, pn
) == 0) {
73 if (allOk
!is null) *allOk
= true;
74 return (dir
.length
== 0 ? n
: fn
);
78 // no filename was found
79 return (allOk
!is null ? dir
: null);
83 // ////////////////////////////////////////////////////////////////////////// //
84 // returns only file name, without path
85 private string
findNameCI (string dir
, in char[] name
, bool wantDir
) @trusted {
86 import std
.file
: exists
, dirEntries
, SpanMode
, isFile
, isDir
;
87 import std
.path
: baseName
, buildPath
, filenameCmp
, CaseSensitive
;
88 if (dir
.length
== 0) dir
= ".";
89 string fullName
= buildPath(dir
, name
);
90 if (fullName
.exists
) {
91 if ((wantDir
&& fullName
.isDir
) ||
(!wantDir
&& fullName
.isFile
)) return name
.idup
;
93 foreach (string fn
; dirEntries(dir
, SpanMode
.shallow
)) {
94 string n
= fn
.baseName
;
95 if (filenameCmp
!(CaseSensitive
.no
)(n
, name
) == 0) {
96 if ((wantDir
&& fn
.isDir
) ||
(!wantDir
&& fn
.isFile
)) return n
;
104 * open file using case-insensitive name in read-only mode.
107 * fileName = file name
108 * diskName = found disk file name on success (can be relative or absolute)
114 * Exception on 'file not found'
116 File
openCI (string fileName
, out string diskName
) @trusted {
117 import std
.file
: exists
, isFile
;
122 if (fileName
.exists
&& fileName
.isFile
) {
123 try return File(fileName
); catch (Exception
) {}
128 foreach (/*auto*/ d
; fileName
.dirName
.expandTilde
.buildNormalizedPath
.pathSplitter
) {
129 if (dir
.length
== 0 && d
.isRooted
) {
132 string realDir
= findNameCI(dir
, d
, true);
133 if (realDir
is null) return File(fileName
); // throw error
134 dir
= buildPath(dir
, realDir
);
138 string name
= findNameCI(dir
, fileName
.baseName
, false);
139 if (name
is null) return File(fileName
); // throw error
141 diskName
= buildPath(dir
, name
);
142 return File(diskName
);
147 * open file using case-insensitive name in read-only mode.
150 * fileName = file name
156 * Exception on 'file not found'
158 File
openCI (string fileName
) @trusted {
160 return openCI(fileName
, dn
);
164 version(test_file
) unittest {
165 import std
.file
, std
.path
, std
.stdio
;
166 string md
= getcwd().dirName
;
170 r
= getPathCI(md
~"/IV/file.D", &ok
);
172 r
= getPathCI(md
~"/IV/filez.D", &ok
);
174 r
= getPathCI(md
~"/IVz/file.D", &ok
);
176 writeln(getPathCI(md
~"/IV/file.D"));
177 writeln(getPathCI(md
~"/IV/filez.D"));
178 writeln(getPathCI(md
~"/IVz/file.D"));
182 // the following code was taken from https://github.com/nordlow/justd/blob/master/bylinefast.d
184 * Reads by line in an efficient way (10 times faster than File.byLine from
185 * std.stdio). This is accomplished by reading entire buffers (fgetc() is not
186 * used), and allocating as little as possible.
188 * The char \n is considered as default separator, removing the previous \r if
191 * The \n is never returned. The \r is not returned if it was
192 * part of a \r\n (but it is returned if it was by itself).
194 * The returned string is always a substring of a temporary buffer, that must
195 * not be stored. If necessary, you must use str[] or .dup or .idup to copy to
196 * another string. DIP-25 return qualifier is used in front() to add extra
197 * checks in @safe callers of front().
201 * File f = File("file.txt");
202 * foreach (string line; ByLineFast(f)) {
205 * string copy = line[];
208 * The file isn't closed when done iterating, unless it was the only reference to
209 * the file (same as std.stdio.byLine). (example: ByLineFast(File("file.txt"))).
211 struct ByLineFast(Char
, Terminator
) {
214 bool firstCall
= true;
217 const string separator
;
220 this (File f
, bool keepTerminator
=false, string separator
="\n", uint bufferSize
=4096) @safe {
221 assert(bufferSize
> 0);
223 this.separator
= separator
;
224 this.keepTerminator
= keepTerminator
;
225 buffer
.length
= bufferSize
;
228 @property bool empty () const @trusted {
229 import std
.stdio
: fgetc
, ungetc
;
230 // Its important to check "line !is null" instead of
231 // "line.length != 0", otherwise, no empty lines can
232 // be returned, the iteration would be closed.
233 if (line
!is null) return false;
235 // Clean the buffer to avoid pointer false positives:
236 (cast(char[])buffer
)[] = 0;
239 // First read. Determine if it's empty and put the char back.
240 auto mutableFP
= (cast(File
*)&file
).getFP();
241 const c
= fgetc(mutableFP
);
243 // Clean the buffer to avoid pointer false positives:
244 (cast(char[])buffer
)[] = 0;
247 if (ungetc(c
, mutableFP
) != c
) assert(false, "Bug in cstdlib implementation");
251 @property char[] front() @safe /*return*//*DIP-25*/ {
259 void popFront() @trusted {
260 import iv
.strex
: indexOf
;
262 if (strBuffer
.length
== 0) {
263 strBuffer
= file
.rawRead(buffer
);
264 if (strBuffer
.length
== 0) {
271 const pos
= indexOf(strBuffer
, this.separator
);
273 if (pos
!= 0 && strBuffer
[pos
-1] == '\r') {
274 line
= strBuffer
[0..pos
-1];
276 line
= strBuffer
[0..pos
];
278 // Pop the line, skipping the terminator:
279 strBuffer
= strBuffer
[pos
+1..$];
281 // More needs to be read here. Copy the tail of the buffer
282 // to the beginning, and try to read with the empty part of
284 // If no buffer was left, extend the size of the buffer before
285 // reading. If the file has ended, then the line is the entire
287 if (strBuffer
.ptr
!= buffer
.ptr
) {
288 import core
.stdc
.string
: memmove
;
289 // Must use memmove because there might be overlap
290 memmove(buffer
.ptr
, strBuffer
.ptr
, strBuffer
.length
*char.sizeof
);
292 const spaceBegin
= strBuffer
.length
;
293 if (strBuffer
.length
== buffer
.length
) {
294 // Must extend the buffer to keep reading.
295 assumeSafeAppend(buffer
);
296 buffer
.length
= buffer
.length
*2;
298 const readPart
= file
.rawRead(buffer
[spaceBegin
..$]);
299 if (readPart
.length
== 0) {
300 // End of the file. Return whats in the buffer.
301 // The next popFront() will try to read again, and then
302 // mark empty condition.
303 if (spaceBegin
!= 0 && buffer
[spaceBegin
-1] == '\r') {
304 line
= buffer
[0..spaceBegin
-1];
306 line
= buffer
[0..spaceBegin
];
311 strBuffer
= buffer
[0..spaceBegin
+readPart
.length
];
312 // Now that we have new data in strBuffer, we can go on.
313 // If a line isn't found, the buffer will be extended again to read more.
319 auto byLineFast(Terminator
=char, Char
=char) (File f
,
320 bool keepTerminator
=false,
321 string separator
="\n",
322 uint bufferSize
=4096) @safe // TODO lookup preferred block type
324 return ByLineFast
!(Char
, Terminator
)(f
, keepTerminator
, separator
, bufferSize
);
328 version(test_file
) unittest {
329 import std
.stdio
: File
, writeln
;
330 import std
.algorithm
.searching
: count
;
331 const path
= "/etc/passwd";
332 assert(File(path
).byLineFast
.count
== File(path
).byLine
.count
);
335 version(test_file
) @safe unittest {
336 import std
.stdio
: File
;
337 const path
= "/etc/passwd";
339 foreach (line
; File(path
).byLineFast
) {
340 mutable_line
= line
; // TODO this should fail
343 auto byline
= File(path
).byLineFast
;
344 mutable_line
= byline
.front
; // TODO this should fail
348 version(none
) unittest {
349 import std
.stdio
: File
, writeln
;
350 import std
.algorithm
.searching
: count
;
351 const path
= "/home/ketmar/muldict.txt";
352 import std
.datetime
: StopWatch
;
357 const c1
= File(path
).byLine
.count
;
360 writeln("byLine: ", d1
, "msecs");
365 const c2
= File(path
).byLineFast
.count
;
368 writeln("byLineFast: ", d2
, "msecs");
370 writeln("Speed-Up: ", d1
/ d2
);