Mercurial > projects > ldc
diff tango/tango/io/FilePath.d @ 132:1700239cab2e trunk
[svn r136] MAJOR UNSTABLE UPDATE!!!
Initial commit after moving to Tango instead of Phobos.
Lots of bugfixes...
This build is not suitable for most things.
author | lindquist |
---|---|
date | Fri, 11 Jan 2008 17:57:40 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tango/tango/io/FilePath.d Fri Jan 11 17:57:40 2008 +0100 @@ -0,0 +1,2083 @@ +/******************************************************************************* + + copyright: Copyright (c) 2004 Kris Bell. All rights reserved + + license: BSD style: $(LICENSE) + + version: Oct 2004: Initial version + version: Nov 2006: Australian version + version: Feb 2007: Mutating version + version: Mar 2007: Folded FileProxy in + version: Nov 2007: VFS dictates '/' always be used + + author: Kris + +*******************************************************************************/ + +module tango.io.FilePath; + +private import tango.sys.Common; + +private import tango.io.FileConst; + +private import tango.core.Exception; + +private import tango.time.Time; + +/******************************************************************************* + +*******************************************************************************/ + +version (Win32) + { + version (Win32SansUnicode) + { + alias char T; + private extern (C) int strlen (char *s); + private alias WIN32_FIND_DATA FIND_DATA; + } + else + { + alias wchar T; + private extern (C) int wcslen (wchar *s); + private alias WIN32_FIND_DATAW FIND_DATA; + } + } + +version (Posix) + { + private import tango.stdc.stdio; + private import tango.stdc.string; + private import tango.stdc.posix.utime; + private import tango.stdc.posix.dirent; + } + +/******************************************************************************* + +*******************************************************************************/ + +private extern (C) void memmove (void* dst, void* src, uint bytes); + +/******************************************************************************* + + Models a file path. These are expected to be used as the constructor + argument to various file classes. The intention is that they easily + convert to other representations such as absolute, canonical, or Url. + + File paths containing non-ansi characters should be UTF-8 encoded. + Supporting Unicode in this manner was deemed to be more suitable + than providing a wchar version of FilePath, and is both consistent + & compatible with the approach taken with the Uri class. + + FilePath is designed to be transformed, thus each mutating method + modifies the internal content. There is a read-only base-class + called PathView, which can be used to provide a view into the + content as desired. + + Note that patterns of adjacent '.' separators are treated specially + in that they will be assigned to the name instead of the suffix. In + addition, a '.' at the start of a name signifies it does not belong + to the suffix i.e. ".file" is a name rather than a suffix. + + Note also that normalization of path-separators occurs by default. + This means that the use of '\' characters with be converted into + '/' instead while parsing. To mutate the path into an O/S native + version, use the native() method. To obtain a copy instead, use the + path.dup.native sequence + + Compile with -version=Win32SansUnicode to enable Win95 & Win32s file + support. + +*******************************************************************************/ + +class FilePath : PathView +{ + private char[] fp; // filepath with trailing 0 + + private bool dir_; // this represents a dir? + + private int end_, // before the trailing 0 + name_, // file/dir name + folder_, // path before name + suffix_; // after rightmost '.' + + public alias set opAssign; // path = x; + public alias append opCatAssign; // path ~= x; + + /*********************************************************************** + + Filter used for screening paths via toList() + + ***********************************************************************/ + + public alias bool delegate (FilePath, bool) Filter; + + /*********************************************************************** + + Passed around during file-scanning + + ***********************************************************************/ + + struct FileInfo + { + char[] path, + name; + ulong bytes; + bool folder; + } + + /*********************************************************************** + + Call-site shortcut to create a FilePath instance. This + enables the same syntax as struct usage, so may expose + a migration path + + ***********************************************************************/ + + static FilePath opCall (char[] filepath = null) + { + return new FilePath (filepath); + } + + /*********************************************************************** + + Create a FilePath from a copy of the provided string. + + FilePath assumes both path & name are present, and therefore + may split what is otherwise a logically valid path. That is, + the 'name' of a file is typically the path segment following + a rightmost path-separator. The intent is to treat files and + directories in the same manner; as a name with an optional + ancestral structure. It is possible to bias the interpretation + by adding a trailing path-separator to the argument. Doing so + will result in an empty name attribute. + + With regard to the filepath copy, we found the common case to + be an explicit .dup, whereas aliasing appeared to be rare by + comparison. We also noted a large proportion interacting with + C-oriented OS calls, implying the postfix of a null terminator. + Thus, FilePath combines both as a single operation. + + ***********************************************************************/ + + this (char[] filepath = null) + { + set (filepath); + } + + // now converts to '/' always. Use toNative to get Win32 paths + deprecated this (char[] filepath, bool native) + { + set (filepath); + } + + /*********************************************************************** + + Return the complete text of this filepath + + ***********************************************************************/ + + final char[] toString () + { + return fp [0 .. end_]; + } + + /*********************************************************************** + + Duplicate this path + + ***********************************************************************/ + + final FilePath dup () + { + return FilePath (toString); + } + + /*********************************************************************** + + Return the complete text of this filepath as a null + terminated string for use with a C api. Use toString + instead for any D api. + + Note that the nul is always embedded within the string + maintained by FilePath, so there's no heap overhead when + making a C call + + ***********************************************************************/ + + final char[] cString () + { + return fp [0 .. end_+1]; + } + + /*********************************************************************** + + Return the root of this path. Roots are constructs such as + "c:" + + ***********************************************************************/ + + final char[] root () + { + return fp [0 .. folder_]; + } + + /*********************************************************************** + + Return the file path. Paths may start and end with a "/". + The root path is "/" and an unspecified path is returned as + an empty string. Directory paths may be split such that the + directory name is placed into the 'name' member; directory + paths are treated no differently than file paths + + ***********************************************************************/ + + final char[] folder () + { + return fp [folder_ .. name_]; + } + + /*********************************************************************** + + Returns a path representing the parent of this one. This + will typically return the current path component, though + with a special case where the name component is empty. In + such cases, the path is scanned for a prior segment: + --- + normal: /x/y/z => /x/y + special: /x/y/ => /x + --- + + Note that this returns a path suitable for splitting into + path and name components (there's no trailing separator). + + See pop() also, which is generally more useful when working + with FilePath instances + + ***********************************************************************/ + + final char[] parent () + { + auto p = path; + if (name.length is 0) + for (int i=p.length-1; --i > 0;) + if (p[i] is FileConst.PathSeparatorChar) + { + p = p[0 .. i]; + break; + } + return stripped (p); + } + + /*********************************************************************** + + Return the name of this file, or directory. + + ***********************************************************************/ + + final char[] name () + { + return fp [name_ .. suffix_]; + } + + /*********************************************************************** + + Ext is the tail of the filename, rightward of the rightmost + '.' separator e.g. path "foo.bar" has ext "bar". Note that + patterns of adjacent separators are treated specially; for + example, ".." will wind up with no ext at all + + ***********************************************************************/ + + final char[] ext () + { + auto x = suffix; + if (x.length) + x = x [1..$]; + return x; + } + + /*********************************************************************** + + Suffix is like ext, but includes the separator e.g. path + "foo.bar" has suffix ".bar" + + ***********************************************************************/ + + final char[] suffix () + { + return fp [suffix_ .. end_]; + } + + /*********************************************************************** + + return the root + folder combination + + ***********************************************************************/ + + final char[] path () + { + return fp [0 .. name_]; + } + + /*********************************************************************** + + return the name + suffix combination + + ***********************************************************************/ + + final char[] file () + { + return fp [name_ .. end_]; + } + + /*********************************************************************** + + Returns true if all fields are equal. + + ***********************************************************************/ + + final override int opEquals (Object o) + { + return (this is o) || (o && toString == o.toString); + } + + /*********************************************************************** + + Does this FilePath equate to the given text? + + ***********************************************************************/ + + final override int opEquals (char[] s) + { + return toString() == s; + } + + /*********************************************************************** + + Returns true if this FilePath is *not* relative to the + current working directory + + ***********************************************************************/ + + final bool isAbsolute () + { + return (folder_ > 0) || + (folder_ < end_ && fp[folder_] is FileConst.PathSeparatorChar); + } + + /*********************************************************************** + + Returns true if this FilePath is empty + + ***********************************************************************/ + + final bool isEmpty () + { + return end_ is 0; + } + + /*********************************************************************** + + Returns true if this FilePath has a parent. Note that a + parent is defined by the presence of a path-separator in + the path. This means 'foo' within "\foo" is considered a + child of the root + + ***********************************************************************/ + + final bool isChild () + { + return folder.length > 0; + } + + /*********************************************************************** + + Replace all 'from' instances with 'to' + + ***********************************************************************/ + + final FilePath replace (char from, char to) + { + foreach (inout char c; path) + if (c is from) + c = to; + return this; + } + + /*********************************************************************** + + Convert path separators to a standard format, using '/' as + the path separator. This is compatible with URI and all of + the contemporary O/S which Tango supports. Known exceptions + include the Windows command-line processor, which considers + '/' characters to be switches instead. Use the native() + method to support that. + + Note: mutates the current path. + + ***********************************************************************/ + + final FilePath standard () + { + return replace ('\\', '/'); + } + + /*********************************************************************** + + Convert to native O/S path separators where that is required, + such as when dealing with the Windows command-line. + + Note: mutates the current path. Use this pattern to obtain a + copy instead: path.dup.native + + ***********************************************************************/ + + final FilePath native () + { + version (Win32) + return replace ('/', '\\'); + else + return this; + } + + /*********************************************************************** + + Concatenate text to this path; no separators are added. + See join() also + + ***********************************************************************/ + + final FilePath cat (char[][] others...) + { + foreach (other; others) + { + auto len = end_ + other.length; + expand (len); + fp [end_ .. len] = other; + fp [len] = 0; + end_ = len; + } + return parse; + } + + /*********************************************************************** + + Append a folder to this path. A leading separator is added + as required + + ***********************************************************************/ + + final FilePath append (char[] path) + { + if (file.length) + path = prefixed (path); + return cat (path); + } + + /*********************************************************************** + + Prepend a folder to this path. A trailing separator is added + if needed + + ***********************************************************************/ + + final FilePath prepend (char[] path) + { + adjust (0, folder_, folder_, padded (path)); + return parse; + } + + /*********************************************************************** + + Reset the content of this path to that of another and + reparse + + ***********************************************************************/ + + FilePath set (FilePath path) + { + return set (path.toString); + } + + /*********************************************************************** + + Reset the content of this path, and reparse. + + ***********************************************************************/ + + final FilePath set (char[] path) + { + end_ = path.length; + + expand (end_); + if (end_) + fp[0 .. end_] = path; + + fp[end_] = '\0'; + return parse; + } + + /*********************************************************************** + + Sidestep the normal lookup for paths that are known to + be folders. Where folder is true, file-system lookups + will be skipped. + + ***********************************************************************/ + + final FilePath isFolder (bool folder) + { + dir_ = folder; + return this; + } + + /*********************************************************************** + + Replace the root portion of this path + + ***********************************************************************/ + + final FilePath root (char[] other) + { + auto x = adjust (0, folder_, folder_, padded (other, ':')); + folder_ += x; + suffix_ += x; + name_ += x; + return this; + } + + /*********************************************************************** + + Replace the folder portion of this path. The folder will be + padded with a path-separator as required + + ***********************************************************************/ + + final FilePath folder (char[] other) + { + auto x = adjust (folder_, name_, name_ - folder_, padded (other)); + suffix_ += x; + name_ += x; + return this; + } + + /*********************************************************************** + + Replace the name portion of this path + + ***********************************************************************/ + + final FilePath name (char[] other) + { + auto x = adjust (name_, suffix_, suffix_ - name_, other); + suffix_ += x; + return this; + } + + /*********************************************************************** + + Replace the suffix portion of this path. The suffix will be + prefixed with a file-separator as required + + ***********************************************************************/ + + final FilePath suffix (char[] other) + { + adjust (suffix_, end_, end_ - suffix_, prefixed (other, '.')); + return this; + } + + /*********************************************************************** + + Replace the root and folder portions of this path and + reparse. The replacement will be padded with a path + separator as required + + ***********************************************************************/ + + final FilePath path (char[] other) + { + adjust (0, name_, name_, padded (other)); + return parse; + } + + /*********************************************************************** + + Replace the file and suffix portions of this path and + reparse. The replacement will be prefixed with a suffix + separator as required + + ***********************************************************************/ + + final FilePath file (char[] other) + { + adjust (name_, end_, end_ - name_, other); + return parse; + } + + /*********************************************************************** + + Pop to the parent of the current filepath (in place) + + ***********************************************************************/ + + final FilePath pop () + { + auto path = parent(); + end_ = path.length; + fp[end_] = '\0'; + return parse; + } + + /*********************************************************************** + + Join a set of path specs together. A path separator is + potentially inserted between each of the segments. + + ***********************************************************************/ + + static char[] join (char[][] paths...) + { + char[] result; + + foreach (path; paths) + result ~= padded (path); + + return result.length ? result [0 .. $-1] : null; + } + + /*********************************************************************** + + Return an adjusted path such that non-empty instances do not + have a trailing separator + + ***********************************************************************/ + + static char[] stripped (char[] path, char c = FileConst.PathSeparatorChar) + { + if (path.length && path[$-1] is c) + path = path [0 .. $-1]; + return path; + } + + /*********************************************************************** + + Return an adjusted path such that non-empty instances always + have a trailing separator + + ***********************************************************************/ + + static char[] padded (char[] path, char c = FileConst.PathSeparatorChar) + { + if (path.length && path[$-1] != c) + path = path ~ c; + return path; + } + + /*********************************************************************** + + Return an adjusted path such that non-empty instances always + have a prefixed separator + + ***********************************************************************/ + + static char[] prefixed (char[] s, char c = FileConst.PathSeparatorChar) + { + if (s.length && s[0] != c) + s = c ~ s; + return s; + } + + /*********************************************************************** + + Parse the path spec + + ***********************************************************************/ + + private final FilePath parse () + { + folder_ = 0; + name_ = suffix_ = -1; + + for (int i=end_; --i >= 0;) + switch (fp[i]) + { + case FileConst.FileSeparatorChar: + if (name_ < 0) + if (suffix_ < 0 && i && fp[i-1] != '.') + suffix_ = i; + break; + + version (Win32) + { + case '\\': + fp[i] = '/'; + } + case FileConst.PathSeparatorChar: + if (name_ < 0) + name_ = i + 1; + break; + + version (Win32) + { + case FileConst.RootSeparatorChar: + folder_ = i + 1; + break; + } + + default: + break; + } + + if (name_ < 0) + name_ = folder_; + + if (suffix_ < 0 || suffix_ is name_) + suffix_ = end_; + + return this; + } + + /*********************************************************************** + + Potentially make room for more content + + ***********************************************************************/ + + private final void expand (uint size) + { + ++size; + if (fp.length < size) + fp.length = (size + 127) & ~127; + } + + /*********************************************************************** + + Insert/delete internal content + + ***********************************************************************/ + + private final int adjust (int head, int tail, int len, char[] sub) + { + len = sub.length - len; + + // don't destroy self-references! + if (len && sub.ptr >= fp.ptr+head+len && sub.ptr < fp.ptr+fp.length) + { + char[512] tmp = void; + assert (sub.length < tmp.length); + sub = tmp[0..sub.length] = sub; + } + + // make some room if necessary + expand (len + end_); + + // slide tail around to insert or remove space + memmove (fp.ptr+tail+len, fp.ptr+tail, end_ +1 - tail); + + // copy replacement + memmove (fp.ptr + head, sub.ptr, sub.length); + + // adjust length + end_ += len; + return len; + } + + + /**********************************************************************/ + /********************** file-system methods ***************************/ + /**********************************************************************/ + + + /*********************************************************************** + + Does this path currently exist? + + ***********************************************************************/ + + final bool exists () + { + try { + fileSize(); + return true; + } catch (IOException){} + return false; + } + + /*********************************************************************** + + Returns the time of the last modification. Accurate + to whatever the OS supports + + ***********************************************************************/ + + final Time modified () + { + return timeStamps.modified; + } + + /*********************************************************************** + + Returns the time of the last access. Accurate to + whatever the OS supports + + ***********************************************************************/ + + final Time accessed () + { + return timeStamps.accessed; + } + + /*********************************************************************** + + Returns the time of file creation. Accurate to + whatever the OS supports + + ***********************************************************************/ + + final Time created () + { + return timeStamps.created; + } + + /*********************************************************************** + + Create an entire path consisting of this folder along with + all parent folders. The path must not contain '.' or '..' + segments. Related methods include PathUtil.normalize() and + FileSystem.toAbsolute() + + Note that each segment is created as a folder, including the + trailing segment. + + Returns: a chaining reference (this) + + Throws: IOException upon systen errors + + Throws: IllegalArgumentException if the path contains invalid + path segment names (such as '.' or '..') or a segment + exists but as a file instead of a folder + + ***********************************************************************/ + + final FilePath create () + { + auto segment = name; + if (segment.length > 0) + { + if (segment == FileConst.CurrentDirString || + segment == FileConst.ParentDirString) + badArg ("FilePath.create :: invalid path: "); + + if (this.exists) + if (this.isFolder) + return this; + else + badArg ("FilePath.create :: file/folder conflict: "); + + FilePath(this.parent).create; + return createFolder; + } + return this; + } + + /*********************************************************************** + + List the set of filenames within this folder, using + the provided filter to control the list: + --- + bool delegate (FilePath path, bool isFolder) Filter + --- + + Returning true from the filter includes the given path, + whilst returning false excludes it. Parameter 'isFolder' + indicates whether the path is a file or folder. + + Note that paths composed of '.' characters are ignored. + + ***********************************************************************/ + + final FilePath[] toList (Filter filter = null) + { + FilePath[] paths; + + foreach (info; this) + { + auto p = from (info); + + // test this entry for inclusion + if (filter is null || filter (p, info.folder)) + paths ~= p; + else + delete p; + } + + return paths; + } + + /*********************************************************************** + + Construct a FilePath from the given FileInfo + + ***********************************************************************/ + + static FilePath from (ref FileInfo info) + { + char[512] tmp = void; + + auto len = info.path.length + info.name.length; + assert (len < tmp.length); + + // construct full pathname + tmp [0 .. info.path.length] = info.path; + tmp [info.path.length .. len] = info.name; + + return FilePath(tmp[0 .. len]).isFolder(info.folder); + } + + /*********************************************************************** + + change the name or location of a file/directory, and + adopt the provided Path + + ***********************************************************************/ + + final FilePath rename (FilePath dst) + { + return rename (dst.toString); + } + + /*********************************************************************** + + Throw an exception using the last known error + + ***********************************************************************/ + + private void exception () + { + throw new IOException (toString ~ ": " ~ SysError.lastMsg); + } + + /*********************************************************************** + + Throw an exception using the last known error + + ***********************************************************************/ + + private void badArg (char[] msg) + { + throw new IllegalArgumentException (msg ~ toString); + } + + /*********************************************************************** + + ***********************************************************************/ + + version (Win32) + { + /*************************************************************** + + return a wchar[] instance of the path + + ***************************************************************/ + + private wchar[] toString16 (wchar[] tmp, char[] path) + { + auto i = MultiByteToWideChar (CP_UTF8, 0, + path.ptr, path.length, + tmp.ptr, tmp.length); + return tmp [0..i]; + } + + /*************************************************************** + + return a char[] instance of the path + + ***************************************************************/ + + private char[] toString (char[] tmp, wchar[] path) + { + auto i = WideCharToMultiByte (CP_UTF8, 0, path.ptr, path.length, + tmp.ptr, tmp.length, null, null); + return tmp [0..i]; + } + + /*************************************************************** + + return a wchar[] instance of the path + + ***************************************************************/ + + private wchar[] name16 (wchar[] tmp, bool withNull=true) + { + int offset = withNull ? 0 : 1; + return toString16 (tmp, this.cString[0 .. $-offset]); + } + + /*************************************************************** + + Get info about this path + + ***************************************************************/ + + private DWORD getInfo (inout WIN32_FILE_ATTRIBUTE_DATA info) + { + version (Win32SansUnicode) + { + if (! GetFileAttributesExA (this.cString.ptr, GetFileInfoLevelStandard, &info)) + exception; + } + else + { + wchar[MAX_PATH] tmp = void; + if (! GetFileAttributesExW (name16(tmp).ptr, GetFileInfoLevelStandard, &info)) + exception; + } + + return info.dwFileAttributes; + } + + /*************************************************************** + + Get flags for this path + + ***************************************************************/ + + private DWORD getFlags () + { + WIN32_FILE_ATTRIBUTE_DATA info = void; + + return getInfo (info); + } + + /*************************************************************** + + Return the file length (in bytes) + + ***************************************************************/ + + final ulong fileSize () + { + WIN32_FILE_ATTRIBUTE_DATA info = void; + + getInfo (info); + return (cast(ulong) info.nFileSizeHigh << 32) + + info.nFileSizeLow; + } + + /*************************************************************** + + Is this file writable? + + ***************************************************************/ + + final bool isWritable () + { + return (getFlags & FILE_ATTRIBUTE_READONLY) == 0; + } + + /*************************************************************** + + Is this file actually a folder/directory? + + ***************************************************************/ + + final bool isFolder () + { + if (dir_) + return true; + + return (getFlags & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + + /*************************************************************** + + Return timestamp information + + ***************************************************************/ + + final Stamps timeStamps () + { + static Time convert (FILETIME time) + { + return Time (TimeSpan.Epoch1601 + *cast(long*) &time); + } + + WIN32_FILE_ATTRIBUTE_DATA info = void; + Stamps time = void; + + getInfo (info); + time.modified = convert (info.ftLastWriteTime); + time.accessed = convert (info.ftLastAccessTime); + time.created = convert (info.ftCreationTime); + return time; + } + + /*********************************************************************** + + Transfer the content of another file to this one. Returns a + reference to this class on success, or throws an IOException + upon failure. + + ***********************************************************************/ + + final FilePath copy (char[] source) + { + auto src = new FilePath (source); + + version (Win32SansUnicode) + { + if (! CopyFileA (src.cString.ptr, this.cString.ptr, false)) + exception; + } + else + { + wchar[MAX_PATH+1] tmp1 = void; + wchar[MAX_PATH+1] tmp2 = void; + + if (! CopyFileW (toString16(tmp1, src.cString).ptr, name16(tmp2).ptr, false)) + exception; + } + + return this; + } + + /*************************************************************** + + Remove the file/directory from the file-system + + ***************************************************************/ + + final FilePath remove () + { + if (isFolder) + { + version (Win32SansUnicode) + { + if (! RemoveDirectoryA (this.cString.ptr)) + exception; + } + else + { + wchar[MAX_PATH] tmp = void; + if (! RemoveDirectoryW (name16(tmp).ptr)) + exception; + } + } + else + version (Win32SansUnicode) + { + if (! DeleteFileA (this.cString.ptr)) + exception; + } + else + { + wchar[MAX_PATH] tmp = void; + if (! DeleteFileW (name16(tmp).ptr)) + exception; + } + + return this; + } + + /*************************************************************** + + change the name or location of a file/directory, and + adopt the provided Path + + ***************************************************************/ + + final FilePath rename (char[] dst) + { + const int Typical = MOVEFILE_REPLACE_EXISTING + + MOVEFILE_COPY_ALLOWED + + MOVEFILE_WRITE_THROUGH; + + int result; + char[] cstr = dst ~ '\0'; + + version (Win32SansUnicode) + result = MoveFileExA (this.cString.ptr, cstr.ptr, Typical); + else + { + wchar[MAX_PATH] tmp1 = void; + wchar[MAX_PATH] tmp2 = void; + result = MoveFileExW (name16(tmp1).ptr, toString16(tmp2, cstr).ptr, Typical); + } + + if (! result) + exception; + + this.set (dst); + return this; + } + + /*************************************************************** + + Create a new file + + ***************************************************************/ + + final FilePath createFile () + { + HANDLE h; + + version (Win32SansUnicode) + h = CreateFileA (this.cString.ptr, GENERIC_WRITE, + 0, null, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, cast(HANDLE) 0); + else + { + wchar[MAX_PATH] tmp = void; + h = CreateFileW (name16(tmp).ptr, GENERIC_WRITE, + 0, null, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, cast(HANDLE) 0); + } + + if (h == INVALID_HANDLE_VALUE) + exception; + + if (! CloseHandle (h)) + exception; + + return this; + } + + /*************************************************************** + + Create a new directory + + ***************************************************************/ + + final FilePath createFolder () + { + version (Win32SansUnicode) + { + if (! CreateDirectoryA (this.cString.ptr, null)) + exception; + } + else + { + wchar[MAX_PATH] tmp = void; + if (! CreateDirectoryW (name16(tmp).ptr, null)) + exception; + } + return this; + } + + /*************************************************************** + + List the set of filenames within this folder. + + Each path and filename is passed to the provided + delegate, along with the path prefix and whether + the entry is a folder or not. + + Returns the number of files scanned. + + ***************************************************************/ + + final int opApply (int delegate(ref FileInfo) dg) + { + HANDLE h; + int ret; + char[] prefix; + char[MAX_PATH+1] tmp = void; + FIND_DATA fileinfo = void; + + int next() + { + version (Win32SansUnicode) + return FindNextFileA (h, &fileinfo); + else + return FindNextFileW (h, &fileinfo); + } + + static T[] padded (T[] s, T[] ext) + { + if (s.length is 0 || s[$-1] != '\\') + return s ~ "\\" ~ ext; + return s ~ ext; + } + + version (Win32SansUnicode) + h = FindFirstFileA (padded(this.toString, "*\0").ptr, &fileinfo); + else + { + wchar[MAX_PATH] host = void; + h = FindFirstFileW (padded(name16(host, false), "*\0").ptr, &fileinfo); + } + + if (h is INVALID_HANDLE_VALUE) + exception; + + scope (exit) + FindClose (h); + + prefix = FilePath.padded (this.toString); + do { + version (Win32SansUnicode) + { + auto len = strlen (fileinfo.cFileName.ptr); + auto str = fileinfo.cFileName.ptr [0 .. len]; + } + else + { + auto len = wcslen (fileinfo.cFileName.ptr); + auto str = toString (tmp, fileinfo.cFileName [0 .. len]); + } + + // skip hidden/system files + if ((fileinfo.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)) is 0) + { + FileInfo info = void; + info.name = str; + info.path = prefix; + info.bytes = (cast(ulong) fileinfo.nFileSizeHigh << 32) + fileinfo.nFileSizeLow; + info.folder = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + + // skip "..." names + if (str.length > 3 || str != "..."[0 .. str.length]) + if ((ret = dg(info)) != 0) + break; + } + } while (next); + + return ret; + } + } + + + /*********************************************************************** + + ***********************************************************************/ + + version (Posix) + { + /*************************************************************** + + Get info about this path + + ***************************************************************/ + + private uint getInfo (inout stat_t stats) + { + if (posix.stat (this.cString.ptr, &stats)) + exception; + + return stats.st_mode; + } + + /*************************************************************** + + Return the file length (in bytes) + + ***************************************************************/ + + final ulong fileSize () + { + stat_t stats = void; + + getInfo (stats); + return cast(ulong) stats.st_size; // 32 bits only + } + + /*************************************************************** + + Is this file writable? + + ***************************************************************/ + + final bool isWritable () + { + stat_t stats = void; + + return (getInfo(stats) & O_RDONLY) == 0; + } + + /*************************************************************** + + Is this file actually a folder/directory? + + ***************************************************************/ + + final bool isFolder () + { + stat_t stats = void; + + return (getInfo(stats) & S_IFDIR) != 0; + } + + /*************************************************************** + + Return timestamp information + + ***************************************************************/ + + final Stamps timeStamps () + { + static Time convert (timeval* tv) + { + return Time.epoch1970 + + TimeSpan.seconds(tv.tv_sec) + + TimeSpan.micros(tv.tv_usec); + } + + stat_t stats = void; + Stamps time = void; + + getInfo (stats); + + time.modified = convert (cast(timeval*) &stats.st_mtime); + time.accessed = convert (cast(timeval*) &stats.st_atime); + time.created = convert (cast(timeval*) &stats.st_ctime); + return time; + } + + /*********************************************************************** + + Transfer the content of another file to this one. Returns a + reference to this class on success, or throws an IOException + upon failure. + + ***********************************************************************/ + + final FilePath copy (char[] source) + { + auto from = new FilePath (source); + + auto src = posix.open (from.cString.ptr, O_RDONLY, 0640); + scope (exit) + if (src != -1) + posix.close (src); + + auto dst = posix.open (this.cString.ptr, O_CREAT | O_RDWR, 0660); + scope (exit) + if (dst != -1) + posix.close (dst); + + if (src is -1 || dst is -1) + exception; + + // copy content + ubyte[] buf = new ubyte [16 * 1024]; + int read = posix.read (src, buf.ptr, buf.length); + while (read > 0) + { + auto p = buf.ptr; + do { + int written = posix.write (dst, p, read); + p += written; + read -= written; + if (written is -1) + exception; + } while (read > 0); + read = posix.read (src, buf.ptr, buf.length); + } + if (read is -1) + exception; + + // copy timestamps + stat_t stats; + if (posix.stat (from.cString.ptr, &stats)) + exception; + + utimbuf utim; + utim.actime = stats.st_atime; + utim.modtime = stats.st_mtime; + if (utime (this.cString.ptr, &utim) is -1) + exception; + + return this; + } + + /*************************************************************** + + Remove the file/directory from the file-system + + ***************************************************************/ + + final FilePath remove () + { + if (isFolder) + { + if (posix.rmdir (this.cString.ptr)) + exception; + } + else + if (tango.stdc.stdio.remove (this.cString.ptr) == -1) + exception; + + return this; + } + + /*************************************************************** + + change the name or location of a file/directory, and + adopt the provided FilePath + + ***************************************************************/ + + final FilePath rename (char[] dst) + { + char[] cstr = dst ~ '\0'; + if (tango.stdc.stdio.rename (this.cString.ptr, cstr.ptr) == -1) + exception; + + this.set (dst); + return this; + } + + /*************************************************************** + + Create a new file + + ***************************************************************/ + + final FilePath createFile () + { + int fd; + + fd = posix.open (this.cString.ptr, O_CREAT | O_WRONLY | O_TRUNC, 0660); + if (fd == -1) + exception; + + if (posix.close(fd) == -1) + exception; + + return this; + } + + /*************************************************************** + + Create a new directory + + ***************************************************************/ + + final FilePath createFolder () + { + if (posix.mkdir (this.cString.ptr, 0777)) + exception; + + return this; + } + + /*************************************************************** + + List the set of filenames within this folder. + + Each path and filename is passed to the provided + delegate, along with the path prefix and whether + the entry is a folder or not. + + Returns the number of files scanned. + + ***************************************************************/ + + final int opApply (int delegate(ref FileInfo) dg) + { + int ret; + DIR* dir; + dirent entry; + dirent* pentry; + stat_t sbuf; + char[] prefix; + char[] sfnbuf; + + dir = tango.stdc.posix.dirent.opendir (this.cString.ptr); + if (! dir) + exception; + + scope (exit) + tango.stdc.posix.dirent.closedir (dir); + + // ensure a trailing '/' is present + prefix = FilePath.padded (this.toString); + + // prepare our filename buffer + sfnbuf = prefix.dup; + + // pentry is null at end of listing, or on an error + while (readdir_r (dir, &entry, &pentry), pentry != null) + { + auto len = tango.stdc.string.strlen (entry.d_name.ptr); + auto str = entry.d_name.ptr [0 .. len]; + ++len; // include the null + + // resize the buffer as necessary ... + if (sfnbuf.length < prefix.length + len) + sfnbuf.length = prefix.length + len; + + sfnbuf [prefix.length .. prefix.length + len] + = entry.d_name.ptr [0 .. len]; + + // skip "..." names + if (str.length > 3 || str != "..."[0 .. str.length]) + { + if (stat (sfnbuf.ptr, &sbuf)) + exception; + + FileInfo info = void; + info.name = str; + info.path = prefix; + info.folder = (sbuf.st_mode & S_IFDIR) != 0; + info.bytes = (sbuf.st_mode & S_IFREG) != 0 ? sbuf.st_size : 0; + + if ((ret = dg(info)) != 0) + break; + } + } + return ret; + } + } +} + + + +/******************************************************************************* + +*******************************************************************************/ + +interface PathView +{ + /*********************************************************************** + + TimeStamp information. Accurate to whatever the OS supports + + ***********************************************************************/ + + struct Stamps + { + Time created, /// time created + accessed, /// last time accessed + modified; /// last time modified + } + + /*********************************************************************** + + Return the complete text of this filepath + + ***********************************************************************/ + + abstract char[] toString (); + + /*********************************************************************** + + Return the complete text of this filepath + + ***********************************************************************/ + + abstract char[] cString (); + + /*********************************************************************** + + Return the root of this path. Roots are constructs such as + "c:" + + ***********************************************************************/ + + abstract char[] root (); + + /*********************************************************************** + + Return the file path. Paths may start and end with a "/". + The root path is "/" and an unspecified path is returned as + an empty string. Directory paths may be split such that the + directory name is placed into the 'name' member; directory + paths are treated no differently than file paths + + ***********************************************************************/ + + abstract char[] folder (); + + /*********************************************************************** + + Return the name of this file, or directory, excluding a + suffix. + + ***********************************************************************/ + + abstract char[] name (); + + /*********************************************************************** + + Ext is the tail of the filename, rightward of the rightmost + '.' separator e.g. path "foo.bar" has ext "bar". Note that + patterns of adjacent separators are treated specially; for + example, ".." will wind up with no ext at all + + ***********************************************************************/ + + abstract char[] ext (); + + /*********************************************************************** + + Suffix is like ext, but includes the separator e.g. path + "foo.bar" has suffix ".bar" + + ***********************************************************************/ + + abstract char[] suffix (); + + /*********************************************************************** + + return the root + folder combination + + ***********************************************************************/ + + abstract char[] path (); + + /*********************************************************************** + + return the name + suffix combination + + ***********************************************************************/ + + abstract char[] file (); + + /*********************************************************************** + + Returns true if this FilePath is *not* relative to the + current working directory. + + ***********************************************************************/ + + abstract bool isAbsolute (); + + /*********************************************************************** + + Returns true if this FilePath is empty + + ***********************************************************************/ + + abstract bool isEmpty (); + + /*********************************************************************** + + Returns true if this FilePath has a parent + + ***********************************************************************/ + + abstract bool isChild (); + + /*********************************************************************** + + Does this path currently exist? + + ***********************************************************************/ + + abstract bool exists (); + + /*********************************************************************** + + Returns the time of the last modification. Accurate + to whatever the OS supports + + ***********************************************************************/ + + abstract Time modified (); + + /*********************************************************************** + + Returns the time of the last access. Accurate to + whatever the OS supports + + ***********************************************************************/ + + abstract Time accessed (); + + /*********************************************************************** + + Returns the time of file creation. Accurate to + whatever the OS supports + + ***********************************************************************/ + + abstract Time created (); + + /*********************************************************************** + + Return the file length (in bytes) + + ***********************************************************************/ + + abstract ulong fileSize (); + + /*********************************************************************** + + Is this file writable? + + ***********************************************************************/ + + abstract bool isWritable (); + + /*********************************************************************** + + Is this file actually a folder/directory? + + ***********************************************************************/ + + abstract bool isFolder (); + + /*********************************************************************** + + Return timestamp information + + ***********************************************************************/ + + abstract Stamps timeStamps (); +} + + + + + +/******************************************************************************* + +*******************************************************************************/ + +debug (UnitTest) +{ + unittest + { + version(Win32) + { + auto fp = new FilePath(r"C:/home/foo/bar"); + fp ~= "john"; + assert (fp == r"C:/home/foo/bar/john"); + fp = r"C:/"; + fp ~= "john"; + assert (fp == r"C:/john"); + fp = "foo.bar"; + fp ~= "john"; + assert (fp == r"foo.bar/john"); + fp = ""; + fp ~= "john"; + assert (fp == r"john"); + + fp = r"C:/home/foo/bar/john/foo.d"; + assert (fp.pop == r"C:/home/foo/bar/john"); + assert (fp.pop == r"C:/home/foo/bar"); + assert (fp.pop == r"C:/home/foo"); + assert (fp.pop == r"C:/home"); + assert (fp.pop == r"C:"); + assert (fp.pop == r"C:"); + + // special case for popping empty names + fp = r"C:/home/foo/bar/john/"; + assert (fp.pop == r"C:/home/foo/bar", fp.toString); + + fp = new FilePath; + fp = r"C:/home/foo/bar/john/"; + assert (fp.isAbsolute); + assert (fp.name == ""); + assert (fp.folder == r"/home/foo/bar/john/"); + assert (fp == r"C:/home/foo/bar/john/"); + assert (fp.path == r"C:/home/foo/bar/john/"); + assert (fp.file == r""); + assert (fp.suffix == r""); + assert (fp.root == r"C:"); + assert (fp.ext == ""); + assert (fp.isChild); + + fp = new FilePath(r"C:/home/foo/bar/john"); + assert (fp.isAbsolute); + assert (fp.name == "john"); + assert (fp.folder == r"/home/foo/bar/"); + assert (fp == r"C:/home/foo/bar/john"); + assert (fp.path == r"C:/home/foo/bar/"); + assert (fp.file == r"john"); + assert (fp.suffix == r""); + assert (fp.ext == ""); + assert (fp.isChild); + + fp.pop; + assert (fp.isAbsolute); + assert (fp.name == "bar"); + assert (fp.folder == r"/home/foo/"); + assert (fp == r"C:/home/foo/bar"); + assert (fp.path == r"C:/home/foo/"); + assert (fp.file == r"bar"); + assert (fp.suffix == r""); + assert (fp.ext == ""); + assert (fp.isChild); + + fp.pop; + assert (fp.isAbsolute); + assert (fp.name == "foo"); + assert (fp.folder == r"/home/"); + assert (fp == r"C:/home/foo"); + assert (fp.path == r"C:/home/"); + assert (fp.file == r"foo"); + assert (fp.suffix == r""); + assert (fp.ext == ""); + assert (fp.isChild); + + fp.pop; + assert (fp.isAbsolute); + assert (fp.name == "home"); + assert (fp.folder == r"/"); + assert (fp == r"C:/home"); + assert (fp.path == r"C:/"); + assert (fp.file == r"home"); + assert (fp.suffix == r""); + assert (fp.ext == ""); + assert (fp.isChild); + + fp = new FilePath(r"foo/bar/john.doe"); + assert (!fp.isAbsolute); + assert (fp.name == "john"); + assert (fp.folder == r"foo/bar/"); + assert (fp.suffix == r".doe"); + assert (fp.file == r"john.doe"); + assert (fp == r"foo/bar/john.doe"); + assert (fp.ext == "doe"); + assert (fp.isChild); + + fp = new FilePath(r"c:doe"); + assert (fp.isAbsolute); + assert (fp.suffix == r""); + assert (fp == r"c:doe"); + assert (fp.folder == r""); + assert (fp.name == "doe"); + assert (fp.file == r"doe"); + assert (fp.ext == ""); + assert (!fp.isChild); + + fp = new FilePath(r"/doe"); + assert (fp.isAbsolute); + assert (fp.suffix == r""); + assert (fp == r"/doe"); + assert (fp.name == "doe"); + assert (fp.folder == r"/"); + assert (fp.file == r"doe"); + assert (fp.ext == ""); + assert (fp.isChild); + + fp = new FilePath(r"john.doe.foo"); + assert (!fp.isAbsolute); + assert (fp.name == "john.doe"); + assert (fp.folder == r""); + assert (fp.suffix == r".foo"); + assert (fp == r"john.doe.foo"); + assert (fp.file == r"john.doe.foo"); + assert (fp.ext == "foo"); + assert (!fp.isChild); + + fp = new FilePath(r".doe"); + assert (!fp.isAbsolute); + assert (fp.suffix == r""); + assert (fp == r".doe"); + assert (fp.name == ".doe"); + assert (fp.folder == r""); + assert (fp.file == r".doe"); + assert (fp.ext == ""); + assert (!fp.isChild); + + fp = new FilePath(r"doe"); + assert (!fp.isAbsolute); + assert (fp.suffix == r""); + assert (fp == r"doe"); + assert (fp.name == "doe"); + assert (fp.folder == r""); + assert (fp.file == r"doe"); + assert (fp.ext == ""); + assert (!fp.isChild); + + fp = new FilePath(r"."); + assert (!fp.isAbsolute); + assert (fp.suffix == r""); + assert (fp == r"."); + assert (fp.name == "."); + assert (fp.folder == r""); + assert (fp.file == r"."); + assert (fp.ext == ""); + assert (!fp.isChild); + + fp = new FilePath(r".."); + assert (!fp.isAbsolute); + assert (fp.suffix == r""); + assert (fp == r".."); + assert (fp.name == ".."); + assert (fp.folder == r""); + assert (fp.file == r".."); + assert (fp.ext == ""); + assert (!fp.isChild); + + fp = new FilePath(r"c:/a/b/c/d/e/foo.bar"); + assert (fp.isAbsolute); + fp.folder (r"/a/b/c/"); + assert (fp.suffix == r".bar"); + assert (fp == r"c:/a/b/c/foo.bar"); + assert (fp.name == "foo"); + assert (fp.folder == r"/a/b/c/"); + assert (fp.file == r"foo.bar"); + assert (fp.ext == "bar"); + assert (fp.isChild); + + fp = new FilePath(r"c:/a/b/c/d/e/foo.bar"); + assert (fp.isAbsolute); + fp.folder (r"/a/b/c/d/e/f/g/"); + assert (fp.suffix == r".bar"); + assert (fp == r"c:/a/b/c/d/e/f/g/foo.bar"); + assert (fp.name == "foo"); + assert (fp.folder == r"/a/b/c/d/e/f/g/"); + assert (fp.file == r"foo.bar"); + assert (fp.ext == "bar"); + assert (fp.isChild); + + fp = new FilePath(r"C:\foo\bar\test.bar"); + assert (fp.path == "C:/foo/bar/"); + fp = new FilePath(r"C:/foo/bar/test.bar"); + assert (fp.path == r"C:/foo/bar/"); + + fp = new FilePath(""); + assert (fp.isEmpty); + assert (!fp.isChild); + assert (!fp.isAbsolute); + assert (fp.suffix == r""); + assert (fp == r""); + assert (fp.name == ""); + assert (fp.folder == r""); + assert (fp.file == r""); + assert (fp.ext == ""); +/+ + fp = new FilePath(r"C:/foo/bar/test.bar"); + fp = new FilePath(fp.asPath ("foo")); + assert (fp.name == r"test"); + assert (fp.folder == r"foo/"); + assert (fp.path == r"C:foo/"); + assert (fp.ext == ".bar"); + + fp = new FilePath(fp.asPath ("")); + assert (fp.name == r"test"); + assert (fp.folder == r""); + assert (fp.path == r"C:"); + assert (fp.ext == ".bar"); + + fp = new FilePath(r"c:/joe/bar"); + assert(fp.cat(r"foo/bar/") == r"c:/joe/bar/foo/bar/"); + assert(fp.cat(new FilePath(r"foo/bar")).toString == r"c:/joe/bar/foo/bar"); + + assert (FilePath.join (r"a/b/c/d", r"e/f/" r"g") == r"a/b/c/d/e/f/g"); + + fp = new FilePath(r"C:/foo/bar/test.bar"); + assert (fp.asExt(null) == r"C:/foo/bar/test"); + assert (fp.asExt("foo") == r"C:/foo/bar/test.foo"); ++/ + } + } +} + + +debug (FilePath) +{ + import tango.io.Console; + + void main() + { + Cout (FilePath("c:/temp/").file("foo.bar")).newline; + } + +}