cosmetix
[iv.d.git] / glob.d
blob279c49480e1c423252af436ba4ecbdfa704689ca
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, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module iv.glob /*is aliced*/;
19 import iv.alice;
22 ////////////////////////////////////////////////////////////////////////////////
23 // low-level interface
25 import core.sys.posix.sys.stat : stat_t;
26 import core.sys.posix.dirent : dirent;
27 //import core.sys.posix.config : __USE_FILE_OFFSET64;
28 import core.sys.linux.config : __USE_GNU, __USE_MISC;
30 //static assert(__USE_MISC);
32 nothrow @trusted @nogc extern(C) {
34 /* Bits set in the FLAGS argument to `glob'. */
35 enum {
36 GLOB_ERR = (1 << 0), /* Return on read errors. */
37 GLOB_MARK = (1 << 1), /* Append a slash to each name. */
38 GLOB_NOSORT = (1 << 2), /* Don't sort the names. */
39 GLOB_DOOFFS = (1 << 3), /* Insert PGLOB->gl_offs NULLs. */
40 GLOB_NOCHECK = (1 << 4), /* If nothing matches, return the pattern. */
41 GLOB_APPEND = (1 << 5), /* Append to results of a previous call. */
42 GLOB_NOESCAPE = (1 << 6), /* Backslashes don't quote metacharacters. */
43 GLOB_PERIOD = (1 << 7), /* Leading `.' can be matched by metachars. */
46 /* posix-2 extensions */
47 static if (__USE_MISC) enum {
48 GLOB_MAGCHAR = (1 << 8), /* Set in gl_flags if any metachars seen. */
49 GLOB_ALTDIRFUNC = (1 << 9), /* Use gl_opendir et al functions. */
50 GLOB_BRACE = (1 << 10), /* Expand "{a,b}" to "a" "b". */
51 GLOB_NOMAGIC = (1 << 11), /* If no magic chars, return the pattern. */
52 GLOB_TILDE = (1 << 12), /* Expand ~user and ~ to home directories. */
53 GLOB_ONLYDIR = (1 << 13), /* Match only directories. */
54 GLOB_TILDE_CHECK = (1 << 14), /* Like GLOB_TILDE but return an error if the user name is not available. */
57 /* Error returns from `glob'. */
58 enum {
59 GLOB_OK = 0,
60 GLOB_NOERROR = 0,
61 GLOB_NOSPACE = 1, /* Ran out of memory. */
62 GLOB_ABORTED = 2, /* Read error. */
63 GLOB_NOMATCH = 3, /* No matches found. */
64 GLOB_NOSYS = 4, /* Not implemented. */
66 /* Previous versions of this file defined GLOB_ABEND instead of
67 GLOB_ABORTED. Provide a compatibility definition here. */
68 static if (__USE_GNU) enum GLOB_ABEND = GLOB_ABORTED;
71 struct glob_t {
72 usize gl_pathc;
73 char** gl_pathv;
74 usize gl_offs;
75 int gl_flags;
77 /* If the GLOB_ALTDIRFUNC flag is set, the following functions
78 are used instead of the normal file access functions. */
79 static if (__USE_GNU) {
80 void function (void*) gl_closedir;
81 dirent* function (void*) gl_readdir;
82 void* function (const char *) gl_opendir;
83 int function (const(char)*, stat_t*) gl_lstat;
84 int function (const(char)*, stat_t*) gl_stat;
85 } else {
86 void function (void*) gl_closedir;
87 void* function (void*) gl_readdir;
88 void* function (const char *) gl_opendir;
89 int function (const(char)*, void*) gl_lstat;
90 int function (const(char)*, void*) gl_stat;
94 alias GlobErrFunc = int function (const(char)* epath, int eerrno) @trusted nothrow @nogc;
96 /* Do glob searching for PATTERN, placing results in PGLOB.
97 The bits defined above may be set in FLAGS.
98 If a directory cannot be opened or read and ERRFUNC is not nil,
99 it is called with the pathname that caused the error, and the
100 `errno' value from the failing call; if it returns non-zero
101 `glob' returns GLOB_ABEND; if it returns zero, the error is ignored.
102 If memory cannot be allocated for PGLOB, GLOB_NOSPACE is returned.
103 Otherwise, `glob' returns zero. */
104 int glob (const(char)* pattern, int flags, GlobErrFunc errfunc, glob_t* pglob);
106 /* Free storage allocated in PGLOB by a previous `glob' call. */
107 void globfree (glob_t* pglob);
109 /* Return nonzero if PATTERN contains any metacharacters.
110 Metacharacters can be quoted with backslashes if QUOTE is nonzero.
112 This function is not part of the interface specified by POSIX.2
113 but several programs want to use it. */
114 static if (__USE_GNU) int glob_pattern_p (const(char)* __pattern, int __quote);
118 ////////////////////////////////////////////////////////////////////////////////
119 // high-level interface
120 struct Glob {
121 private import std.traits;
123 @trusted:
124 private int opApplyX(string dir, DG) (scope DG dg)
125 if (isCallable!DG &&
126 (ParameterTypeTuple!DG.length == 1 &&
127 (is(Unqual!(ParameterTypeTuple!DG[0]) : Item)) ||
128 is(ParameterTypeTuple!DG[0] == string) ||
129 is(ParameterTypeTuple!DG[0] == const(char)[]) ||
130 is(ParameterTypeTuple!DG[0] == const char[]) ||
131 is(ParameterTypeTuple!DG[0] == char[])) ||
132 (ParameterTypeTuple!DG.length == 2 && isIntegral!(ParameterTypeTuple!DG[0]) &&
133 (is(Unqual!(ParameterTypeTuple!DG[1]) : Item) ||
134 is(ParameterTypeTuple!DG[1] == string) ||
135 is(ParameterTypeTuple!DG[1] == const(char)[]) ||
136 is(ParameterTypeTuple!DG[1] == const char[]) ||
137 is(ParameterTypeTuple!DG[1] == char[])))
140 alias ptt = ParameterTypeTuple!DG;
141 alias xarg = ptt[ptt.length-1];
142 int res = 0;
143 enum foreachBody = q{
144 static if (is(Unqual!(xarg) : Item)) {
145 auto it = Item(ge, idx);
146 } else {
147 // it's ok to cast here
148 auto it = cast(xarg)getName(idx).dup;
150 static if (ptt.length == 2) {
151 static if (is(typeof(idx) == ptt[0])) {
152 res = dg(idx, it);
153 } else {
154 auto i = cast(ptt[0])idx;
155 res = dg(i, it);
157 } else {
158 res = dg(it);
160 if (res) break;
162 static if (dir == "normal") {
163 foreach (usize idx; 0..ge.gb.gl_pathc) { mixin(foreachBody); }
164 } else static if (dir == "reverse") {
165 foreach_reverse (usize idx; 0..ge.gb.gl_pathc) { mixin(foreachBody); }
166 } else {
167 static assert(false, "wtf?!");
169 return res;
172 auto opApply(Args...) (Args args) { return opApplyX!("normal", Args)(args); }
173 auto opApplyReverse(Args...) (Args args) { return opApplyX!("reverse", Args)(args); }
175 nothrow @nogc: // ah, let's dance!
176 private:
177 this (globent* ange, usize idx=0) {
178 ge = ange;
179 Glob.incref(ge);
182 public:
183 this (const(char)[] pattern, int flags=GLOB_BRACE|GLOB_TILDE_CHECK) {
184 // remove bad flags, add good flags
185 flags &= ~(GLOB_DOOFFS|GLOB_APPEND);
186 static if (__USE_MISC) flags &= ~(GLOB_MAGCHAR|GLOB_ALTDIRFUNC);
187 import core.stdc.stdlib : malloc;
188 import core.stdc.string : memset;
189 import core.exception : onOutOfMemoryErrorNoGC;
190 import std.internal.cstring : tempCString;
191 ge = cast(globent*)malloc(globent.sizeof);
192 if (ge is null) onOutOfMemoryErrorNoGC();
193 version(iv_glob_debug) { import core.stdc.stdio : stdout, fprintf; fprintf(stdout, "new %p\n", ge); }
194 memset(ge, globent.sizeof, 0); // just in case
195 ge.refcount = 1;
196 ge.res = .glob(pattern.tempCString, flags|GLOB_ERR, null, &ge.gb);
199 this (this) { Glob.incref(ge); }
201 ~this () { Glob.decref(ge); }
203 // note that "no match" is error too!
204 @property bool error () pure const { return (ge.res != 0); }
205 @property bool nomatch () pure const { return (ge.res == GLOB_NOMATCH); }
207 @property usize length () pure const { return (idx < ge.gb.gl_pathc ? ge.gb.gl_pathc-idx : 0); }
208 alias opDollar = length;
210 void rewind () { idx = 0; }
212 @property bool empty () pure const { return (idx >= ge.gb.gl_pathc); }
213 @property auto save () { return Glob(ge, idx); }
214 void popFront () { if (idx < ge.gb.gl_pathc) ++idx; }
216 @property auto front () {
217 if (idx >= ge.gb.gl_pathc) {
218 import core.exception : RangeError;
219 throw staticError!RangeError();
221 return Item(ge, idx);
224 auto opIndex (usize idx) {
225 if (idx >= ge.gb.gl_pathc) {
226 import core.exception : RangeError;
227 throw staticError!RangeError();
229 return Item(ge, idx);
232 private:
233 static struct globent {
234 usize refcount;
235 glob_t gb;
236 int res;
239 public static struct Item {
240 public import core.sys.posix.sys.types : INode = ino_t;
241 private import core.sys.posix.sys.stat;
242 nothrow @trusted @nogc: // ah, let's dance!
243 private:
244 globent* ge;
245 usize idx;
246 stat_t st;
247 bool statvalid;
249 this (globent* ange, usize anidx) {
250 ge = ange;
251 idx = anidx;
252 statvalid = false; // just in case
253 Glob.incref(ge);
256 private:
257 bool updateStat () {
258 if (!statvalid && idx < ge.gb.gl_pathc) {
259 if (stat(ge.gb.gl_pathv[idx], &st) == 0) {
260 statvalid = true;
261 return true;
264 return false;
267 public:
268 this (this) { assert(ge !is null); Glob.incref(ge); }
269 ~this () { assert(ge !is null); Glob.decref(ge); }
271 @property usize index () pure const { return idx; }
272 @property usize length () pure const { return ge.gb.gl_pathc; }
274 // WARNING! this can escape!
275 @property const(char)[] name () pure const return {
276 if (idx < ge.gb.gl_pathc) {
277 usize pos = 0;
278 auto ptr = ge.gb.gl_pathv[idx];
279 while (ptr[pos]) ++pos;
280 return ptr[0..pos];
281 } else {
282 return "";
286 // WARNING! this can escape!
287 @property const(char)[] basename () pure const return {
288 if (idx < ge.gb.gl_pathc) {
289 usize pos = 0;
290 auto ptr = ge.gb.gl_pathv[idx];
291 while (ptr[pos]) ++pos;
292 usize epos = pos;
293 while (pos > 0 && ptr[pos-1] != '/') --pos;
294 return ptr[pos..epos];
295 } else {
296 return "";
300 @property bool prev () { if (idx > 0) { statvalid = false; --idx; return true; } else return false; }
301 @property bool next () { if (idx < ge.gb.gl_pathc) { statvalid = false; ++idx; return true; } else return false; }
303 bool getStat (ref stat_t outst) const {
304 if (idx < ge.gb.gl_pathc) {
305 if (!statvalid) {
306 stat_t st = void;
307 if (stat(ge.gb.gl_pathv[idx], &st) == 0) { outst = st; return true; }
308 } else {
309 outst = st;
310 return true;
313 return false;
316 bool getStat (ref stat_t outst) {
317 if (updateStat()) { outst = st; return true; }
318 return false;
321 uint mode () const {
322 stat_t st = void;
323 return (getStat(st) ? st.st_mode : 0);
326 uint mode () {
327 return (updateStat() ? st.st_mode : 0);
330 @property bool isFile () inout { pragma(inline, true); return ((mode&S_IFREG) != 0); } // symlinks are regular files too!
331 @property bool isDir () inout { pragma(inline, true); return ((mode&S_IFDIR) != 0); }
332 @property bool isLink () inout { pragma(inline, true); return ((mode&S_IFLNK) == S_IFLNK); }
334 @property ulong size () const {
335 stat_t st = void;
336 return (getStat(st) ? st.st_size : 0);
339 @property ulong size () {
340 return (updateStat() ? st.st_size : 0);
343 @property INode inode () const {
344 stat_t st = void;
345 return (getStat(st) ? st.st_ino : 0);
348 @property INode inode () {
349 return (updateStat() ? st.st_ino : 0);
353 static void incref (globent* ge) @safe nothrow @nogc {
354 pragma(inline, true);
355 assert(ge !is null);
356 ++ge.refcount;
359 static void decref (globent* ge) @trusted nothrow @nogc {
360 pragma(inline, true);
361 assert(ge !is null);
362 if (--ge.refcount == 0) {
363 version(iv_glob_debug) { import core.stdc.stdio : stdout, fprintf; fprintf(stdout, "freeing %p\n", ge); }
364 import core.stdc.stdlib : free;
365 globfree(&ge.gb);
366 free(ge);
370 // WARNING! this can escape!
371 @property const(char)[] getName (usize idx) pure const return {
372 usize pos = 0;
373 auto ptr = ge.gb.gl_pathv[idx];
374 while (ptr[pos]) ++pos;
375 return ptr[0..pos];
378 private:
379 globent* ge;
380 usize idx; // current position in range
384 ////////////////////////////////////////////////////////////////////////////////
385 // shamelessly borrowed from `core.exception`
386 // TLS storage shared for all errors, chaining might create circular reference
387 private void[128] stestore_;
389 // only Errors for now as those are rarely chained
390 private T staticError(T, Args...) (auto ref Args args) @nogc if (is(T : Error)) {
391 // pure hack, what we actually need is @noreturn and allow to call that in pure functions
392 static T get () {
393 static assert(__traits(classInstanceSize, T) <= stestore_.length, T.stringof~" is too large for staticError()");
394 stestore_[0..__traits(classInstanceSize, T)] = typeid(T).initializer[];
395 return cast(T)stestore_.ptr;
397 auto res = (cast(T function () pure nothrow @trusted @nogc) &get)();
398 void doInit () { res.__ctor(args); }
399 void initIt (scope void delegate () dg) {
400 auto xinit = cast(void delegate () pure nothrow @trusted @nogc)dg;
401 xinit();
403 initIt(&doInit);
404 return res;