Add GenericPath.bySegment2 and .head2 as fully nogc alternatives.
This commit is contained in:
parent
32858b7aee
commit
6ef53121ad
|
@ -1,14 +1,14 @@
|
||||||
/**
|
/**
|
||||||
Contains routines for high level path handling.
|
Contains routines for high level path handling.
|
||||||
|
|
||||||
Copyright: © 2012-2018 Sönke Ludwig
|
Copyright: © 2012-2019 Sönke Ludwig
|
||||||
License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
|
License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
|
||||||
Authors: Sönke Ludwig
|
Authors: Sönke Ludwig
|
||||||
*/
|
*/
|
||||||
module vibe.core.path;
|
module vibe.core.path;
|
||||||
|
|
||||||
import std.algorithm.searching : commonPrefix, endsWith, startsWith;
|
import std.algorithm.searching : commonPrefix, endsWith, startsWith;
|
||||||
import std.algorithm.comparison : min;
|
import std.algorithm.comparison : equal, min;
|
||||||
import std.algorithm.iteration : map;
|
import std.algorithm.iteration : map;
|
||||||
import std.exception : enforce;
|
import std.exception : enforce;
|
||||||
import std.range : empty, front, popFront, popFrontExactly, takeExactly;
|
import std.range : empty, front, popFront, popFrontExactly, takeExactly;
|
||||||
|
@ -27,7 +27,6 @@ import std.traits : isInstanceOf;
|
||||||
Path relativeTo(Path)(Path path, Path base_path) @safe
|
Path relativeTo(Path)(Path path, Path base_path) @safe
|
||||||
if (isInstanceOf!(GenericPath, Path))
|
if (isInstanceOf!(GenericPath, Path))
|
||||||
{
|
{
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
import std.array : array, replicate;
|
import std.array : array, replicate;
|
||||||
import std.range : chain, drop, take;
|
import std.range : chain, drop, take;
|
||||||
|
|
||||||
|
@ -345,6 +344,201 @@ struct GenericPath(F) {
|
||||||
else m_front = Segment.init;
|
else m_front = Segment.init;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void readFront()
|
||||||
|
nothrow {
|
||||||
|
import std.array : array;
|
||||||
|
|
||||||
|
auto n = Format.getFrontNode(m_path);
|
||||||
|
m_path = m_path[n.length .. $];
|
||||||
|
|
||||||
|
char sep = '\0';
|
||||||
|
if (Format.isSeparator(n[$-1])) {
|
||||||
|
sep = n[$-1];
|
||||||
|
n = n[0 .. $-1];
|
||||||
|
}
|
||||||
|
static if (is(typeof(Format.decodeSingleSegment(n)) == string))
|
||||||
|
string ndec = Format.decodeSingleSegment(n);
|
||||||
|
else
|
||||||
|
string ndec = Format.decodeSingleSegment(n).array;
|
||||||
|
m_front = Segment.fromTrustedString(ndec, sep);
|
||||||
|
assert(m_front != Segment.init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A single path segment.
|
||||||
|
*/
|
||||||
|
static struct Segment2 {
|
||||||
|
@safe:
|
||||||
|
|
||||||
|
private {
|
||||||
|
string m_encodedName;
|
||||||
|
char m_separator = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructs a new path segment including an optional trailing
|
||||||
|
separator.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
name = The raw (unencoded) name of the path segment
|
||||||
|
separator = Optional trailing path separator (e.g. `'/'`)
|
||||||
|
|
||||||
|
Throws:
|
||||||
|
A `PathValidationException` is thrown if the name contains
|
||||||
|
characters that are invalid for the path type. In particular,
|
||||||
|
any path separator characters may not be part of the name.
|
||||||
|
*/
|
||||||
|
this(string name, char separator = '\0')
|
||||||
|
{
|
||||||
|
import std.algorithm.searching : any;
|
||||||
|
|
||||||
|
enforce!PathValidationException(separator == '\0' || Format.isSeparator(separator),
|
||||||
|
"Invalid path separator.");
|
||||||
|
auto err = Format.validateDecodedSegment(name);
|
||||||
|
enforce!PathValidationException(err is null, err);
|
||||||
|
|
||||||
|
m_encodedName = Format.encodeSegment(name);
|
||||||
|
m_separator = separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructs a path segment without performing validation.
|
||||||
|
|
||||||
|
Note that in debug builds, there are still assertions in place
|
||||||
|
that verify that the provided values are valid.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
name = The raw (unencoded) name of the path segment
|
||||||
|
separator = Optional trailing path separator (e.g. `'/'`)
|
||||||
|
*/
|
||||||
|
static Segment2 fromTrustedString(string name, char separator = '\0')
|
||||||
|
nothrow pure {
|
||||||
|
import std.algorithm.searching : any;
|
||||||
|
assert(separator == '\0' || Format.isSeparator(separator));
|
||||||
|
assert(Format.validateDecodedSegment(name) is null, "Invalid path segment.");
|
||||||
|
return fromTrustedEncodedString(Format.encodeSegment(name), separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructs a path segment without performing validation.
|
||||||
|
|
||||||
|
Note that in debug builds, there are still assertions in place
|
||||||
|
that verify that the provided values are valid.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
name = The encoded name of the path segment
|
||||||
|
separator = Optional trailing path separator (e.g. `'/'`)
|
||||||
|
*/
|
||||||
|
static Segment2 fromTrustedEncodedString(string encoded_name, char separator = '\0')
|
||||||
|
nothrow @nogc pure {
|
||||||
|
import std.algorithm.searching : any;
|
||||||
|
import std.utf : byCodeUnit;
|
||||||
|
|
||||||
|
assert(separator == '\0' || Format.isSeparator(separator));
|
||||||
|
assert(!encoded_name.byCodeUnit.any!(c => Format.isSeparator(c)));
|
||||||
|
assert(Format.validatePath(encoded_name) is null, "Invalid path segment.");
|
||||||
|
|
||||||
|
Segment2 ret;
|
||||||
|
ret.m_encodedName = encoded_name;
|
||||||
|
ret.m_separator = separator;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The (file/directory) name of the path segment.
|
||||||
|
|
||||||
|
Note: Depending on the path type, this may return a generic range
|
||||||
|
type instead of `string`. Use `name.to!string` in that
|
||||||
|
case if you need an actual `string`.
|
||||||
|
*/
|
||||||
|
@property auto name() const nothrow @nogc { return Format.decodeSingleSegment(m_encodedName); }
|
||||||
|
/// The encoded representation of the path segment name
|
||||||
|
@property string encodedName() const nothrow @nogc { return m_encodedName; }
|
||||||
|
/// The trailing separator (e.g. `'/'`) or `'\0'`.
|
||||||
|
@property char separator() const nothrow @nogc { return m_separator; }
|
||||||
|
/// ditto
|
||||||
|
@property void separator(char ch) {
|
||||||
|
enforce!PathValidationException(ch == '\0' || Format.isSeparator(ch),
|
||||||
|
"Character is not a valid path separator.");
|
||||||
|
m_separator = ch;
|
||||||
|
}
|
||||||
|
/// Returns `true` $(I iff) the segment has a trailing path separator.
|
||||||
|
@property bool hasSeparator() const nothrow @nogc { return m_separator != '\0'; }
|
||||||
|
|
||||||
|
/** Converts the segment to another path type.
|
||||||
|
|
||||||
|
The segment name will be re-validated during the conversion. The
|
||||||
|
separator, if any, will be adopted or replaced by the default
|
||||||
|
separator of the target path type.
|
||||||
|
|
||||||
|
Throws:
|
||||||
|
A `PathValidationException` is thrown if the segment name cannot
|
||||||
|
be represented in the target path format.
|
||||||
|
*/
|
||||||
|
GenericPath!F.Segment2 opCast(T : GenericPath!F.Segment2, F)()
|
||||||
|
{
|
||||||
|
import std.array : array;
|
||||||
|
|
||||||
|
char dsep = '\0';
|
||||||
|
if (m_separator) {
|
||||||
|
if (F.isSeparator(m_separator)) dsep = m_separator;
|
||||||
|
else dsep = F.defaultSeparator;
|
||||||
|
}
|
||||||
|
static if (is(typeof(this.name) == string))
|
||||||
|
string n = this.name;
|
||||||
|
else
|
||||||
|
string n = this.name.array;
|
||||||
|
return GenericPath!F.Segment2(n, dsep);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares two path segment names
|
||||||
|
bool opEquals(Segment2 other)
|
||||||
|
const nothrow @nogc {
|
||||||
|
try return equal(this.name, other.name) && this.hasSeparator == other.hasSeparator;
|
||||||
|
catch (Exception e) assert(false, e.msg);
|
||||||
|
}
|
||||||
|
/// ditto
|
||||||
|
bool opEquals(string name)
|
||||||
|
const nothrow @nogc {
|
||||||
|
import std.utf : byCodeUnit;
|
||||||
|
try return equal(this.name, name.byCodeUnit);
|
||||||
|
catch (Exception e) assert(false, e.msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents a path as an forward range of `Segment2`s.
|
||||||
|
|
||||||
|
Note that in contrast to `PathRange`, this range allows pure `@nogc`
|
||||||
|
iteration over encoded path segments.
|
||||||
|
*/
|
||||||
|
static struct PathRange2 {
|
||||||
|
import std.traits : ReturnType;
|
||||||
|
|
||||||
|
private {
|
||||||
|
string m_path;
|
||||||
|
Segment2 m_front;
|
||||||
|
}
|
||||||
|
|
||||||
|
private this(string path)
|
||||||
|
{
|
||||||
|
m_path = path;
|
||||||
|
if (m_path.length) {
|
||||||
|
auto ap = Format.getAbsolutePrefix(m_path);
|
||||||
|
if (ap.length && !Format.isSeparator(ap[0]))
|
||||||
|
m_front = Segment2.fromTrustedEncodedString(null, '/');
|
||||||
|
else readFront();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@property bool empty() const nothrow @nogc { return m_path.length == 0 && m_front == Segment2.init; }
|
||||||
|
|
||||||
|
@property PathRange2 save() { return this; }
|
||||||
|
|
||||||
|
@property Segment2 front() { return m_front; }
|
||||||
|
|
||||||
|
void popFront()
|
||||||
|
nothrow {
|
||||||
|
assert(m_front != Segment2.init);
|
||||||
|
if (m_path.length) readFront();
|
||||||
|
else m_front = Segment2.init;
|
||||||
|
}
|
||||||
|
|
||||||
private void readFront()
|
private void readFront()
|
||||||
{
|
{
|
||||||
auto n = Format.getFrontNode(m_path);
|
auto n = Format.getFrontNode(m_path);
|
||||||
|
@ -355,11 +549,12 @@ struct GenericPath(F) {
|
||||||
sep = n[$-1];
|
sep = n[$-1];
|
||||||
n = n[0 .. $-1];
|
n = n[0 .. $-1];
|
||||||
}
|
}
|
||||||
m_front = Segment.fromTrustedString(Format.decodeSingleSegment(n), sep);
|
m_front = Segment2.fromTrustedEncodedString(n, sep);
|
||||||
assert(m_front != Segment.init);
|
assert(m_front != Segment2.init);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private {
|
private {
|
||||||
string m_path;
|
string m_path;
|
||||||
}
|
}
|
||||||
|
@ -390,6 +585,12 @@ struct GenericPath(F) {
|
||||||
import std.range : only;
|
import std.range : only;
|
||||||
this(only(segment));
|
this(only(segment));
|
||||||
}
|
}
|
||||||
|
/// ditto
|
||||||
|
this(Segment2 segment)
|
||||||
|
{
|
||||||
|
import std.range : only;
|
||||||
|
this(only(segment));
|
||||||
|
}
|
||||||
|
|
||||||
/** Constructs a path from an input range of `Segment`s.
|
/** Constructs a path from an input range of `Segment`s.
|
||||||
|
|
||||||
|
@ -405,6 +606,15 @@ struct GenericPath(F) {
|
||||||
Format.toString(segments, dst);
|
Format.toString(segments, dst);
|
||||||
m_path = dst.data;
|
m_path = dst.data;
|
||||||
}
|
}
|
||||||
|
/// ditto
|
||||||
|
this(R)(R segments)
|
||||||
|
if (isInputRange!R && is(ElementType!R : Segment2))
|
||||||
|
{
|
||||||
|
import std.array : appender;
|
||||||
|
auto dst = appender!string;
|
||||||
|
Format.toString(segments, dst);
|
||||||
|
m_path = dst.data;
|
||||||
|
}
|
||||||
|
|
||||||
/** Constructs a path from its string representation.
|
/** Constructs a path from its string representation.
|
||||||
|
|
||||||
|
@ -451,16 +661,39 @@ struct GenericPath(F) {
|
||||||
/// Iterates over the path by `Segment`.
|
/// Iterates over the path by `Segment`.
|
||||||
@property PathRange bySegment() const { return PathRange(m_path); }
|
@property PathRange bySegment() const { return PathRange(m_path); }
|
||||||
|
|
||||||
|
/// Iterates over the path by `Segment`.
|
||||||
|
@property PathRange2 bySegment2() const { return PathRange2(m_path); }
|
||||||
|
|
||||||
/// Returns the trailing segment of the path.
|
/// Returns the trailing segment of the path.
|
||||||
@property Segment head()
|
@property Segment head()
|
||||||
const {
|
const {
|
||||||
|
import std.array : array;
|
||||||
|
|
||||||
auto n = Format.getBackNode(m_path);
|
auto n = Format.getBackNode(m_path);
|
||||||
char sep = '\0';
|
char sep = '\0';
|
||||||
if (n.length > 0 && Format.isSeparator(n[$-1])) {
|
if (n.length > 0 && Format.isSeparator(n[$-1])) {
|
||||||
sep = n[$-1];
|
sep = n[$-1];
|
||||||
n = n[0 .. $-1];
|
n = n[0 .. $-1];
|
||||||
}
|
}
|
||||||
return Segment.fromTrustedString(Format.decodeSingleSegment(n), sep);
|
|
||||||
|
static if (is(typeof(Format.decodeSingleSegment(n)) == string))
|
||||||
|
string ndec = Format.decodeSingleSegment(n);
|
||||||
|
else
|
||||||
|
string ndec = Format.decodeSingleSegment(n).array;
|
||||||
|
|
||||||
|
return Segment.fromTrustedString(ndec, sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the trailing segment of the path.
|
||||||
|
@property Segment2 head2()
|
||||||
|
const @nogc {
|
||||||
|
auto n = Format.getBackNode(m_path);
|
||||||
|
char sep = '\0';
|
||||||
|
if (n.length > 0 && Format.isSeparator(n[$-1])) {
|
||||||
|
sep = n[$-1];
|
||||||
|
n = n[0 .. $-1];
|
||||||
|
}
|
||||||
|
return Segment2.fromTrustedEncodedString(n, sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Determines if the `parentPath` property is valid.
|
/** Determines if the `parentPath` property is valid.
|
||||||
|
@ -580,8 +813,12 @@ struct GenericPath(F) {
|
||||||
/// ditto
|
/// ditto
|
||||||
GenericPath opBinary(string op : "~")(Segment subpath) const { return this ~ GenericPath(subpath); }
|
GenericPath opBinary(string op : "~")(Segment subpath) const { return this ~ GenericPath(subpath); }
|
||||||
/// ditto
|
/// ditto
|
||||||
|
GenericPath opBinary(string op : "~")(Segment2 subpath) const { return this ~ GenericPath(subpath); }
|
||||||
|
/// ditto
|
||||||
GenericPath opBinary(string op : "~", F)(GenericPath!F.Segment subpath) const { return this ~ cast(Segment)(subpath); }
|
GenericPath opBinary(string op : "~", F)(GenericPath!F.Segment subpath) const { return this ~ cast(Segment)(subpath); }
|
||||||
/// ditto
|
/// ditto
|
||||||
|
GenericPath opBinary(string op : "~", F)(GenericPath!F.Segment2 subpath) const { return this ~ cast(Segment2)(subpath); }
|
||||||
|
/// ditto
|
||||||
GenericPath opBinary(string op : "~")(GenericPath subpath) const nothrow {
|
GenericPath opBinary(string op : "~")(GenericPath subpath) const nothrow {
|
||||||
assert(!subpath.absolute || m_path.length == 0, "Cannot append absolute path.");
|
assert(!subpath.absolute || m_path.length == 0, "Cannot append absolute path.");
|
||||||
if (endsWithSlash || empty) return GenericPath.fromTrustedString(m_path ~ subpath.m_path);
|
if (endsWithSlash || empty) return GenericPath.fromTrustedString(m_path ~ subpath.m_path);
|
||||||
|
@ -595,6 +832,12 @@ struct GenericPath(F) {
|
||||||
{
|
{
|
||||||
return this ~ GenericPath(entries);
|
return this ~ GenericPath(entries);
|
||||||
}
|
}
|
||||||
|
/// ditto
|
||||||
|
GenericPath opBinary(string op : "~", R)(R entries) const nothrow
|
||||||
|
if (isInputRange!R && is(ElementType!R : Segment2))
|
||||||
|
{
|
||||||
|
return this ~ GenericPath(entries);
|
||||||
|
}
|
||||||
|
|
||||||
/// Appends a relative path to this path.
|
/// Appends a relative path to this path.
|
||||||
void opOpAssign(string op : "~", T)(T op) { this = this ~ op; }
|
void opOpAssign(string op : "~", T)(T op) { this = this ~ op; }
|
||||||
|
@ -610,8 +853,6 @@ struct GenericPath(F) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest {
|
unittest {
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
|
|
||||||
assert(PosixPath("hello/world").bySegment.equal([PosixPath.Segment("hello",'/'), PosixPath.Segment("world")]));
|
assert(PosixPath("hello/world").bySegment.equal([PosixPath.Segment("hello",'/'), PosixPath.Segment("world")]));
|
||||||
assert(PosixPath("/hello/world/").bySegment.equal([PosixPath.Segment("",'/'), PosixPath.Segment("hello",'/'), PosixPath.Segment("world",'/')]));
|
assert(PosixPath("/hello/world/").bySegment.equal([PosixPath.Segment("",'/'), PosixPath.Segment("hello",'/'), PosixPath.Segment("world",'/')]));
|
||||||
assert(PosixPath("hello\\world").bySegment.equal([PosixPath.Segment("hello\\world")]));
|
assert(PosixPath("hello\\world").bySegment.equal([PosixPath.Segment("hello\\world")]));
|
||||||
|
@ -621,10 +862,18 @@ 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")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
assert(PosixPath("hello/world").bySegment2.equal([PosixPath.Segment2("hello",'/'), PosixPath.Segment2("world")]));
|
||||||
|
assert(PosixPath("/hello/world/").bySegment2.equal([PosixPath.Segment2("",'/'), PosixPath.Segment2("hello",'/'), PosixPath.Segment2("world",'/')]));
|
||||||
|
assert(PosixPath("hello\\world").bySegment2.equal([PosixPath.Segment2("hello\\world")]));
|
||||||
|
assert(WindowsPath("hello/world").bySegment2.equal([WindowsPath.Segment2("hello",'/'), WindowsPath.Segment2("world")]));
|
||||||
|
assert(WindowsPath("/hello/world/").bySegment2.equal([WindowsPath.Segment2("",'/'), WindowsPath.Segment2("hello",'/'), WindowsPath.Segment2("world",'/')]));
|
||||||
|
assert(WindowsPath("hello\\w/orld").bySegment2.equal([WindowsPath.Segment2("hello",'\\'), WindowsPath.Segment2("w",'/'), WindowsPath.Segment2("orld")]));
|
||||||
|
assert(WindowsPath("hello/w\\orld").bySegment2.equal([WindowsPath.Segment2("hello",'/'), WindowsPath.Segment2("w",'\\'), WindowsPath.Segment2("orld")]));
|
||||||
|
}
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto unc = "\\\\server\\share\\path";
|
auto unc = "\\\\server\\share\\path";
|
||||||
auto uncp = WindowsPath(unc);
|
auto uncp = WindowsPath(unc);
|
||||||
|
@ -704,6 +953,55 @@ unittest
|
||||||
assert(PosixPath("foo/") ~ NativePath("bar") == PosixPath("foo/bar"));
|
assert(PosixPath("foo/") ~ NativePath("bar") == PosixPath("foo/bar"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto unc = "\\\\server\\share\\path";
|
||||||
|
auto uncp = WindowsPath(unc);
|
||||||
|
assert(uncp.absolute);
|
||||||
|
uncp.normalize();
|
||||||
|
version(Windows) assert(uncp.toNativeString() == unc);
|
||||||
|
assert(uncp.absolute);
|
||||||
|
assert(!uncp.endsWithSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto abspath = "/test/path/";
|
||||||
|
auto abspathp = PosixPath(abspath);
|
||||||
|
assert(abspathp.toString() == abspath);
|
||||||
|
version(Windows) {} else assert(abspathp.toNativeString() == abspath);
|
||||||
|
assert(abspathp.absolute);
|
||||||
|
assert(abspathp.endsWithSlash);
|
||||||
|
alias S = PosixPath.Segment2;
|
||||||
|
assert(abspathp.bySegment2.equal([S("", '/'), S("test", '/'), S("path", '/')]));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto relpath = "test/path/";
|
||||||
|
auto relpathp = PosixPath(relpath);
|
||||||
|
assert(relpathp.toString() == relpath);
|
||||||
|
version(Windows) assert(relpathp.toNativeString() == "test/path/");
|
||||||
|
else assert(relpathp.toNativeString() == relpath);
|
||||||
|
assert(!relpathp.absolute);
|
||||||
|
assert(relpathp.endsWithSlash);
|
||||||
|
alias S = PosixPath.Segment2;
|
||||||
|
assert(relpathp.bySegment2.equal([S("test", '/'), S("path", '/')]));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto winpath = "C:\\windows\\test";
|
||||||
|
auto winpathp = WindowsPath(winpath);
|
||||||
|
assert(winpathp.toString() == "C:\\windows\\test");
|
||||||
|
assert((cast(PosixPath)winpathp).toString() == "/C:/windows/test", (cast(PosixPath)winpathp).toString());
|
||||||
|
version(Windows) assert(winpathp.toNativeString() == winpath);
|
||||||
|
else assert(winpathp.toNativeString() == "/C:/windows/test");
|
||||||
|
assert(winpathp.absolute);
|
||||||
|
assert(!winpathp.endsWithSlash);
|
||||||
|
alias S = WindowsPath.Segment2;
|
||||||
|
assert(winpathp.bySegment2.equal([S("", '/'), S("C:", '\\'), S("windows", '\\'), S("test")]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@safe unittest {
|
@safe unittest {
|
||||||
import std.array : appender;
|
import std.array : appender;
|
||||||
auto app = appender!(PosixPath[]);
|
auto app = appender!(PosixPath[]);
|
||||||
|
@ -714,7 +1012,6 @@ unittest
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest {
|
unittest {
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
import std.exception : assertThrown, assertNotThrown;
|
import std.exception : assertThrown, assertNotThrown;
|
||||||
|
|
||||||
assertThrown!PathValidationException(WindowsPath.Segment("foo/bar"));
|
assertThrown!PathValidationException(WindowsPath.Segment("foo/bar"));
|
||||||
|
@ -727,6 +1024,20 @@ unittest {
|
||||||
assert(p.toString() == "/foo%2fbar/baz%2Fbam", p.toString);
|
assert(p.toString() == "/foo%2fbar/baz%2Fbam", p.toString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
import std.exception : assertThrown, assertNotThrown;
|
||||||
|
|
||||||
|
assertThrown!PathValidationException(WindowsPath.Segment2("foo/bar"));
|
||||||
|
assertThrown!PathValidationException(PosixPath.Segment2("foo/bar"));
|
||||||
|
assertNotThrown!PathValidationException(InetPath.Segment2("foo/bar"));
|
||||||
|
|
||||||
|
auto p = InetPath("/foo%2fbar/");
|
||||||
|
import std.conv : to;
|
||||||
|
assert(p.bySegment2.equal([InetPath.Segment2("",'/'), InetPath.Segment2("foo/bar",'/')]), p.bySegment2.to!string);
|
||||||
|
p ~= InetPath.Segment2("baz/bam");
|
||||||
|
assert(p.toString() == "/foo%2fbar/baz%2Fbam", p.toString);
|
||||||
|
}
|
||||||
|
|
||||||
unittest {
|
unittest {
|
||||||
assert(!PosixPath("").hasParentPath);
|
assert(!PosixPath("").hasParentPath);
|
||||||
assert(!PosixPath("/").hasParentPath);
|
assert(!PosixPath("/").hasParentPath);
|
||||||
|
@ -754,6 +1065,10 @@ unittest {
|
||||||
assert(WindowsPath([WindowsPath.Segment("foo"), WindowsPath.Segment("bar")]).toString() == "foo\\bar");
|
assert(WindowsPath([WindowsPath.Segment("foo"), WindowsPath.Segment("bar")]).toString() == "foo\\bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
assert(WindowsPath([WindowsPath.Segment2("foo"), WindowsPath.Segment2("bar")]).toString() == "foo\\bar");
|
||||||
|
}
|
||||||
|
|
||||||
/// Thrown when an invalid string representation of a path is detected.
|
/// Thrown when an invalid string representation of a path is detected.
|
||||||
class PathValidationException : Exception {
|
class PathValidationException : Exception {
|
||||||
this(string text, string file = __FILE__, size_t line = cast(size_t)__LINE__, Throwable next = null)
|
this(string text, string file = __FILE__, size_t line = cast(size_t)__LINE__, Throwable next = null)
|
||||||
|
@ -925,7 +1240,6 @@ struct WindowsPathFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest {
|
unittest {
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
|
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
|
||||||
assert(decodeSegment!Segment("foo").equal([Segment("foo")]));
|
assert(decodeSegment!Segment("foo").equal([Segment("foo")]));
|
||||||
assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
|
assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
|
||||||
|
@ -935,13 +1249,12 @@ struct WindowsPathFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
static string decodeSingleSegment(string segment)
|
static string decodeSingleSegment(string segment)
|
||||||
{
|
@nogc {
|
||||||
assert(segment.length == 0 || segment[$-1] != '/');
|
assert(segment.length == 0 || segment[$-1] != '/');
|
||||||
return segment;
|
return segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest {
|
unittest {
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
|
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
|
||||||
assert(decodeSingleSegment("foo") == "foo");
|
assert(decodeSingleSegment("foo") == "foo");
|
||||||
assert(decodeSingleSegment("fo%20o") == "fo%20o");
|
assert(decodeSingleSegment("fo%20o") == "fo%20o");
|
||||||
|
@ -1002,8 +1315,7 @@ struct WindowsPathFormat {
|
||||||
|
|
||||||
static string encodeSegment(string segment)
|
static string encodeSegment(string segment)
|
||||||
{
|
{
|
||||||
assert(segment.length > 0, "Path segment string must not be empty.");
|
assert(segment.length == 0 || segment[$-1] != '/');
|
||||||
assert(segment[$-1] != '/');
|
|
||||||
return segment;
|
return segment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1132,7 +1444,6 @@ struct PosixPathFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest {
|
unittest {
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
|
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
|
||||||
assert(decodeSegment!Segment("foo").equal([Segment("foo")]));
|
assert(decodeSegment!Segment("foo").equal([Segment("foo")]));
|
||||||
assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
|
assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
|
||||||
|
@ -1140,13 +1451,12 @@ struct PosixPathFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
static string decodeSingleSegment(string segment)
|
static string decodeSingleSegment(string segment)
|
||||||
{
|
@nogc {
|
||||||
assert(segment.length == 0 || segment[$-1] != '/');
|
assert(segment.length == 0 || segment[$-1] != '/');
|
||||||
return segment;
|
return segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest {
|
unittest {
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
|
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
|
||||||
assert(decodeSingleSegment("foo") == "foo");
|
assert(decodeSingleSegment("foo") == "foo");
|
||||||
assert(decodeSingleSegment("fo%20o\\") == "fo%20o\\");
|
assert(decodeSingleSegment("fo%20o\\") == "fo%20o\\");
|
||||||
|
@ -1154,8 +1464,7 @@ struct PosixPathFormat {
|
||||||
|
|
||||||
static string encodeSegment(string segment)
|
static string encodeSegment(string segment)
|
||||||
{
|
{
|
||||||
assert(segment.length > 0, "Path segment string must not be empty.");
|
assert(segment.length == 0 || segment[$-1] != '/');
|
||||||
assert(segment[$-1] != '/');
|
|
||||||
return segment;
|
return segment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1173,7 +1482,9 @@ struct InetPathFormat {
|
||||||
foreach (e; segments) {
|
foreach (e; segments) {
|
||||||
if (!first || lastsep) dst.put('/');
|
if (!first || lastsep) dst.put('/');
|
||||||
else first = false;
|
else first = false;
|
||||||
encodeSegment(dst, e.name);
|
static if (is(typeof(e.encodedName)))
|
||||||
|
dst.put(e.encodedName);
|
||||||
|
else encodeSegment(dst, e.name);
|
||||||
lastsep = e.separator;
|
lastsep = e.separator;
|
||||||
}
|
}
|
||||||
if (lastsep) dst.put('/');
|
if (lastsep) dst.put('/');
|
||||||
|
@ -1309,18 +1620,26 @@ struct InetPathFormat {
|
||||||
deprecated("Use decodeSingleSegment instead.")
|
deprecated("Use decodeSingleSegment instead.")
|
||||||
static auto decodeSegment(S)(string segment)
|
static auto decodeSegment(S)(string segment)
|
||||||
{
|
{
|
||||||
|
import std.algorithm.searching : any;
|
||||||
|
import std.array : array;
|
||||||
|
import std.exception : assumeUnique;
|
||||||
import std.range : only;
|
import std.range : only;
|
||||||
|
import std.utf : byCodeUnit;
|
||||||
|
|
||||||
if (!segment.length) return only(S.fromTrustedString(null));
|
if (!segment.length) return only(S.fromTrustedString(null));
|
||||||
char sep = '\0';
|
char sep = '\0';
|
||||||
if (segment[$-1] == '/') {
|
if (segment[$-1] == '/') {
|
||||||
sep = '/';
|
sep = '/';
|
||||||
segment = segment[0 .. $-1];
|
segment = segment[0 .. $-1];
|
||||||
}
|
}
|
||||||
return only(S(decodeSingleSegment(segment), sep));
|
|
||||||
|
if (!segment.byCodeUnit.any!(c => c == '%'))
|
||||||
|
return only(S(segment, sep));
|
||||||
|
string n = decodeSingleSegment(segment).array;
|
||||||
|
return only(S(n, sep));
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest {
|
unittest {
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
|
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
|
||||||
assert(decodeSegment!Segment("foo").equal([Segment("foo")]));
|
assert(decodeSegment!Segment("foo").equal([Segment("foo")]));
|
||||||
assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
|
assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
|
||||||
|
@ -1328,9 +1647,8 @@ struct InetPathFormat {
|
||||||
assert(decodeSegment!Segment("foo%20").equal([Segment("foo ")]));
|
assert(decodeSegment!Segment("foo%20").equal([Segment("foo ")]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static string decodeSingleSegment(string segment)
|
static auto decodeSingleSegment(string segment)
|
||||||
{
|
@nogc {
|
||||||
import std.array : appender;
|
|
||||||
import std.string : indexOf;
|
import std.string : indexOf;
|
||||||
|
|
||||||
static int hexDigit(char ch) @safe nothrow @nogc {
|
static int hexDigit(char ch) @safe nothrow @nogc {
|
||||||
|
@ -1340,32 +1658,48 @@ struct InetPathFormat {
|
||||||
else return ch - 'A' + 10;
|
else return ch - 'A' + 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
static string urlDecode(string s) @safe nothrow {
|
static struct R {
|
||||||
auto idx = s.indexOf('%');
|
@safe pure nothrow @nogc:
|
||||||
if (idx < 0) return s;
|
|
||||||
|
|
||||||
auto ret = appender!string;
|
private {
|
||||||
ret.put(s[0 .. idx]);
|
string m_str;
|
||||||
|
size_t m_index;
|
||||||
for (size_t i = idx; i < s.length; i++) {
|
|
||||||
if (s[i] == '%') {
|
|
||||||
assert(i+2 < s.length, "segment string not validated!?");
|
|
||||||
ret.put(cast(char)(hexDigit(s[i+1]) << 4 | hexDigit(s[i+2])));
|
|
||||||
i += 2;
|
|
||||||
} else ret.put(s[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret.data;
|
this(string s)
|
||||||
|
{
|
||||||
|
m_str = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(segment.length == 0 || segment[$-1] != '/');
|
@property bool empty() const { return m_index >= m_str.length; }
|
||||||
return urlDecode(segment);
|
|
||||||
|
@property char front()
|
||||||
|
const {
|
||||||
|
auto ch = m_str[m_index];
|
||||||
|
if (ch != '%') return ch;
|
||||||
|
|
||||||
|
auto a = m_str[m_index+1];
|
||||||
|
auto b = m_str[m_index+2];
|
||||||
|
return cast(char)(16 * hexDigit(a) + hexDigit(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void popFront()
|
||||||
|
{
|
||||||
|
assert(!empty);
|
||||||
|
if (m_str[m_index] == '%') m_index += 3;
|
||||||
|
else m_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return R(segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest {
|
unittest {
|
||||||
assert(decodeSingleSegment("foo") == "foo");
|
scope (failure) assert(false);
|
||||||
assert(decodeSingleSegment("fo%20o\\") == "fo o\\");
|
|
||||||
assert(decodeSingleSegment("foo%20") == "foo ");
|
assert(decodeSingleSegment("foo").equal("foo"));
|
||||||
|
assert(decodeSingleSegment("fo%20o\\").equal("fo o\\"));
|
||||||
|
assert(decodeSingleSegment("foo%20").equal("foo "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1424,4 +1758,5 @@ struct InetPathFormat {
|
||||||
|
|
||||||
unittest { // regression tests
|
unittest { // regression tests
|
||||||
assert(NativePath("").bySegment.empty);
|
assert(NativePath("").bySegment.empty);
|
||||||
|
assert(NativePath("").bySegment2.empty);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue