egedit: do not save cursor movement in undo -- this is my stupid habit, and it comple...
[iv.d.git] / glob.d
blob2a749e23beaf76ae0ae5dbd056de8c0e92c33136
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 module iv.glob /*is aliced*/;
18 import iv.alice;
21 ////////////////////////////////////////////////////////////////////////////////
22 // low-level interface
24 import core.sys.posix.sys.stat : stat_t;
25 import core.sys.posix.dirent : dirent;
26 //import core.sys.posix.config : __USE_FILE_OFFSET64;
27 import core.sys.linux.config : __USE_GNU, __USE_MISC;
29 //static assert(__USE_MISC);
31 nothrow @trusted @nogc extern(C) {
33 /* Bits set in the FLAGS argument to `glob'. */
34 enum {
35 GLOB_ERR = (1 << 0), /* Return on read errors. */
36 GLOB_MARK = (1 << 1), /* Append a slash to each name. */
37 GLOB_NOSORT = (1 << 2), /* Don't sort the names. */
38 GLOB_DOOFFS = (1 << 3), /* Insert PGLOB->gl_offs NULLs. */
39 GLOB_NOCHECK = (1 << 4), /* If nothing matches, return the pattern. */
40 GLOB_APPEND = (1 << 5), /* Append to results of a previous call. */
41 GLOB_NOESCAPE = (1 << 6), /* Backslashes don't quote metacharacters. */
42 GLOB_PERIOD = (1 << 7), /* Leading `.' can be matched by metachars. */
45 /* posix-2 extensions */
46 static if (__USE_MISC) enum {
47 GLOB_MAGCHAR = (1 << 8), /* Set in gl_flags if any metachars seen. */
48 GLOB_ALTDIRFUNC = (1 << 9), /* Use gl_opendir et al functions. */
49 GLOB_BRACE = (1 << 10), /* Expand "{a,b}" to "a" "b". */
50 GLOB_NOMAGIC = (1 << 11), /* If no magic chars, return the pattern. */
51 GLOB_TILDE = (1 << 12), /* Expand ~user and ~ to home directories. */
52 GLOB_ONLYDIR = (1 << 13), /* Match only directories. */
53 GLOB_TILDE_CHECK = (1 << 14), /* Like GLOB_TILDE but return an error if the user name is not available. */
56 /* Error returns from `glob'. */
57 enum {
58 GLOB_OK = 0,
59 GLOB_NOERROR = 0,
60 GLOB_NOSPACE = 1, /* Ran out of memory. */
61 GLOB_ABORTED = 2, /* Read error. */
62 GLOB_NOMATCH = 3, /* No matches found. */
63 GLOB_NOSYS = 4, /* Not implemented. */
65 /* Previous versions of this file defined GLOB_ABEND instead of
66 GLOB_ABORTED. Provide a compatibility definition here. */
67 static if (__USE_GNU) enum GLOB_ABEND = GLOB_ABORTED;
70 struct glob_t {
71 usize gl_pathc;
72 char** gl_pathv;
73 usize gl_offs;
74 int gl_flags;
76 /* If the GLOB_ALTDIRFUNC flag is set, the following functions
77 are used instead of the normal file access functions. */
78 static if (__USE_GNU) {
79 void function (void*) gl_closedir;
80 dirent* function (void*) gl_readdir;
81 void* function (const char *) gl_opendir;
82 int function (const(char)*, stat_t*) gl_lstat;
83 int function (const(char)*, stat_t*) gl_stat;
84 } else {
85 void function (void*) gl_closedir;
86 void* function (void*) gl_readdir;
87 void* function (const char *) gl_opendir;
88 int function (const(char)*, void*) gl_lstat;
89 int function (const(char)*, void*) gl_stat;
93 alias GlobErrFunc = int function (const(char)* epath, int eerrno) @trusted nothrow @nogc;
95 /* Do glob searching for PATTERN, placing results in PGLOB.
96 The bits defined above may be set in FLAGS.
97 If a directory cannot be opened or read and ERRFUNC is not nil,
98 it is called with the pathname that caused the error, and the
99 `errno' value from the failing call; if it returns non-zero
100 `glob' returns GLOB_ABEND; if it returns zero, the error is ignored.
101 If memory cannot be allocated for PGLOB, GLOB_NOSPACE is returned.
102 Otherwise, `glob' returns zero. */
103 int glob (const(char)* pattern, int flags, GlobErrFunc errfunc, glob_t* pglob);
105 /* Free storage allocated in PGLOB by a previous `glob' call. */
106 void globfree (glob_t* pglob);
108 /* Return nonzero if PATTERN contains any metacharacters.
109 Metacharacters can be quoted with backslashes if QUOTE is nonzero.
111 This function is not part of the interface specified by POSIX.2
112 but several programs want to use it. */
113 static if (__USE_GNU) int glob_pattern_p (const(char)* __pattern, int __quote);
117 ////////////////////////////////////////////////////////////////////////////////
118 // high-level interface
119 struct Glob {
120 private import std.traits;
122 @trusted:
123 private int opApplyX(string dir, DG) (scope DG dg)
124 if (isCallable!DG &&
125 (ParameterTypeTuple!DG.length == 1 &&
126 (is(Unqual!(ParameterTypeTuple!DG[0]) : Item)) ||
127 is(ParameterTypeTuple!DG[0] == string) ||
128 is(ParameterTypeTuple!DG[0] == const(char)[]) ||
129 is(ParameterTypeTuple!DG[0] == const char[]) ||
130 is(ParameterTypeTuple!DG[0] == char[])) ||
131 (ParameterTypeTuple!DG.length == 2 && isIntegral!(ParameterTypeTuple!DG[0]) &&
132 (is(Unqual!(ParameterTypeTuple!DG[1]) : Item) ||
133 is(ParameterTypeTuple!DG[1] == string) ||
134 is(ParameterTypeTuple!DG[1] == const(char)[]) ||
135 is(ParameterTypeTuple!DG[1] == const char[]) ||
136 is(ParameterTypeTuple!DG[1] == char[])))
139 alias ptt = ParameterTypeTuple!DG;
140 alias xarg = ptt[ptt.length-1];
141 int res = 0;
142 enum foreachBody = q{
143 static if (is(Unqual!(xarg) : Item)) {
144 auto it = Item(ge, idx);
145 } else {
146 // it's ok to cast here
147 auto it = cast(xarg)getName(idx).dup;
149 static if (ptt.length == 2) {
150 static if (is(typeof(idx) == ptt[0])) {
151 res = dg(idx, it);
152 } else {
153 auto i = cast(ptt[0])idx;
154 res = dg(i, it);
156 } else {
157 res = dg(it);
159 if (res) break;
161 static if (dir == "normal") {
162 foreach (usize idx; 0..ge.gb.gl_pathc) { mixin(foreachBody); }
163 } else static if (dir == "reverse") {
164 foreach_reverse (usize idx; 0..ge.gb.gl_pathc) { mixin(foreachBody); }
165 } else {
166 static assert(false, "wtf?!");
168 return res;
171 auto opApply(Args...) (Args args) { return opApplyX!("normal", Args)(args); }
172 auto opApplyReverse(Args...) (Args args) { return opApplyX!("reverse", Args)(args); }
174 nothrow @nogc: // ah, let's dance!
175 private:
176 this (globent* ange, usize idx=0) {
177 ge = ange;
178 Glob.incref(ge);
181 public:
182 this (const(char)[] pattern, int flags=GLOB_BRACE|GLOB_TILDE_CHECK) {
183 // remove bad flags, add good flags
184 flags &= ~(GLOB_DOOFFS|GLOB_APPEND);
185 static if (__USE_MISC) flags &= ~(GLOB_MAGCHAR|GLOB_ALTDIRFUNC);
186 import core.stdc.stdlib : malloc;
187 import core.stdc.string : memset;
188 import core.exception : onOutOfMemoryErrorNoGC;
189 import std.internal.cstring : tempCString;
190 ge = cast(globent*)malloc(globent.sizeof);
191 if (ge is null) onOutOfMemoryErrorNoGC();
192 version(iv_glob_debug) { import core.stdc.stdio : stdout, fprintf; fprintf(stdout, "new %p\n", ge); }
193 memset(ge, globent.sizeof, 0); // just in case
194 ge.refcount = 1;
195 ge.res = .glob(pattern.tempCString, flags|GLOB_ERR, null, &ge.gb);
198 this (this) { Glob.incref(ge); }
200 ~this () { Glob.decref(ge); }
202 // note that "no match" is error too!
203 @property bool error () pure const { return (ge.res != 0); }
204 @property bool nomatch () pure const { return (ge.res == GLOB_NOMATCH); }
206 @property usize length () pure const { return (idx < ge.gb.gl_pathc ? ge.gb.gl_pathc-idx : 0); }
207 alias opDollar = length;
209 void rewind () { idx = 0; }
211 @property bool empty () pure const { return (idx >= ge.gb.gl_pathc); }
212 @property auto save () { return Glob(ge, idx); }
213 void popFront () { if (idx < ge.gb.gl_pathc) ++idx; }
215 @property auto front () {
216 if (idx >= ge.gb.gl_pathc) {
217 import core.exception : RangeError;
218 throw staticError!RangeError();
220 return Item(ge, idx);
223 auto opIndex (usize idx) {
224 if (idx >= ge.gb.gl_pathc) {
225 import core.exception : RangeError;
226 throw staticError!RangeError();
228 return Item(ge, idx);
231 private:
232 static struct globent {
233 usize refcount;
234 glob_t gb;
235 int res;
238 public static struct Item {
239 public import core.sys.posix.sys.types : INode = ino_t;
240 private import core.sys.posix.sys.stat;
241 nothrow @trusted @nogc: // ah, let's dance!
242 private:
243 globent* ge;
244 usize idx;
245 stat_t st;
246 bool statvalid;
248 this (globent* ange, usize anidx) {
249 ge = ange;
250 idx = anidx;
251 statvalid = false; // just in case
252 Glob.incref(ge);
255 private:
256 bool updateStat () {
257 if (!statvalid && idx < ge.gb.gl_pathc) {
258 if (stat(ge.gb.gl_pathv[idx], &st) == 0) {
259 statvalid = true;
260 return true;
263 return false;
266 public:
267 this (this) { assert(ge !is null); Glob.incref(ge); }
268 ~this () { assert(ge !is null); Glob.decref(ge); }
270 @property usize index () pure const { return idx; }
271 @property usize length () pure const { return ge.gb.gl_pathc; }
273 // WARNING! this can escape!
274 @property const(char)[] name () pure const return {
275 if (idx < ge.gb.gl_pathc) {
276 usize pos = 0;
277 auto ptr = ge.gb.gl_pathv[idx];
278 while (ptr[pos]) ++pos;
279 return ptr[0..pos];
280 } else {
281 return "";
285 // WARNING! this can escape!
286 @property const(char)[] basename () pure const return {
287 if (idx < ge.gb.gl_pathc) {
288 usize pos = 0;
289 auto ptr = ge.gb.gl_pathv[idx];
290 while (ptr[pos]) ++pos;
291 usize epos = pos;
292 while (pos > 0 && ptr[pos-1] != '/') --pos;
293 return ptr[pos..epos];
294 } else {
295 return "";
299 @property bool prev () { if (idx > 0) { statvalid = false; --idx; return true; } else return false; }
300 @property bool next () { if (idx < ge.gb.gl_pathc) { statvalid = false; ++idx; return true; } else return false; }
302 bool getStat (ref stat_t outst) const {
303 if (idx < ge.gb.gl_pathc) {
304 if (!statvalid) {
305 stat_t st = void;
306 if (stat(ge.gb.gl_pathv[idx], &st) == 0) { outst = st; return true; }
307 } else {
308 outst = st;
309 return true;
312 return false;
315 bool getStat (ref stat_t outst) {
316 if (updateStat()) { outst = st; return true; }
317 return false;
320 uint mode () const {
321 stat_t st = void;
322 return (getStat(st) ? st.st_mode : 0);
325 uint mode () {
326 return (updateStat() ? st.st_mode : 0);
329 @property bool isFile () inout { pragma(inline, true); return ((mode&S_IFREG) != 0); } // symlinks are regular files too!
330 @property bool isDir () inout { pragma(inline, true); return ((mode&S_IFDIR) != 0); }
331 @property bool isLink () inout { pragma(inline, true); return ((mode&S_IFLNK) == S_IFLNK); }
333 @property ulong size () const {
334 stat_t st = void;
335 return (getStat(st) ? st.st_size : 0);
338 @property ulong size () {
339 return (updateStat() ? st.st_size : 0);
342 @property INode inode () const {
343 stat_t st = void;
344 return (getStat(st) ? st.st_ino : 0);
347 @property INode inode () {
348 return (updateStat() ? st.st_ino : 0);
352 static void incref (globent* ge) @safe nothrow @nogc {
353 pragma(inline, true);
354 assert(ge !is null);
355 ++ge.refcount;
358 static void decref (globent* ge) @trusted nothrow @nogc {
359 pragma(inline, true);
360 assert(ge !is null);
361 if (--ge.refcount == 0) {
362 version(iv_glob_debug) { import core.stdc.stdio : stdout, fprintf; fprintf(stdout, "freeing %p\n", ge); }
363 import core.stdc.stdlib : free;
364 globfree(&ge.gb);
365 free(ge);
369 // WARNING! this can escape!
370 @property const(char)[] getName (usize idx) pure const return {
371 usize pos = 0;
372 auto ptr = ge.gb.gl_pathv[idx];
373 while (ptr[pos]) ++pos;
374 return ptr[0..pos];
377 private:
378 globent* ge;
379 usize idx; // current position in range
383 ////////////////////////////////////////////////////////////////////////////////
384 // shamelessly borrowed from `core.exception`
385 // TLS storage shared for all errors, chaining might create circular reference
386 private void[128] stestore_;
388 // only Errors for now as those are rarely chained
389 private T staticError(T, Args...) (auto ref Args args) @nogc if (is(T : Error)) {
390 // pure hack, what we actually need is @noreturn and allow to call that in pure functions
391 static T get () {
392 static assert(__traits(classInstanceSize, T) <= stestore_.length, T.stringof~" is too large for staticError()");
393 stestore_[0..__traits(classInstanceSize, T)] = typeid(T).initializer[];
394 return cast(T)stestore_.ptr;
396 auto res = (cast(T function () pure nothrow @trusted @nogc) &get)();
397 void doInit () { res.__ctor(args); }
398 void initIt (scope void delegate () dg) {
399 auto xinit = cast(void delegate () pure nothrow @trusted @nogc)dg;
400 xinit();
402 initIt(&doInit);
403 return res;