1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314 |
|
/*******************************************************************************
copyright: Copyright (c) 2008 Kris Bell. All rights reserved
copyright: Normalization & Patterns copyright (c) 2006-2009
Max Samukha, Thomas Kühne, Grzegorz Adam Hankiewicz
license: BSD style: $(LICENSE)
version: Mar 2008: Initial version$(BR)
Oct 2009: Added PathUtil code
A more direct route to the file-system than FilePath. Use this
if you don't need path editing features. For example, if all you
want is to check some path exists, using this module would likely
be more convenient than FilePath:
---
if (exists ("some/file/path"))
...
---
These functions may be less efficient than FilePath because they
generally attach a null to the filename for each underlying O/S
call. Use Path when you need pedestrian access to the file-system,
and are not manipulating the path components. Use FilePath where
path-editing or mutation is desired.
We encourage the use of "named import" with this module, such as:
---
import Path = tango.io.Path;
if (Path.exists ("some/file/path"))
...
---
Also residing here is a lightweight path-parser, which splits a
filepath into constituent components. FilePath is based upon the
same PathParser:
---
auto p = Path.parse ("some/file/path");
auto path = p.path;
auto name = p.name;
auto suffix = p.suffix;
...
---
Path normalization and pattern-matching is also hosted here via
the normalize() and pattern() functions. See the doc towards the
end of this module.
Compile with -version=Win32SansUnicode to enable Win95 & Win32s
file support.
*******************************************************************************/
module tango.io.Path;
private import tango.sys.Common;
public import tango.time.Time : Time, TimeSpan;
private import tango.io.model.IFile : FileConst, FileInfo;
public import tango.core.Exception : IOException, IllegalArgumentException;
private import tango.stdc.string : memmove;
private import tango.core.Octal;
/*******************************************************************************
Various imports
*******************************************************************************/
version (Win32)
{
version (Win32SansUnicode)
{
private extern (C) int strlen (const char *s);
private alias WIN32_FIND_DATA FIND_DATA;
}
else
{
private extern (C) int wcslen (const 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;
}
/*******************************************************************************
Wraps the O/S specific calls with a D API. Note that these accept
null-terminated strings only, which is why it's not public. We need
this declared first to avoid forward-reference issues.
*******************************************************************************/
package struct FS
{
/***********************************************************************
TimeStamp information. Accurate to whatever the F/S supports.
***********************************************************************/
struct Stamps
{
Time created; /// Time created.
Time accessed; /// Last time accessed.
Time modified; /// Last time modified.
}
/***********************************************************************
Some fruct glue for directory listings.
***********************************************************************/
struct Listing
{
const(char)[] folder;
bool allFiles;
int opApply (scope int delegate(ref FileInfo) dg)
{
char[256] tmp = void;
auto path = strz (folder, tmp);
// sanity check on Win32 ...
version (Win32)
{
bool kosher(){foreach (c; path) if (c is '\\') return false; return true;}
assert (kosher(), "attempting to use non-standard '\\' in a path for a folder listing");
}
return list (path, dg, allFiles);
}
}
/***********************************************************************
Throw an exception using the last known error.
***********************************************************************/
static void exception (const(char)[] filename)
{
exception (filename[0..$-1] ~ ": ", SysError.lastMsg);
}
/***********************************************************************
Throw an IO exception.
***********************************************************************/
static void exception (const(char)[] prefix, const(char)[] error)
{
throw new IOException ((prefix ~ error).idup);
}
/***********************************************************************
Return an adjusted path such that non-empty instances always
have a trailing separator.
Note: Allocates memory where path is not already terminated.
***********************************************************************/
static inout(char)[] padded (inout(char)[] path, char c = '/')
{
if (path.length && path[$-1] != c)
path = path ~ c;
return path;
}
/***********************************************************************
Return an adjusted path such that non-empty instances always
have a leading separator.
Note: Allocates memory where path is not already terminated.
***********************************************************************/
static inout(char)[] paddedLeading (inout(char)[] path, char c = '/')
{
if (path.length && path[0] != c)
path = c ~ path;
return path;
}
/***********************************************************************
Return an adjusted path such that non-empty instances do not
have a trailing separator.
***********************************************************************/
static inout(char)[] stripped (inout(char)[] path, char c = '/')
{
if (path.length && path[$-1] is c)
path = path [0 .. $-1];
return path;
}
/***********************************************************************
Join a set of path specs together. A path separator is
potentially inserted between each of the segments.
Note: Allocates memory.
***********************************************************************/
static char[] join (const(char[])[] paths...)
{
char[] result;
if (paths.length)
{
result ~= stripped(paths[0]);
foreach (path; paths[1 .. $-1])
result ~= paddedLeading (stripped(path));
result ~= paddedLeading(paths[$-1]);
return result;
}
return "".dup;
}
/***********************************************************************
Append a terminating null onto a string, cheaply where
feasible.
Note: Allocates memory where the dst is too small.
***********************************************************************/
static char[] strz (const(char)[] src, char[] dst)
{
auto i = src.length + 1;
if (dst.length < i)
dst.length = i;
dst [0 .. i-1] = src;
dst[i-1] = 0;
return dst [0 .. i];
}
/***********************************************************************
Win32 API code
***********************************************************************/
version (Win32)
{
/***************************************************************
Return a wchar[] instance of the path.
***************************************************************/
private static wchar[] toString16 (wchar[] tmp, const(char)[] path)
{
auto i = MultiByteToWideChar (CP_UTF8, 0,
cast(PCHAR)path.ptr, path.length,
tmp.ptr, tmp.length);
return tmp [0..i];
}
/***************************************************************
Return a char[] instance of the path.
***************************************************************/
private static char[] toString (char[] tmp, const(wchar[]) path)
{
auto i = WideCharToMultiByte (CP_UTF8, 0, path.ptr, path.length,
cast(PCHAR)tmp.ptr, tmp.length, null, null);
return tmp [0..i];
}
/***************************************************************
Get info about this path.
***************************************************************/
private static bool fileInfo (const(char)[] name, ref WIN32_FILE_ATTRIBUTE_DATA info)
{
version (Win32SansUnicode)
{
if (! GetFileAttributesExA (name.ptr, GetFileInfoLevelStandard, &info))
return false;
}
else
{
wchar[MAX_PATH] tmp = void;
if (! GetFileAttributesExW (toString16(tmp, name).ptr, GetFileInfoLevelStandard, &info))
return false;
}
return true;
}
/***************************************************************
Get info about this path.
***************************************************************/
private static DWORD getInfo (const(char)[] name, ref WIN32_FILE_ATTRIBUTE_DATA info)
{
if (! fileInfo (name, info))
exception (name);
return info.dwFileAttributes;
}
/***************************************************************
Get flags for this path.
***************************************************************/
private static DWORD getFlags (const(char)[] name)
{
WIN32_FILE_ATTRIBUTE_DATA info = void;
return getInfo (name, info);
}
/***************************************************************
Return whether the file or path exists.
***************************************************************/
static bool exists (const(char)[] name)
{
WIN32_FILE_ATTRIBUTE_DATA info = void;
return fileInfo (name, info);
}
/***************************************************************
Return the file length (in bytes.)
***************************************************************/
static ulong fileSize (const(char)[] name)
{
WIN32_FILE_ATTRIBUTE_DATA info = void;
getInfo (name, info);
return (cast(ulong) info.nFileSizeHigh << 32) +
info.nFileSizeLow;
}
/***************************************************************
Is this file writable?
***************************************************************/
static bool isWritable (const(char)[] name)
{
return (getFlags(name) & FILE_ATTRIBUTE_READONLY) is 0;
}
/***************************************************************
Is this file actually a folder/directory?
***************************************************************/
static bool isFolder (const(char)[] name)
{
return (getFlags(name) & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
/***************************************************************
Is this a normal file?
***************************************************************/
static bool isFile (const(char)[] name)
{
return (getFlags(name) & FILE_ATTRIBUTE_DIRECTORY) == 0;
}
/***************************************************************
Return timestamp information.
Timestamps are returns in a format dictated by the
file-system. For example NTFS keeps UTC time,
while FAT timestamps are based on the local time.
***************************************************************/
static Stamps timeStamps (const(char)[] name)
{
static Time convert (FILETIME time)
{
return Time (TimeSpan.Epoch1601 + *cast(long*) &time);
}
WIN32_FILE_ATTRIBUTE_DATA info = void;
Stamps time = void;
getInfo (name, info);
time.modified = convert (info.ftLastWriteTime);
time.accessed = convert (info.ftLastAccessTime);
time.created = convert (info.ftCreationTime);
return time;
}
/***************************************************************
Set the accessed and modified timestamps of the
specified file.
***************************************************************/
static void timeStamps (const(char)[] name, Time accessed, Time modified)
{
void set (HANDLE h)
{
FILETIME m1, a1;
auto m = modified - Time.epoch1601;
auto a = accessed - Time.epoch1601;
*cast(long*) &a1.dwLowDateTime = m.ticks;
*cast(long*) &m1.dwLowDateTime = m.ticks;
if (SetFileTime (h, null, &a1, &m1) is 0)
exception (name);
}
createFile (name, &set);
}
/***************************************************************
Transfer the content of another file to this one.
Throws an IOException upon failure.
***************************************************************/
static void copy (const(char)[] src, const(char)[] dst)
{
version (Win32SansUnicode)
{
if (! CopyFileA (src.ptr, dst.ptr, false))
exception (src);
}
else
{
wchar[MAX_PATH+1] tmp1 = void;
wchar[MAX_PATH+1] tmp2 = void;
if (! CopyFileW (toString16(tmp1, src).ptr, toString16(tmp2, dst).ptr, false))
exception (src);
}
}
/***************************************************************
Remove the file/directory from the file-system.
Returns true on success - false otherwise.
***************************************************************/
static bool remove (const(char)[] name)
{
if (isFolder(name))
{
version (Win32SansUnicode)
return RemoveDirectoryA (name.ptr) != 0;
else
{
wchar[MAX_PATH] tmp = void;
return RemoveDirectoryW (toString16(tmp, name).ptr) != 0;
}
}
else
version (Win32SansUnicode)
return DeleteFileA (name.ptr) != 0;
else
{
wchar[MAX_PATH] tmp = void;
return DeleteFileW (toString16(tmp, name).ptr) != 0;
}
}
/***************************************************************
Change the name or location of a file/directory.
***************************************************************/
static void rename (const(char)[] src, const(char)[] dst)
{
const int Typical = MOVEFILE_REPLACE_EXISTING +
MOVEFILE_COPY_ALLOWED +
MOVEFILE_WRITE_THROUGH;
int result;
version (Win32SansUnicode)
result = MoveFileExA (src.ptr, dst.ptr, Typical);
else
{
wchar[MAX_PATH] tmp1 = void;
wchar[MAX_PATH] tmp2 = void;
result = MoveFileExW (toString16(tmp1, src).ptr, toString16(tmp2, dst).ptr, Typical);
}
if (! result)
exception (src);
}
/***************************************************************
Create a new file.
***************************************************************/
static void createFile (const(char)[] name)
{
createFile (name, null);
}
/***************************************************************
Create a new directory.
***************************************************************/
static void createFolder (const(char)[] name)
{
version (Win32SansUnicode)
{
if (! CreateDirectoryA (name.ptr, null))
exception (name);
}
else
{
wchar[MAX_PATH] tmp = void;
if (! CreateDirectoryW (toString16(tmp, name).ptr, null))
exception (name);
}
}
/***************************************************************
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.
Note: Allocates a small memory buffer.
***************************************************************/
static int list (const(char)[] folder, scope int delegate(ref FileInfo) dg, bool all=false)
{
HANDLE h;
int ret;
const(char)[] prefix;
char[MAX_PATH+1] tmp = void;
FIND_DATA fileinfo = void;
version (Win32SansUnicode)
alias char T;
else
alias wchar T;
int next()
{
version (Win32SansUnicode)
return FindNextFileA (h, &fileinfo);
else
return FindNextFileW (h, &fileinfo);
}
static T[] padded (const(T)[] s, const(T)[] ext)
{
if (s.length && s[$-1] is '/')
return cast(T[])(s ~ ext); // Should be a safe cast here
return s ~ "/" ~ ext;
}
version (Win32SansUnicode)
h = FindFirstFileA (padded(folder[0..$-1], "*\0").ptr, &fileinfo);
else
{
wchar[MAX_PATH] host = void;
h = FindFirstFileW (padded(toString16(host, folder[0..$-1]), "*\0").ptr, &fileinfo);
}
if (h is INVALID_HANDLE_VALUE)
return ret;
scope (exit)
FindClose (h);
prefix = FS.padded (folder[0..$-1]);
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 (all || (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;
info.hidden = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0;
info.system = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0;
// skip "..." names
if (str.length > 3 || str != "..."[0 .. str.length])
if ((ret = dg(info)) != 0)
break;
}
} while (next());
return ret;
}
/***************************************************************
Create a new file.
***************************************************************/
private static void createFile (const(char)[] name, scope void delegate(HANDLE) dg)
{
HANDLE h;
auto flags = dg !is null ? OPEN_EXISTING : CREATE_ALWAYS;
version (Win32SansUnicode)
h = CreateFileA (name.ptr, GENERIC_WRITE,
0, null, flags, FILE_ATTRIBUTE_NORMAL,
cast(HANDLE) 0);
else
{
wchar[MAX_PATH] tmp = void;
h = CreateFileW (toString16(tmp, name).ptr, GENERIC_WRITE,
0, null, flags, FILE_ATTRIBUTE_NORMAL,
cast(HANDLE) 0);
}
if (h is INVALID_HANDLE_VALUE)
exception (name);
if (dg !is null)
dg(h);
if (! CloseHandle (h))
exception (name);
}
}
/***********************************************************************
Posix-specific code.
***********************************************************************/
version (Posix)
{
/***************************************************************
Get info about this path.
***************************************************************/
private static uint getInfo (const(char)[] name, ref stat_t stats)
{
if (posix.stat (name.ptr, &stats))
exception (name);
return stats.st_mode;
}
/***************************************************************
Return whether the file or path exists.
***************************************************************/
static bool exists (const(char)[] name)
{
stat_t stats = void;
return posix.stat (name.ptr, &stats) is 0;
}
/***************************************************************
Return the file length (in bytes.)
***************************************************************/
static ulong fileSize (const(char)[] name)
{
stat_t stats = void;
getInfo (name, stats);
return cast(ulong) stats.st_size;
}
/***************************************************************
Is this file writable?
***************************************************************/
static bool isWritable (const(char)[] name)
{
stat_t stats = void;
return (getInfo(name, stats) & O_RDONLY) is 0;
}
/***************************************************************
Is this file actually a folder/directory?
***************************************************************/
static bool isFolder (const(char)[] name)
{
stat_t stats = void;
return (getInfo(name, stats) & S_IFMT) is S_IFDIR;
}
/***************************************************************
Is this a normal file?
***************************************************************/
static bool isFile (const(char)[] name)
{
stat_t stats = void;
return (getInfo(name, stats) & S_IFMT) is S_IFREG;
}
/***************************************************************
Return timestamp information.
Timestamps are returns in a format dictated by the
file-system. For example NTFS keeps UTC time,
while FAT timestamps are based on the local time.
***************************************************************/
static Stamps timeStamps (const(char)[] name)
{
static Time convert (typeof(stat_t.st_mtime) secs)
{
return Time.epoch1970 +
TimeSpan.fromSeconds(secs);
}
stat_t stats = void;
Stamps time = void;
getInfo (name, stats);
time.modified = convert (stats.st_mtime);
time.accessed = convert (stats.st_atime);
time.created = convert (stats.st_ctime);
return time;
}
/***************************************************************
Set the accessed and modified timestamps of the
specified file.
***************************************************************/
static void timeStamps (const(char)[] name, Time accessed, Time modified)
{
utimbuf time = void;
time.actime = cast(time_t)(accessed - Time.epoch1970).seconds;
time.modtime = cast(time_t)(modified - Time.epoch1970).seconds;
if (utime (name.ptr, &time) is -1)
exception (name);
}
/***********************************************************************
Transfer the content of another file to this one. Returns a
reference to this class on success, or throws an IOException
upon failure.
Note: Allocates a memory buffer.
***********************************************************************/
static void copy (const(char)[] source, const(char)[] dest)
{
auto src = posix.open (source.ptr, O_RDONLY, octal!(640));
scope (exit)
if (src != -1)
posix.close (src);
auto dst = posix.open (dest.ptr, O_CREAT | O_RDWR, octal!(660));
scope (exit)
if (dst != -1)
posix.close (dst);
if (src is -1 || dst is -1)
exception (source);
// copy content
ubyte[] buf = new ubyte [16 * 1024];
auto read = posix.read (src, buf.ptr, buf.length);
while (read > 0)
{
auto p = buf.ptr;
do {
auto written = posix.write (dst, p, read);
p += written;
read -= written;
if (written is -1)
exception (dest);
} while (read > 0);
read = posix.read (src, buf.ptr, buf.length);
}
if (read is -1)
exception (source);
// copy timestamps
stat_t stats;
if (posix.stat (source.ptr, &stats))
exception (source);
utimbuf utim;
utim.actime = stats.st_atime;
utim.modtime = stats.st_mtime;
if (utime (dest.ptr, &utim) is -1)
exception (dest);
}
/***************************************************************
Remove the file/directory from the file-system.
Returns true on success - false otherwise.
***************************************************************/
static bool remove (const(char)[] name)
{
return tango.stdc.stdio.remove(name.ptr) != -1;
}
/***************************************************************
Change the name or location of a file/directory.
***************************************************************/
static void rename (const(char)[] src, const(char)[] dst)
{
if (tango.stdc.stdio.rename (src.ptr, dst.ptr) is -1)
exception (src);
}
/***************************************************************
Create a new file.
***************************************************************/
static void createFile (const(char)[] name)
{
int fd;
fd = posix.open (name.ptr, O_CREAT | O_WRONLY | O_TRUNC, octal!(660));
if (fd is -1)
exception (name);
if (posix.close(fd) is -1)
exception (name);
}
/***************************************************************
Create a new directory.
***************************************************************/
static void createFolder (const(char)[] name)
{
if (posix.mkdir (name.ptr, octal!(777)))
exception (name);
}
/***************************************************************
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.
Note: Allocates and reuses a small memory buffer.
***************************************************************/
static int list (const(char)[] folder, scope int delegate(ref FileInfo) dg, bool all=false)
{
int ret;
DIR* dir;
dirent entry;
dirent* pentry;
stat_t sbuf;
const(char)[] prefix;
char[] sfnbuf;
dir = tango.stdc.posix.dirent.opendir (folder.ptr);
if (! dir)
return ret;
scope (exit)
tango.stdc.posix.dirent.closedir (dir);
// ensure a trailing '/' is present
prefix = FS.padded (folder[0..$-1]);
// prepare our filename buffer
sfnbuf = prefix.dup;
while (true)
{
// pentry is null at end of listing, or on an error
readdir_r (dir, &entry, &pentry);
if (pentry is null)
break;
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])
{
FileInfo info = void;
info.bytes = 0;
info.name = str;
info.path = prefix;
info.hidden = str[0] is '.';
info.folder = info.system = false;
if (! stat (sfnbuf.ptr, &sbuf))
{
info.folder = (sbuf.st_mode & S_IFDIR) != 0;
if (info.folder is false)
{
if ((sbuf.st_mode & S_IFREG) is 0)
info.system = true;
else
info.bytes = cast(ulong) sbuf.st_size;
}
}
if (all || (info.hidden | info.system) is false)
if ((ret = dg(info)) != 0)
break;
}
}
return ret;
}
}
}
/*******************************************************************************
Parses a file path.
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 PathParser, and is both consistent
& compatible with the approach taken with the Uri class.
Note that patterns of adjacent '.' separators are treated specially
in that they will be assigned to the name where there is no distinct
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.
Patterns of intermediate '.' characters will otherwise be assigned
to the suffix, such that "file....suffix" includes the dots within
the suffix itself. See method ext() for a suffix without dots.
Note also that normalization of path-separators does *not* occur by
default. This means that usage of '\' characters should be explicitly
converted beforehand into '/' instead (an exception is thrown in those
cases where '\' is present). On-the-fly conversion is avoided because
(a) the provided path is considered immutable and (b) we avoid taking
a copy of the original path. Module FilePath exists at a higher level,
without such contraints.
*******************************************************************************/
struct PathParser(char_t = char)
{
package char_t[] fp; // filepath with trailing
package int end_, // before any trailing 0
ext_, // after rightmost '.'
name_, // file/dir name
folder_, // path before name
suffix_; // including leftmost '.'
/***********************************************************************
Parse the path spec.
***********************************************************************/
PathParser parse (char_t[] path)
{
return parse (path, path.length);
}
/***********************************************************************
Duplicate this path.
Note: Allocates memory for the path content.
***********************************************************************/
@property PathParser dup () const
{
PathParser ret;
ret.fp = fp.dup;
ret.end_ = end_;
ret.ext_ = ext_;
ret.name_ = name_;
ret.folder_ = folder_;
ret.suffix_ = suffix_;
return ret;
}
/***********************************************************************
Return the complete text of this filepath.
***********************************************************************/
inout(char_t)[] dString () inout
{
return fp [0 .. end_];
}
immutable(char)[] toString() const
{
return dString().idup;
}
/***********************************************************************
Return the root of this path. Roots are constructs such as
"C:".
***********************************************************************/
@property inout(char_t)[] root () inout
{
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.
***********************************************************************/
@property inout(char_t)[] folder () inout
{
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:
$(UL
$(LI normal: /x/y/z => /x/y)
$(LI special: /x/y/ => /x)
$(LI normal: /x => /)
$(LI normal: / => [empty]))
Note that this returns a path suitable for splitting into
path and name components (there's no trailing separator).
***********************************************************************/
@property inout(char_t)[] parent () inout
{
auto p = path;
if (name.length is 0)
for (int i=cast(int)p.length-1; --i > 0;)
if (p[i] is FileConst.PathSeparatorChar)
{
p = p[0 .. i];
break;
}
return FS.stripped (p);
}
/***********************************************************************
Pop the rightmost element off this path, stripping off a
trailing '/' as appropriate:
$(UL
$(LI /x/y/z => /x/y)
$(LI /x/y/ => /x/y (note trailing '/' in the original))
$(LI /x/y => /x)
$(LI /x => /)
$(LI / => [empty]))
Note that this returns a path suitable for splitting into
path and name components (there's no trailing separator).
***********************************************************************/
inout(char_t)[] pop () inout
{
return FS.stripped (path);
}
/***********************************************************************
Return the name of this file, or directory.
***********************************************************************/
@property inout(char_t)[] name () inout
{
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.
***********************************************************************/
@property char_t[] ext ()
{
auto x = suffix;
if (x.length)
{
if (ext_ is 0)
foreach (c; x)
{
if (c is '.')
++ext_;
else
break;
}
x = x [ext_ .. $];
}
return x;
}
/***********************************************************************
Suffix is like ext, but includes the separator e.g. path
"foo.bar" has suffix ".bar".
***********************************************************************/
@property inout(char_t)[] suffix () inout
{
return fp [suffix_ .. end_];
}
/***********************************************************************
Return the root + folder combination.
***********************************************************************/
@property inout(char_t)[] path () inout
{
return fp [0 .. name_];
}
/***********************************************************************
Return the name + suffix combination.
***********************************************************************/
@property inout(char_t)[] file () inout
{
return fp [name_ .. end_];
}
/***********************************************************************
Returns true if this path is *not* relative to the
current working directory.
***********************************************************************/
@property const bool isAbsolute ()
{
return (folder_ > 0) ||
(folder_ < end_ && fp[folder_] is FileConst.PathSeparatorChar);
}
/***********************************************************************
Returns true if this FilePath is empty.
***********************************************************************/
@property const bool isEmpty ()
{
return end_ is 0;
}
/***********************************************************************
Returns true if this path 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.
***********************************************************************/
@property const bool isChild ()
{
return folder().length > 0;
}
/***********************************************************************
Does this path equate to the given text? We ignore trailing
path-separators when testing equivalence.
***********************************************************************/
/*int opEquals (char[] s)
{
return FS.stripped(s) == FS.stripped(toString);
}*/
const bool equals (const(char)[] s)
{
return FS.stripped(s) == FS.stripped(dString());
}
/***********************************************************************
Parse the path spec with explicit end point. A '\' is
considered illegal in the path and should be normalized
out before this is invoked (the content managed here is
considered immutable, and thus cannot be changed by this
function.)
***********************************************************************/
package PathParser parse (char_t[] path, size_t end)
{
end_ = cast(int)end;
fp = path;
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;
case FileConst.PathSeparatorChar:
if (name_ < 0)
name_ = i + 1;
break;
// Windows file separators are illegal. Use
// standard() or equivalent to convert first
case '\\':
FS.exception ("unexpected '\\' character in path: ", path[0..end]);
break;
version (Win32)
{
case ':':
folder_ = i + 1;
break;
}
default:
break;
}
if (name_ < 0)
name_ = folder_;
if (suffix_ < 0 || suffix_ is name_)
suffix_ = end_;
return this;
}
}
/*******************************************************************************
Does this path currently exist?
*******************************************************************************/
bool exists (const(char)[] name)
{
char[512] tmp = void;
return FS.exists (FS.strz(name, tmp));
}
/*******************************************************************************
Returns the time of the last modification. Accurate
to whatever the F/S supports, and in a format dictated
by the file-system. For example NTFS keeps UTC time,
while FAT timestamps are based on the local time.
*******************************************************************************/
Time modified (const(char)[] name)
{
return timeStamps(name).modified;
}
/*******************************************************************************
Returns the time of the last access. Accurate to
whatever the F/S supports, and in a format dictated
by the file-system. For example NTFS keeps UTC time,
while FAT timestamps are based on the local time.
*******************************************************************************/
Time accessed (const(char)[] name)
{
return timeStamps(name).accessed;
}
/*******************************************************************************
Returns the time of file creation. Accurate to
whatever the F/S supports, and in a format dictated
by the file-system. For example NTFS keeps UTC time,
while FAT timestamps are based on the local time.
*******************************************************************************/
Time created (const(char)[] name)
{
return timeStamps(name).created;
}
/*******************************************************************************
Return the file length (in bytes.)
*******************************************************************************/
ulong fileSize (const(char)[] name)
{
char[512] tmp = void;
return FS.fileSize (FS.strz(name, tmp));
}
/*******************************************************************************
Is this file writable?
*******************************************************************************/
bool isWritable (const(char)[] name)
{
char[512] tmp = void;
return FS.isWritable (FS.strz(name, tmp));
}
/*******************************************************************************
Is this file actually a folder/directory?
*******************************************************************************/
bool isFolder (const(char)[] name)
{
char[512] tmp = void;
return FS.isFolder (FS.strz(name, tmp));
}
/*******************************************************************************
Is this file actually a normal file?
Not a directory or (on unix) a device file or link.
*******************************************************************************/
bool isFile (const(char)[] name)
{
char[512] tmp = void;
return FS.isFile (FS.strz(name, tmp));
}
/*******************************************************************************
Return timestamp information.
Timestamps are returns in a format dictated by the
file-system. For example NTFS keeps UTC time,
while FAT timestamps are based on the local time.
*******************************************************************************/
FS.Stamps timeStamps (const(char)[] name)
{
char[512] tmp = void;
return FS.timeStamps (FS.strz(name, tmp));
}
/*******************************************************************************
Set the accessed and modified timestamps of the specified file.
Since: 0.99.9
*******************************************************************************/
void timeStamps (const(char)[] name, Time accessed, Time modified)
{
char[512] tmp = void;
FS.timeStamps (FS.strz(name, tmp), accessed, modified);
}
/*******************************************************************************
Remove the file/directory from the file-system. Returns true if
successful, false otherwise.
*******************************************************************************/
bool remove (const(char)[] name)
{
char[512] tmp = void;
return FS.remove (FS.strz(name, tmp));
}
/*******************************************************************************
Remove the files and folders listed in the provided paths. Where
folders are listed, they should be preceded by their contained
files in order to be successfully removed. Returns a set of paths
that failed to be removed (where .length is zero upon success).
The collate() function can be used to provide the input paths:
---
remove (collate (".", "*.d", true));
---
Use with great caution.
Note: May allocate memory.
Since: 0.99.9
*******************************************************************************/
char[][] remove (char[][] paths)
{
char[][] failed;
foreach (path; paths)
if (! remove (path))
failed ~= path;
return failed;
}
/*******************************************************************************
Create a new file.
*******************************************************************************/
void createFile (const(char)[] name)
{
char[512] tmp = void;
FS.createFile (FS.strz(name, tmp));
}
/*******************************************************************************
Create a new directory.
*******************************************************************************/
void createFolder (const(char)[] name)
{
char[512] tmp = void;
FS.createFolder (FS.strz(name, tmp));
}
/*******************************************************************************
Create an entire path consisting of this folder along with
all parent folders. The path should not contain '.' or '..'
segments, which can be removed via the normalize() function.
Note that each segment is created as a folder, including the
trailing segment.
Throws: IOException upon system errors.
Throws: IllegalArgumentException if a segment exists but as a
file instead of a folder.
*******************************************************************************/
void createPath (const(char)[] path)
{
void test (const(char)[] segment)
{
if (segment.length)
{
if (! exists (segment))
createFolder (segment);
else
if (! isFolder (segment))
throw new IllegalArgumentException (("Path.createPath :: file/folder conflict: " ~ segment).idup);
}
}
foreach (i, char c; path)
if (c is '/')
test (path [0 .. i]);
test (path);
}
/*******************************************************************************
Change the name or location of a file/directory.
*******************************************************************************/
void rename (const(char)[] src, const(char)[] dst)
{
char[512] tmp1 = void;
char[512] tmp2 = void;
FS.rename (FS.strz(src, tmp1), FS.strz(dst, tmp2));
}
/*******************************************************************************
Transfer the content of one file to another. Throws
an IOException upon failure.
*******************************************************************************/
void copy (const(char)[] src, const(char)[] dst)
{
char[512] tmp1 = void;
char[512] tmp2 = void;
FS.copy (FS.strz(src, tmp1), FS.strz(dst, tmp2));
}
/*******************************************************************************
Provides foreach support via a fruct, as in
---
foreach (info; children("myfolder"))
...
---
Each path and filename is passed to the foreach
delegate, along with the path prefix and whether
the entry is a folder or not. The info construct
exposes the following attributes:
---
char[] path
char[] name
ulong bytes
bool folder
---
Argument 'all' controls whether hidden and system
files are included - these are ignored by default.
*******************************************************************************/
FS.Listing children (const(char)[] path, bool all=false)
{
return FS.Listing (path, all);
}
/*******************************************************************************
Collate all files and folders from the given path whose name matches
the given pattern. Folders will be traversed where recurse is enabled,
and a set of matching names is returned as filepaths (including those
folders which match the pattern.)
Note: Allocates memory for returned paths.
Since: 0.99.9
*******************************************************************************/
char[][] collate (const(char)[] path, const(char)[] pattern, bool recurse=false)
{
char[][] list;
foreach (info; children (path))
{
if (info.folder && recurse)
list ~= collate (join(info.path, info.name), pattern, true);
if (patternMatch (info.name, pattern))
list ~= join (info.path, info.name);
}
return list;
}
/*******************************************************************************
Join a set of path specs together. A path separator is
potentially inserted between each of the segments.
Note: May allocate memory.
*******************************************************************************/
char[] join (const(char[])[] paths...)
{
return FS.join (paths);
}
/*******************************************************************************
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 provided path.
*******************************************************************************/
char[] standard (char[] path)
{
return replace (path, '\\', '/');
}
/*******************************************************************************
Convert to native O/S path separators where that is required,
such as when dealing with the Windows command-line.
Note: Mutates the provided path. Use this pattern to obtain a
copy instead: native(path.dup);
*******************************************************************************/
char[] native (char[] path)
{
version (Win32)
replace (path, '/', '\\');
return path;
}
/*******************************************************************************
Returns a path representing the parent of this one, with a special
case concerning a trailing '/':
$(UL
$(LI normal: /x/y/z => /x/y)
$(LI normal: /x/y/ => /x/y)
$(LI special: /x/y/ => /x)
$(LI normal: /x => /)
$(LI normal: / => empty))
The result can be split via parse().
*******************************************************************************/
inout(char)[] parent (inout(char)[] path)
{
return pop (FS.stripped (path));
}
/*******************************************************************************
Returns a path representing the parent of this one:
$(UL
$(LI normal: /x/y/z => /x/y)
$(LI normal: /x/y/ => /x/y)
$(LI normal: /x => /)
$(LI normal: / => empty))
The result can be split via parse().
*******************************************************************************/
inout(char)[] pop (inout(char)[] path)
{
int i = cast(int)path.length;
while (i && path[--i] != '/') {}
return path [0..i];
}
/*******************************************************************************
Break a path into "head" and "tail" components. For example:
$(UL
$(LI "/a/b/c" -> "/a","b/c")
$(LI "a/b/c" -> "a","b/c"))
*******************************************************************************/
inout(char)[] split (inout(char)[] path, out inout(char)[] head, out inout(char)[] tail)
{
head = path;
if (path.length > 1)
foreach (i, char c; path[1..$])
if (c is '/')
{
head = path [0 .. i+1];
tail = path [i+2 .. $];
break;
}
return path;
}
/*******************************************************************************
Replace all path 'from' instances with 'to', in place (overwrites
the provided path).
*******************************************************************************/
char[] replace (char[] path, char from, char to)
{
foreach (ref char c; path)
if (c is from)
c = to;
return path;
}
/*******************************************************************************
Parse a path into its constituent components.
Note that the provided path is sliced, not duplicated.
*******************************************************************************/
PathParser!(char_t) parse(char_t) (char_t[] path)
{
PathParser!(char_t) p;
p.parse (path);
return p;
}
/*******************************************************************************
*******************************************************************************/
debug(UnitTest)
{
unittest
{
auto p = parse ("/foo/bar/file.ext".dup);
assert (p.equals("/foo/bar/file.ext"));
assert (p.folder == "/foo/bar/");
assert (p.path == "/foo/bar/");
assert (p.file == "file.ext");
assert (p.name == "file");
assert (p.suffix == ".ext");
assert (p.ext == "ext");
assert (p.isChild == true);
assert (p.isEmpty == false);
assert (p.isAbsolute == true);
}
}
/******************************************************************************
Matches a pattern against a filename.
Some characters of pattern have special a meaning (they are
$(EM meta-characters)) and $(B can't) be escaped. These are:
$(TABLE
$(TR
$(TD $(B *))
$(TD Matches 0 or more instances of any character.))
$(TR
$(TD $(B ?))
$(TD Matches exactly one instances of any character.))
$(TR
$(TD $(B [)$(EM chars)$(B ]))
$(TD Matches one instance of any character that appears
between the brackets.))
$(TR
$(TD $(B [!)$(EM chars)$(B ]))
$(TD Matches one instance of any character that does not appear
between the brackets after the exclamation mark.))
)
Internally individual character comparisons are done calling
charMatch(), so its rules apply here too. Note that path
separators and dots don't stop a meta-character from matching
further portions of the filename.
Returns: true if pattern matches filename, false otherwise.
Throws: Nothing.
-----
version (Win32)
{
patternMatch("foo.bar", "*"); // => true
patternMatch(r"foo/foo\bar", "f*b*r"); // => true
patternMatch("foo.bar", "f?bar"); // => false
patternMatch("Goo.bar", "[fg]???bar"); // => true
patternMatch(r"d:\foo\bar", "d*foo?bar"); // => true
}
version (Posix)
{
patternMatch("Go*.bar", "[fg]???bar"); // => false
patternMatch("/foo*home/bar", "?foo*bar"); // => true
patternMatch("foobar", "foo?bar"); // => true
}
-----
******************************************************************************/
bool patternMatch (const(char)[] filename, const(char)[] pattern)
in
{
// Verify that pattern[] is valid
bool inbracket = false;
for (auto i=0; i < pattern.length; i++)
{
switch (pattern[i])
{
case '[':
assert(!inbracket);
inbracket = true;
break;
case ']':
assert(inbracket);
inbracket = false;
break;
default:
break;
}
}
}
body
{
int pi;
int ni;
char pc;
char nc;
int j;
int not;
int anymatch;
bool charMatch (char c1, char c2)
{
version (Win32)
{
if (c1 != c2)
return ((c1 >= 'a' && c1 <= 'z') ? c1 - ('a' - 'A') : c1) ==
((c2 >= 'a' && c2 <= 'z') ? c2 - ('a' - 'A') : c2);
return true;
}
version (Posix)
return c1 == c2;
}
ni = 0;
for (pi = 0; pi < pattern.length; pi++)
{
pc = pattern [pi];
switch (pc)
{
case '*':
if (pi + 1 == pattern.length)
goto match;
for (j = ni; j < filename.length; j++)
{
if (patternMatch(filename[j .. filename.length],
pattern[pi + 1 .. pattern.length]))
goto match;
}
goto nomatch;
case '?':
if (ni == filename.length)
goto nomatch;
ni++;
break;
case '[':
if (ni == filename.length)
goto nomatch;
nc = filename[ni];
ni++;
not = 0;
pi++;
if (pattern[pi] == '!')
{
not = 1;
pi++;
}
anymatch = 0;
while (1)
{
pc = pattern[pi];
if (pc == ']')
break;
if (!anymatch && charMatch(nc, pc))
anymatch = 1;
pi++;
}
if (!(anymatch ^ not))
goto nomatch;
break;
default:
if (ni == filename.length)
goto nomatch;
nc = filename[ni];
if (!charMatch(pc, nc))
goto nomatch;
ni++;
break;
}
}
if (ni < filename.length)
goto nomatch;
match:
return true;
nomatch:
return false;
}
/*******************************************************************************
*******************************************************************************/
debug (UnitTest)
{
unittest
{
version (Win32)
assert(patternMatch("foo", "Foo"));
version (Posix)
assert(!patternMatch("foo", "Foo"));
assert(patternMatch("foo", "*"));
assert(patternMatch("foo.bar", "*"));
assert(patternMatch("foo.bar", "*.*"));
assert(patternMatch("foo.bar", "foo*"));
assert(patternMatch("foo.bar", "f*bar"));
assert(patternMatch("foo.bar", "f*b*r"));
assert(patternMatch("foo.bar", "f???bar"));
assert(patternMatch("foo.bar", "[fg]???bar"));
assert(patternMatch("foo.bar", "[!gh]*bar"));
assert(!patternMatch("foo", "bar"));
assert(!patternMatch("foo", "*.*"));
assert(!patternMatch("foo.bar", "f*baz"));
assert(!patternMatch("foo.bar", "f*b*x"));
assert(!patternMatch("foo.bar", "[gh]???bar"));
assert(!patternMatch("foo.bar", "[!fg]*bar"));
assert(!patternMatch("foo.bar", "[fg]???baz"));
}
}
/*******************************************************************************
Normalizes a path component.
$(UL
$(LI $(B .) segments are removed)
$(LI <segment>$(B /..) are removed))
Multiple consecutive forward slashes are replaced with a single
forward slash. On Windows, \ will be converted to / prior to any
normalization.
Note that any number of .. segments at the front is ignored,
unless it is an absolute path, in which case they are removed.
The input path is copied into either the provided buffer, or a heap
allocated array if no buffer was provided. Normalization modifies
this copy before returning the relevant slice.
-----
normalize("/home/foo/./bar/../../john/doe"); // => "/home/john/doe"
-----
Note: Allocates memory.
*******************************************************************************/
char[] normalize (const(char)[] in_path, char[] buf = null)
{
char[] path; // Mutable path
size_t idx; // Current position
size_t moveTo; // Position to move
bool isAbsolute; // Whether the path is absolute
enum {NodeStackLength = 64}
// Starting positions of regular path segments are pushed
// on this stack to avoid backward scanning when .. segments
// are encountered
size_t[NodeStackLength] nodeStack;
size_t nodeStackTop;
// Moves the path tail starting at the current position to
// moveTo. Then sets the current position to moveTo.
void move ()
{
auto len = path.length - idx;
memmove (path.ptr + moveTo, path.ptr + idx, len);
path = path[0..moveTo + len];
idx = moveTo;
}
// Checks if the character at the current position is a
// separator. If true, normalizes the separator to '/' on
// Windows and advances the current position to the next
// character.
bool isSep (ref size_t i)
{
char c = path[i];
version (Windows)
{
if (c == '\\')
path[i] = '/';
else if (c != '/')
return false;
}
else
{
if (c != '/')
return false;
}
i++;
return true;
}
if (buf is null)
path = in_path.dup;
else
path = buf[0..in_path.length] = in_path;
version (Windows)
{
// Skip Windows drive specifiers
if (path.length >= 2 && path[1] == ':')
{
auto c = path[0];
if (c >= 'a' && c <= 'z')
{
path[0] = cast(char)(c - 32);
idx = 2;
}
else
if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')
idx = 2;
}
}
if (idx == path.length)
return path;
moveTo = idx;
if (isSep(idx))
{
moveTo++; // preserve root separator.
isAbsolute = true;
}
while (idx < path.length)
{
// Skip duplicate separators
if (isSep(idx))
continue;
if (path[idx] == '.')
{
// leave the current position at the start of
// the segment
auto i = idx + 1;
if (i < path.length && path[i] == '.')
{
i++;
if (i == path.length || isSep(i))
{
// It is a '..' segment. If the stack is not
// empty, set moveTo and the current position
// to the start position of the last found
// regular segment
if (nodeStackTop > 0)
moveTo = nodeStack[--nodeStackTop];
// If no regular segment start positions on the
// stack, drop the .. segment if it is absolute
// path or, otherwise, advance moveTo and the
// current position to the character after the
// '..' segment
else
if (!isAbsolute)
{
if (moveTo != idx)
{
i -= idx - moveTo;
move();
}
moveTo = i;
}
idx = i;
continue;
}
}
// If it is '.' segment, skip it.
if (i == path.length || isSep(i))
{
idx = i;
continue;
}
}
// Remove excessive '/', '.' and/or '..' preceeding the
// segment
if (moveTo != idx)
move();
// Push the start position of the regular segment on the
// stack
assert (nodeStackTop < NodeStackLength);
nodeStack[nodeStackTop++] = idx;
// Skip the regular segment and set moveTo to the position
// after the segment (including the trailing '/' if present)
for (; idx < path.length && !isSep(idx); idx++)
{}
moveTo = idx;
}
if (moveTo != idx)
move();
return path;
}
/*******************************************************************************
*******************************************************************************/
debug (UnitTest)
{
unittest
{
assert (normalize ("") == "");
assert (normalize ("/home/../john/../.tango/.htaccess") == "/.tango/.htaccess");
assert (normalize ("/home/../john/../.tango/foo.conf") == "/.tango/foo.conf");
assert (normalize ("/home/john/.tango/foo.conf") == "/home/john/.tango/foo.conf");
assert (normalize ("/foo/bar/.htaccess") == "/foo/bar/.htaccess");
assert (normalize ("foo/bar/././.") == "foo/bar/");
assert (normalize ("././foo/././././bar") == "foo/bar");
assert (normalize ("/foo/../john") == "/john");
assert (normalize ("foo/../john") == "john");
assert (normalize ("foo/bar/..") == "foo/");
assert (normalize ("foo/bar/../john") == "foo/john");
assert (normalize ("foo/bar/doe/../../john") == "foo/john");
assert (normalize ("foo/bar/doe/../../john/../bar") == "foo/bar");
assert (normalize ("./foo/bar/doe") == "foo/bar/doe");
assert (normalize ("./foo/bar/doe/../../john/../bar") == "foo/bar");
assert (normalize ("./foo/bar/../../john/../bar") == "bar");
assert (normalize ("foo/bar/./doe/../../john") == "foo/john");
assert (normalize ("../../foo/bar") == "../../foo/bar");
assert (normalize ("../../../foo/bar") == "../../../foo/bar");
assert (normalize ("d/") == "d/");
assert (normalize ("/home/john/./foo/bar.txt") == "/home/john/foo/bar.txt");
assert (normalize ("/home//john") == "/home/john");
assert (normalize("/../../bar/") == "/bar/");
assert (normalize("/../../bar/../baz/./") == "/baz/");
assert (normalize("/../../bar/boo/../baz/.bar/.") == "/bar/baz/.bar/");
assert (normalize("../..///.///bar/..//..//baz/.//boo/..") == "../../../baz/");
assert (normalize("./bar/./..boo/./..bar././/") == "bar/..boo/..bar./");
assert (normalize("/bar/..") == "/");
assert (normalize("bar/") == "bar/");
assert (normalize(".../") == ".../");
assert (normalize("///../foo") == "/foo");
assert (normalize("./foo") == "foo");
auto buf = new char[100];
auto ret = normalize("foo/bar/./baz", buf);
assert (ret.ptr == buf.ptr);
assert (ret == "foo/bar/baz");
version (Windows)
{
assert (normalize ("\\foo\\..\\john") == "/john");
assert (normalize ("foo\\..\\john") == "john");
assert (normalize ("foo\\bar\\..") == "foo/");
assert (normalize ("foo\\bar\\..\\john") == "foo/john");
assert (normalize ("foo\\bar\\doe\\..\\..\\john") == "foo/john");
assert (normalize ("foo\\bar\\doe\\..\\..\\john\\..\\bar") == "foo/bar");
assert (normalize (".\\foo\\bar\\doe") == "foo/bar/doe");
assert (normalize (".\\foo\\bar\\doe\\..\\..\\john\\..\\bar") == "foo/bar");
assert (normalize (".\\foo\\bar\\..\\..\\john\\..\\bar") == "bar");
assert (normalize ("foo\\bar\\.\\doe\\..\\..\\john") == "foo/john");
assert (normalize ("..\\..\\foo\\bar") == "../../foo/bar");
assert (normalize ("..\\..\\..\\foo\\bar") == "../../../foo/bar");
assert (normalize(r"C:") == "C:");
assert (normalize(r"C") == "C");
assert (normalize(r"c:\") == "C:/");
assert (normalize(r"C:\..\.\..\..\") == "C:/");
assert (normalize(r"c:..\.\boo\") == "C:../boo/");
assert (normalize(r"C:..\..\boo\foo\..\.\..\..\bar") == "C:../../../bar");
assert (normalize(r"C:boo\..") == "C:");
}
}
}
/*******************************************************************************
*******************************************************************************/
debug (Path)
{
import tango.io.Stdout;
void main()
{
foreach (file; collate (".", "*.d", true))
Stdout (file).newline;
}
}
|