diff --git a/source/vibe/core/path.d b/source/vibe/core/path.d index 21198dd..99bb7dd 100644 --- a/source/vibe/core/path.d +++ b/source/vibe/core/path.d @@ -1,19 +1,83 @@ module vibe.core.path; +import std.exception : enforce; + static import std.path; + struct Path { - nothrow: @safe: + @safe: private string m_path; this(string p) - { + nothrow { m_path = p; } - string toString() const { return m_path; } + @property bool absolute() + { + import std.algorithm.searching : startsWith; + // FIXME: Windows paths! + return m_path.startsWith('/'); + } - string toNativeString() const { return m_path; } + @property void endsWithSlash(bool v) + { + import std.algorithm.searching : endsWith; + + if (!m_path.endsWith('/') && v) m_path ~= '/'; + else if (m_path.endsWith('/') && !v) m_path = m_path[0 .. $-1]; // FIXME: "/test//" -> "/test/" + } + + @property bool empty() const { return m_path.length == 0 || m_path == "/"; } + + + Path parentPath() + { + import std.string : lastIndexOf; + auto idx = m_path.lastIndexOf('/'); + version (Windows) { + auto idx2 = m_path.lastIndexOf('\\'); + if (idx2 > idx) idx = idx2; + } + enforce(idx > 0, "Path has no parent path."); + return Path(m_path[0 .. idx+1]); + } + + Path relativeTo(Path p) { assert(false, "TODO!"); } + Path relativeToWeb(Path p) { assert(false, "TODO!"); } + + void normalize() + { + assert(false, "TODO!"); + } + + // FIXME: this should decompose the two paths into their parts and compare the part sequence + bool startsWith(Path other) const { + import std.algorithm.searching : startsWith; + return m_path[].startsWith(other.m_path); + } + + string toString() const nothrow { return m_path; } + + string toNativeString() const nothrow { return m_path; } + + PathEntry opIndex(size_t idx) + const { + import std.string : indexOf; + + string s = m_path; + while (true) { + auto sidx = m_path.indexOf('/'); + if (idx == 0) break; + enforce(sidx > 0, "Index out of bounds"); + s = s[sidx+1 .. $]; + idx--; + } + + auto sidx = s.indexOf('/'); + return PathEntry(s[0 .. sidx >= 0 ? sidx : $]); + } Path opBinary(string op : "~")(string subpath) { return this ~ Path(subpath); } Path opBinary(string op : "~")(Path subpath) { return Path(std.path.buildPath(m_path, subpath.toString())); } @@ -27,4 +91,75 @@ struct PathEntry { { m_name = name; } + + alias toString this; + + string toString() { return m_name; } +} + +private enum PathType { + unix, + windows, + inet +} + +private struct PathEntryRange(PathType TYPE) { + enum type = TYPE; + private { + string m_path; + PathEntry m_front; + } + + this(string path) + { + m_path = path; + skipLeadingSeparator(); + } + + @property bool empty() const { return m_path.length == 0; } + + @property ref const(PathEntry) front() const { return m_front; } + + void popFront() + { + import std.string : indexOf; + + auto idx = m_path.indexOf('/'); + static if (type == PathType.windows) { + if (idx >= 0) { + auto idx2 = m_path[0 .. idx].indexOf('\\'); + if (idx2 >= 0) idx = idx2; + } else idx = m_path.indexOf('\\'); + } + + if (idx < 0) { + m_front = PathEntry(m_path); + m_path = null; + } else { + m_front = PathEntry(m_path[0 .. idx]); + m_path = m_path[idx+1 .. $]; + } + } + + private void skipLeadingSeparator() + { + import std.algorithm.searching : startsWith; + + if (m_path.startsWith('/')) m_path = m_path[1 .. $]; + else static if (type == PathType.windows) { + if (m_path.startsWith('\\')) m_path = m_path[1 .. $]; + } + } +} + +unittest { + import std.algorithm.comparison : equal; + + assert(PathEntryRange!(PathType.unix)("hello/world").equal([PathEntry("hello"), PathEntry("world")])); + assert(PathEntryRange!(PathType.unix)("/hello/world/").equal([PathEntry("hello"), PathEntry("world")])); + assert(PathEntryRange!(PathType.unix)("hello\\world").equal([PathEntry("hello\\world")])); + assert(PathEntryRange!(PathType.windows)("hello/world").equal([PathEntry("hello"), PathEntry("world")])); + assert(PathEntryRange!(PathType.windows)("/hello/world/").equal([PathEntry("hello"), PathEntry("world")])); + assert(PathEntryRange!(PathType.windows)("hello\\w/orld").equal([PathEntry("hello"), PathEntry("w"), PathEntry("orld")])); + assert(PathEntryRange!(PathType.windows)("hello/w\\orld").equal([PathEntry("hello"), PathEntry("w"), PathEntry("orld")])); }