diff --git a/source/vibe/core/path.d b/source/vibe/core/path.d index 89836ba..d9d2b94 100644 --- a/source/vibe/core/path.d +++ b/source/vibe/core/path.d @@ -19,14 +19,15 @@ import std.utf : byChar; /** Computes the relative path from `base_path` to this path. + Note that both, `path` and `base_path` must be absolute. + Params: path = The destination path base_path = The path from which the relative path starts See_also: `relativeToWeb` */ -Path relativeTo(Path)(in Path path, in Path base_path) @safe - if (isInstanceOf!(GenericPath, Path)) +GenericPath!Format relativeTo(Format)(in GenericPath!Format path, in GenericPath!Format base_path) @safe { import std.array : array, replicate; import std.range : chain, drop, take; @@ -34,7 +35,15 @@ Path relativeTo(Path)(in Path path, in Path base_path) @safe assert(base_path.absolute, "Base path must be absolute for relativeTo."); assert(path.absolute, "Path must be absolute for relativeTo."); - if (is(Path.Format == WindowsPathFormat)) { // FIXME: this shouldn't be a special case here! + return relativeTo(AbsolutePath!Format.assumeAbsolute(path), AbsolutePath!Format.assumeAbsolute(base_path)); +} +/// ditto +GenericPath!Format relativeTo(Format)(in AbsolutePath!Format path, in AbsolutePath!Format base_path) @safe +{ + import std.array : array, replicate; + import std.range : chain, drop, take; + + if (is(Format == WindowsPathFormat)) { // FIXME: this shouldn't be a special case here! bool samePrefix(size_t n) { return path.bySegment.map!(n => n.encodedName).take(n).equal(base_path.bySegment.map!(n => n.encodedName).take(n)); @@ -65,10 +74,10 @@ Path relativeTo(Path)(in Path path, in Path base_path) @safe base++; } - enum up = Path.Segment("..", Path.defaultSeparator); - auto ret = Path(base_nodes.map!(p => up).chain(nodes)); + enum up = GenericPath!Format.Segment("..", GenericPath!Format.defaultSeparator); + auto ret = GenericPath!Format(base_nodes.map!(p => up).chain(nodes)); if (path.endsWithSlash) { - if (ret.empty) return Path.fromTrustedString("." ~ path.toString()[$-1]); + if (ret.empty) return GenericPath!Format.fromTrustedString("." ~ path.toString()[$-1]); else ret.endsWithSlash = true; } return ret; @@ -200,23 +209,55 @@ string toNativeString(P)(P path) /// Represents a path on Windows operating systems. alias WindowsPath = GenericPath!WindowsPathFormat; /// ditto +alias AbsoluteWindowsPath = AbsolutePath!WindowsPathFormat; +/// +alias RelativeWindowsPath = RelativePath!WindowsPathFormat; +/// ditto alias WindowsPathN = NormalizedPath!WindowsPath; +/// ditto +alias AbsoluteWindowsPathN = NormalizedPath!AbsoluteWindowsPath; +/// ditto +alias RelativeWindowsPathN = NormalizedPath!RelativeWindowsPath; /// Represents a path on Unix/Posix systems. alias PosixPath = GenericPath!PosixPathFormat; /// ditto +alias AbsolutePosixPath = AbsolutePath!PosixPathFormat; +/// ditto +alias RelativePosixPath = RelativePath!PosixPathFormat; +/// ditto alias PosixPathN = NormalizedPath!PosixPath; +/// ditto +alias AbsolutePosixPathN = NormalizedPath!AbsolutePosixPath; +/// ditto +alias RelativePosixPathN = NormalizedPath!RelativePosixPath; /// Represents a path as part of an URI. alias InetPath = GenericPath!InetPathFormat; /// ditto +alias AbsoluteInetPath = AbsolutePath!InetPathFormat; +/// ditto +alias RelativeInetPath = RelativePath!InetPathFormat; +/// ditto alias InetPathN = NormalizedPath!InetPath; +/// ditto +alias AbsoluteInetPathN = NormalizedPath!AbsoluteInetPath; +/// ditto +alias RelativeInetPathN = NormalizedPath!RelativeInetPath; /// The path type native to the target operating system. version (Windows) alias NativePath = WindowsPath; else alias NativePath = PosixPath; /// ditto +alias AbsoluteNativePath = AbsolutePath!(NativePath.Format); +/// ditto +alias RelativeNativePath = RelativePath!(NativePath.Format); +/// ditto alias NativePathN = NormalizedPath!NativePath; +/// ditto +alias AbsoluteNativePathN = NormalizedPath!AbsoluteNativePath; +/// ditto +alias RelativeNativePathN = NormalizedPath!RelativeNativePath; /// Provides a common interface to operate on paths of various kinds. struct GenericPath(F) { @@ -1039,6 +1080,162 @@ unittest { } +/** Represents a `GenericPath` that is a relative path. +*/ +struct RelativePath(F) { + alias Path = GenericPath!F; + alias Format = F; + alias Segment = Path.Segment; + + private { + Path m_path; + } + + Path toPath() const { return m_path; } + alias toPath this; + + this(string p) { this(Path(p)); } + this(Segment segment) { this(Path(segment)); } + this(R)(R segments) if (isInputRange!R && is(ElementType!R : Segment)) { this(Path(segments)); } + this(Path path) { + enforce(!path.absolute, "Treating absolute path as relative"); + m_path = path; + } + + static RelativePath fromString(string p) { return RelativePath(Path(p)); } + + static RelativePath fromTrustedString(string p) { return RelativePath(Path.fromTrustedString(p)); } + + @property RelativePath parentPath() const { return assumeRelative(m_path.parentPath); } + + void normalize() { this.m_path = this.normalized.m_path; } + + /** Concatenates two paths. + + The right hand side must represent a relative path. + */ + RelativePath opBinary(string op : "~")(string subpath) const { return this ~ Path(subpath); } + /// ditto + RelativePath opBinary(string op : "~")(Segment subpath) const { return this ~ Path(subpath); } + /// ditto + RelativePath opBinary(string op : "~", F)(GenericPath!F.Segment subpath) const { return this ~ cast(Segment)(subpath); } + /// ditto + RelativePath opBinary(string op : "~")(Path subpath) const nothrow { + assert(!subpath.absolute, "Cannot append absolute path."); + return assumeRelative(m_path ~ subpath); + } + /// ditto + RelativePath opBinary(string op : "~", F)(GenericPath!F subpath) const if (!is(F == Format)) { return this ~ cast(Path)subpath; } + /// ditto + RelativePath opBinary(string op : "~", R)(R entries) const nothrow + if (isInputRange!R && is(ElementType!R : Segment)) + { + return this ~ GenericPath(entries); + } + + /// Appends a relative path to this path. + void opOpAssign(string op : "~", T)(T op) { this = this ~ op; } + + private static RelativePath assumeRelative(Path p) + { + assert(!p.absolute, "Attempt to create relative path from absolute path"); + RelativePath ret; + ret.m_path = p; + return ret; + } +} + +/// +unittest { + import std.exception : assertThrown; + + auto p = RelativePosixPath("foo/bar"); + p = p.parentPath; + assert(p.toString() == "foo/"); + p ~= "bar"; + assert(p.toString() == "foo/bar"); + assertThrown(RelativePosixPath("/foo")); +} + + +/** Represents a `GenericPath` that is an absolute path. +*/ +struct AbsolutePath(F) { + alias Path = GenericPath!F; + alias Format = F; + alias Segment = Path.Segment; + + private { + Path m_path; + } + + Path toPath() const { return m_path; } + alias toPath this; + + this(string p) { this(Path(p)); } + this(Segment segment) { this(Path(segment)); } + this(R)(R segments) if (isInputRange!R && is(ElementType!R : Segment)) { this(Path(segments)); } + this(Path path) { + enforce(path.absolute, "Treating relative path as absolute"); + m_path = path; + } + + static AbsolutePath fromString(string p) { return AbsolutePath(Path(p)); } + + static AbsolutePath fromTrustedString(string p) { return AbsolutePath(Path.fromTrustedString(p)); } + + @property AbsolutePath parentPath() const { return assumeAbsolute(m_path.parentPath); } + + void normalize() { this.m_path = this.normalized.m_path; } + + /** Concatenates two paths. + + The right hand side must represent a relative path. + */ + AbsolutePath opBinary(string op : "~")(string subpath) const { return this ~ Path(subpath); } + /// ditto + AbsolutePath opBinary(string op : "~")(Segment subpath) const { return this ~ Path(subpath); } + /// ditto + AbsolutePath opBinary(string op : "~", F)(GenericPath!F.Segment subpath) const { return this ~ cast(Segment)(subpath); } + /// ditto + AbsolutePath opBinary(string op : "~")(Path subpath) const nothrow { + assert(!subpath.absolute, "Cannot append absolute path."); + return assumeAbsolute(m_path ~ subpath); + } + /// ditto + AbsolutePath opBinary(string op : "~", F)(GenericPath!F subpath) const if (!is(F == Format)) { return this ~ cast(Path)subpath; } + /// ditto + AbsolutePath opBinary(string op : "~", R)(R entries) const nothrow + if (isInputRange!R && is(ElementType!R : Segment)) + { + return this ~ GenericPath(entries); + } + + /// Appends a relative path to this path. + void opOpAssign(string op : "~", T)(T op) { this = this ~ op; } + + private static AbsolutePath assumeAbsolute(Path p) + { + assert(p.absolute, "Attempt to create absolute path from relative path"); + AbsolutePath ret; + ret.m_path = p; + return ret; + } +} + +/// +unittest { + import std.exception : assertThrown; + + auto p = AbsolutePosixPath("/foo/bar"); + p = p.parentPath; + assert(p.toString() == "/foo/"); + p ~= "bar"; + assert(p.toString() == "/foo/bar"); + assertThrown(AbsolutePosixPath("foo")); +} + + /** Represents a path in its normalized form. This path type ensures that the represented path is always in normalized