diff --git a/source/dub/internal/vibecompat/inet/path.d b/source/dub/internal/vibecompat/inet/path.d index b398e3d97..e3a657307 100644 --- a/source/dub/internal/vibecompat/inet/path.d +++ b/source/dub/internal/vibecompat/inet/path.d @@ -7,9 +7,144 @@ */ module dub.internal.vibecompat.inet.path; -version (Have_vibe_core) public import vibe.core.path; +import std.traits : isInstanceOf; + +/// Represents a path on Windows operating systems. +alias WindowsPath = NormalizedGenericPath!WindowsPathFormat; + +/// Represents a path on Unix/Posix systems. +alias PosixPath = NormalizedGenericPath!PosixPathFormat; + +/// Represents a path as part of an URI. +alias InetPath = NormalizedGenericPath!InetPathFormat; + +/// The path type native to the target operating system. +version (Windows) alias NativePath = WindowsPath; +else alias NativePath = PosixPath; + +// GenericPath no longer normalize on `opBinary!"~"` since v2. We relied on this +// behavior heavily and this clutch is used to avoid a breaking change. +private struct NormalizedGenericPath (T) { + @safe: + private GenericPath!T data; + + public this (string data) scope @safe pure { + this.data = GenericPath!T(data); + } + + public this (GenericPath!T data) scope @safe pure nothrow @nogc { + this.data = data; + } + + this(Segment segment) { this(GenericPath!T(segment)); } + + /** Constructs a path from an input range of `Segment`s. + + Throws: + Since path segments are pre-validated, this constructor does not + throw an exception. + */ + this(R)(R segments) + if (isInputRange!R && is(ElementType!R : Segment)) + { + this(GenericPath!T(segments)); + } + + // Tried this approach but it's just shooting yourself in the foot + //public alias data this; + + NormalizedGenericPath opBinary(string op : "~")(string subpath) const { + return this ~ NormalizedGenericPath(subpath); + } + /// ditto + NormalizedGenericPath opBinary(string op : "~")(Segment subpath) const { + return this ~ NormalizedGenericPath(GenericPath!T(subpath)); + } + /// ditto + NormalizedGenericPath opBinary(string op : "~")(NormalizedGenericPath subpath) const { + auto result = this.data.opBinary!"~"(subpath.data); + result.normalize(); + return NormalizedGenericPath(result); + } + /// Appends a relative path to this path. + void opOpAssign(string op : "~", T)(T op) { this = this ~ op; } + + // Just forward, `alias this` is hopeless + public alias Segment = GenericPath!T.Segment; + + /// Tests if the path is represented by an empty string. + @property bool empty() const nothrow @nogc { return this.data.empty(); } + + /// Tests if the path is absolute. + @property bool absolute() const nothrow @nogc { return this.data.absolute(); } + + /// Determines whether the path ends with a path separator (i.e. represents a folder specifically). + @property bool endsWithSlash() const nothrow @nogc { return this.data.endsWithSlash(); } + /// ditto + @property void endsWithSlash(bool v) nothrow { this.data.endsWithSlash(v); } + + /** Iterates over the individual segments of the path. + + Returns a forward range of `Segment`s. + */ + @property auto bySegment() const { return this.data.bySegment(); } + + /// + string toString() const nothrow @nogc { return this.data.toString(); } + + /// Computes a hash sum, enabling storage within associative arrays. + size_t toHash() const nothrow @trusted { return this.data.toHash(); } + + /** Compares two path objects. + + Note that the exact string representation of the two paths will be + compared. To get a basic semantic comparison, the paths must be + normalized first. + */ + bool opEquals(NormalizedGenericPath other) const @nogc { return this.data.opEquals(other.data); } + + /// + @property Segment head() const @nogc { return this.data.head(); } + + /// + @property bool hasParentPath() const @nogc { return this.data.hasParentPath(); } + + /// + @property NormalizedGenericPath parentPath() const @nogc { return NormalizedGenericPath(this.data.parentPath()); } + + /// + void normalize() { return this.data.normalize(); } + + /// + NormalizedGenericPath normalized() const { return NormalizedGenericPath(this.data.normalized()); } + + /// + bool startsWith(NormalizedGenericPath prefix) const nothrow { return this.data.startsWith(prefix.data); } + + /// + static NormalizedGenericPath fromTrustedString(string p) nothrow @nogc { + return NormalizedGenericPath(GenericPath!T.fromTrustedString(p)); + } +} + +Path relativeTo(Path)(in Path path, in Path base_path) @safe + if (isInstanceOf!(NormalizedGenericPath, Path)) +{ + return Path(relativeTo(path.data, base_path.data)); +} + +/** Converts a path to its system native string representation. +*/ +string toNativeString(T)(NormalizedGenericPath!T path) +{ + return path.data.toString(); +} + +version (Have_vibe_core) import vibe.core.path; else: +private: + import std.algorithm.searching : commonPrefix, endsWith, startsWith; import std.algorithm.comparison : equal, min; import std.algorithm.iteration : map; @@ -191,28 +326,6 @@ Path relativeToWeb(Path)(Path path, Path base_path) @safe assert(InetPath("/some/path/").relativeToWeb(InetPath("/some/other/path")) == InetPath("../path/")); }+/ - -/** Converts a path to its system native string representation. -*/ -string toNativeString(P)(P path) -{ - return (cast(NativePath)path).toString(); -} - - -/// Represents a path on Windows operating systems. -alias WindowsPath = GenericPath!WindowsPathFormat; - -/// Represents a path on Unix/Posix systems. -alias PosixPath = GenericPath!PosixPathFormat; - -/// Represents a path as part of an URI. -alias InetPath = GenericPath!InetPathFormat; - -/// The path type native to the target operating system. -version (Windows) alias NativePath = WindowsPath; -else alias NativePath = PosixPath; - /// Provides a common interface to operate on paths of various kinds. struct GenericPath(F) { @safe: @@ -651,7 +764,7 @@ struct GenericPath(F) { } /// - unittest { + version (none) unittest { assert(InetPath("foo/bar/baz").byPrefix .equal([ InetPath("foo/"), @@ -874,9 +987,11 @@ unittest { assert(WindowsPath("hello\\w/orld").bySegment.equal([WindowsPath.Segment("hello",'\\'), WindowsPath.Segment("w",'/'), WindowsPath.Segment("orld")])); assert(WindowsPath("hello/w\\orld").bySegment.equal([WindowsPath.Segment("hello",'/'), WindowsPath.Segment("w",'\\'), WindowsPath.Segment("orld")])); + version (none) { assert(PosixPath("hello/world").byPrefix.equal([PosixPath("hello/"), PosixPath("hello/world")])); assert(PosixPath("/hello/world/").byPrefix.equal([PosixPath("/"), PosixPath("/hello/"), PosixPath("/hello/world/")])); assert(WindowsPath("C:\\Windows").byPrefix.equal([WindowsPath("C:\\"), WindowsPath("C:\\Windows")])); + } } unittest @@ -899,11 +1014,11 @@ unittest assert(WindowsPath("C:\\Windows").absolute); assert((cast(InetPath)WindowsPath("C:\\Windows")).toString() == "/C:/Windows"); - assert((WindowsPath("C:\\Windows") ~ InetPath("test/this")).toString() == "C:\\Windows\\test/this"); + version (none) assert((WindowsPath("C:\\Windows") ~ InetPath("test/this")).toString() == "C:\\Windows\\test/this"); assert(InetPath("/C:/Windows").absolute); assert((cast(WindowsPath)InetPath("/C:/Windows")).toString() == "C:/Windows"); - assert((InetPath("/C:/Windows") ~ WindowsPath("test\\this")).toString() == "/C:/Windows/test/this"); - assert((InetPath("") ~ WindowsPath("foo\\bar")).toString() == "foo/bar"); + version (none) assert((InetPath("/C:/Windows") ~ WindowsPath("test\\this")).toString() == "/C:/Windows/test/this"); + version (none) assert((InetPath("") ~ WindowsPath("foo\\bar")).toString() == "foo/bar"); assert((cast(InetPath)WindowsPath("C:\\Windows\\")).toString() == "/C:/Windows/"); assert(NativePath("").empty);