1 module filequery;
2 
3 import core.stdc.errno;
4 import std.typecons : Flag, Yes, No;
5 import std.file : FileException;
6 import std.traits : isNarrowString;
7 
8 ////////////////////////////////////////////////////////////////////////////////
9 // NOTE: this was copied from file.d
10 ////////////////////////////////////////////////////////////////////////////////
11 // Character type used for operating system filesystem APIs
12 version (Windows)
13 {
14     private alias FSChar = wchar;
15 }
16 else version (Posix)
17 {
18     private alias FSChar = char;
19 }
20 else
21     static assert(0);
22 private T cenforce(T)(T condition, lazy const(char)[] name, string file = __FILE__, size_t line = __LINE__)
23 {
24     if (condition)
25         return condition;
26     version (Windows)
27     {
28         throw new FileException(name, .GetLastError(), file, line);
29     }
30     else version (Posix)
31     {
32         throw new FileException(name, .errno, file, line);
33     }
34 }
35 version (Windows)
36 @trusted
37 private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
38     string file = __FILE__, size_t line = __LINE__)
39 {
40     if (condition)
41         return condition;
42     if (!name)
43     {
44         import core.stdc.wchar_ : wcslen;
45         import std.conv : to;
46 
47         auto len = namez ? wcslen(namez) : 0;
48         name = to!string(namez[0 .. len]);
49     }
50     throw new FileException(name, .GetLastError(), file, line);
51 }
52 
53 version (Posix)
54 @trusted
55 private T cenforce(T)(T condition, const(char)[] name, const(FSChar)* namez,
56     string file = __FILE__, size_t line = __LINE__)
57 {
58     if (condition)
59         return condition;
60     if (!name)
61     {
62         import core.stdc.string : strlen;
63 
64         auto len = namez ? strlen(namez) : 0;
65         name = namez[0 .. len].idup;
66     }
67     throw new FileException(name, .errno, file, line);
68 }
69 ////////////////////////////////////////////////////////////////////////////////
70 ////////////////////////////////////////////////////////////////////////////////
71 ////////////////////////////////////////////////////////////////////////////////
72 
73 
74 static struct AllSupportedOps
75 {
76     //
77     // The following settings are named by the operations that we would want to support on the FileInfo.
78     //
79     /++
80     If true, will cause queryFile to throw an exception if the file does not exist and
81     modifies FileInfo to not have any representation for a non-existing file.
82     +/
83     enum bool exists = true;
84     enum bool isFile = true;
85 
86     // Windows: GetFileAttributes FILE_ATTRIBUTE_DIRECTTORY
87     // Posix: ...
88     enum bool isDir  = true;
89     // Windows: GetFileAttributes FILE_ATTRIBUTE_REPARSE_POINT
90     enum bool isSymlink = true;
91     // Windows: ?
92     // Posix: stat/lstat st_size
93     enum bool getSize = true;
94 
95     // Windows: ?
96     // Posix: stat/lstat st_mtime
97     enum bool timeLastModified = true;
98 
99     // Windows Specific
100     enum bool isArchive = true;    // See GetFileAttributes FILE_ATTRIBUTE_ARCHIVE
101     enum bool isCompressed = true; // See GetFileAttributes FILE_ATTRIBUTE_COMPRESSED
102     enum bool isHidden = true;     // See GetFileAttributes FILE_ATTRIBUTE_HIDDEN
103     enum bool isNormal = true;     // See GetFileAttributes FILE_ATTRIBUTE_NORMAL
104     enum bool isReadOnly = true;   // See GetFileAttributes FILE_ATTRIBUTE_READONLY
105 
106     // Posix Specific
107     enum bool getDeviceID = true;        // See stat.st_dev
108     enum bool getInode = true;           // See stat.st_ino
109     enum bool getMode = true;            // See stat.st_mode
110     enum bool getHardLinkCount = true;   // See stat.st_nlink
111     enum bool getUid = true;             // See stat.st_uid
112     enum bool getGid = true;             // See stat.st_gid
113     enum bool getSpecialDeviceID = true; // See stat.st_rdev
114 }
115 
116 struct FileQueryEverythingEnabledExamplePolicy
117 {
118     // Posix: with the stat function, you can get the info on the symbol link or on it's file.
119     //        You do this by either calling `stat` or `lstat`.
120     enum followSymlink = true;
121     alias SupportedOps = AllSupportedOps;
122 }
123 
124 
125 
126 template fileQueryTemplate(Policy)
127 {
128     // TODO: static assert(isValidFileQueryPolicy(Policy));
129     private bool supportOp(string name)
130     {
131         foreach(op; Policy.SupportedOps)
132 	{
133 	    if (op == name)
134 	    {
135 	        return true;
136 	    }
137 	}
138         return false;
139     }
140 
141     version(Windows)
142     {
143         private enum oneOrMoreAttributesSupported =
144             (supportOp("isFile") ||
145 	     supportOp("isDir") ||
146 	     supportOp("isSymlink") ||
147 	     supportOp("isArchive") ||
148 	     supportOp("isCompressed") ||
149 	     supportOp("isHidden") ||
150 	     supportOp("isNormal") ||
151 	     supportOp("isReadOnly"));
152 
153         static if (oneOrMoreAttributesSupported)
154         {
155             enum useGetFileAttributes = oneOrMoreAttributesSupported;
156 	}
157 	else static if (supportOp("exists"))
158 	{
159             // TODO: maybe in this case we should just use the PathFileExists function istead?
160             enum useGetFileAttributes = true;
161         }
162         else
163         {
164 	    enum useGetFileAttributes = false;
165         }
166     }
167     else version(Posix)
168     {
169 
170         // TODO: determine if we are going to use the stat function
171 	enum useStat = true;
172 
173         static if (useStat)
174 	{
175             import core.sys.posix.sys.stat;
176             static if (supportOp("exists"))
177                 enum saveStatSucceeded = true;
178 	    else
179 	        enum saveStatSucceeded = false;
180 	}
181     }
182     else static assert(0);
183 
184     struct FileInfo
185     {
186         version(Windows)
187 	{
188             static if (useGetFileAttributes)
189             {
190                 DWORD getFileAttributesResult;
191             }
192 	}
193 	else
194 	{
195 	    static if (useStat)
196 	    {
197 	        stat_t statbuf;
198                 static if (saveStatSucceeded)
199                 {
200                     bool statSucceeded;
201                 }
202 	    }
203 	    else static assert(0, "not implemented");
204 	}
205 
206         static if (supportOp("exists"))
207         {
208             @property bool exists()
209             {
210                 version(Windows)
211                 {
212                     static if (useGetFileAttributes)
213                     {
214                         return getFileAttributesResult != INVALID_FILE_ATTRIBUTES;
215                     }
216                     else static assert(0);
217                 }
218 		else version(Posix)
219 		{
220 		    static if (useStat)
221 		    {
222 		        return statSucceeded;
223 		    }
224 		    else static assert(0, "not implemented");
225 		}
226                 else static assert(0, "not implemented");
227 	    }
228 	}
229         static if (supportOp("isFile"))
230         {
231             @property bool isFile()
232             {
233                 version(Windows)
234 		{
235 		    static assert(0, "not implemented");
236 		}
237 		else version(Posix)
238 		{
239 		    static if (useStat)
240 		    {
241                         return (statbuf.st_mode & S_IFMT) == S_IFREG;
242 		    }
243 		    else static assert(0, "not implemented");
244 		}
245                 else static assert(0, "not implemented");
246             }
247         }
248     }
249 
250     
251 /*
252     /// Note: If a symlink is passed in, this function will query the underlying file instead. 
253     ///       You can use querySymlink to query the symlink itself.
254     FileInfo query(R)(R name)
255     {
256         FileInfo info = void;
257 	query(&info, name);
258 	return info;
259     }
260     */
261     /// ditto
262     auto query(Policy, R)(FileInfo* outInfo, R name)
263     {
264         import std.internal.cstring : tempCString;
265         version(Windows)
266 	{
267 	    static assert(0, "not implemented");
268 	}
269 	else version(Posix)
270 	{
271 	    static if (useStat)
272 	    {
273                 import core.sys.posix.sys.stat : stat;
274 		auto namez = name.tempCString();
275 		bool statSucceeded = (0 == stat(namez, &outInfo.statbuf));
276 		
277 	        static if(saveStatSucceeded)
278 		{
279 		    outInfo.statSucceeded = statSucceeded;
280 		}
281 		else
282 		{
283                     static if (isNarrowString!R && is(Unqual!(ElementEncodingType!R) == char))
284                         alias names = name;
285                     else
286                         string names = null;
287                     cenforce(statSucceeded, names, namez);
288 		}
289 		if (!statSucceeded)
290 		{
291 		    static if (Policy.enforceExists)
292 		    {
293 		        
294 		    
295 		    }
296 		}
297 	    }
298 	}
299         else assert(0, "not implemented");
300     }
301     version(Posix)
302     {
303         // query the symlink itself instead of the underlying file
304         FileInfo querySymlink(R)(R name)
305         {
306             assert(0, "not implemented");
307         }
308     }
309 }
310 /+
311 struct SplitString
312 {
313     string str;
314     size_t splitStart;
315     size_t splitEnd;
316     @property string left() const { return str[0 .. splitStart]; }
317     @property string right() const { return str[splitEnd .. $]; }
318 }
319 SplitString splitString(string str, char delimiter) pure
320 {
321     size_t index;
322     for (index = 0; index < str.length; index++)
323     {
324         if (str[index] == delimiter)
325 	{
326 	    return SplitString(str, index, index + 1);
327 	}
328     }
329     return SplitString(str, index, index);
330 }
331 
332 template tuple(T...)
333 {
334     alias tuple = T;
335 }
336 template OpTuple(string ops)
337 {
338     static if (ops.length == 0)
339     {
340         alias OpTuple = tuple!();
341     }
342     else
343     {
344         private enum splitOps = splitString(ops, ',');
345         alias OpTuple = tuple!(splitOps.left, OpTuple!(splitOps.right));
346     }
347 }
348 +/
349 mixin template makeFileQuery(string[] options)
350 {
351     static struct FileQueryPolicy
352     {
353         //alias SupportedOps = OpTuple!options;
354         alias SupportedOps = options;
355     }
356     alias fileQuery = fileQueryTemplate!FileQueryPolicy;
357 }
358 
359 unittest
360 {
361     import std.stdio;
362     {
363         mixin makeFileQuery!(["exists"]);
364 	auto helloInfo = fileQuery.query("hello");
365 	writefln("helloInfo.exists = %s", helloInfo.exists);
366     }
367     {
368         mixin makeFileQuery!(["isFile"]);
369 	auto helloInfo = fileQuery.query("hello");
370 	writefln("helloInfo.isFile = %s", helloInfo.isFile);
371     }
372 }