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.
|
||||
|
||||
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.
|
||||
Authors: Sönke Ludwig
|
||||
*/
|
||||
module vibe.core.path;
|
||||
|
||||
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.exception : enforce;
|
||||
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
|
||||
if (isInstanceOf!(GenericPath, Path))
|
||||
{
|
||||
import std.algorithm.comparison : equal;
|
||||
import std.array : array, replicate;
|
||||
import std.range : chain, drop, take;
|
||||
|
||||
|
@ -345,6 +344,201 @@ struct GenericPath(F) {
|
|||
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()
|
||||
{
|
||||
auto n = Format.getFrontNode(m_path);
|
||||
|
@ -355,11 +549,12 @@ struct GenericPath(F) {
|
|||
sep = n[$-1];
|
||||
n = n[0 .. $-1];
|
||||
}
|
||||
m_front = Segment.fromTrustedString(Format.decodeSingleSegment(n), sep);
|
||||
assert(m_front != Segment.init);
|
||||
m_front = Segment2.fromTrustedEncodedString(n, sep);
|
||||
assert(m_front != Segment2.init);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private {
|
||||
string m_path;
|
||||
}
|
||||
|
@ -390,6 +585,12 @@ struct GenericPath(F) {
|
|||
import std.range : only;
|
||||
this(only(segment));
|
||||
}
|
||||
/// ditto
|
||||
this(Segment2 segment)
|
||||
{
|
||||
import std.range : only;
|
||||
this(only(segment));
|
||||
}
|
||||
|
||||
/** Constructs a path from an input range of `Segment`s.
|
||||
|
||||
|
@ -405,6 +606,15 @@ struct GenericPath(F) {
|
|||
Format.toString(segments, dst);
|
||||
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.
|
||||
|
||||
|
@ -451,16 +661,39 @@ struct GenericPath(F) {
|
|||
/// Iterates over the path by `Segment`.
|
||||
@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.
|
||||
@property Segment head()
|
||||
const {
|
||||
import std.array : array;
|
||||
|
||||
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 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.
|
||||
|
@ -580,8 +813,12 @@ struct GenericPath(F) {
|
|||
/// ditto
|
||||
GenericPath opBinary(string op : "~")(Segment subpath) const { return this ~ GenericPath(subpath); }
|
||||
/// 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); }
|
||||
/// 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 {
|
||||
assert(!subpath.absolute || m_path.length == 0, "Cannot append absolute path.");
|
||||
if (endsWithSlash || empty) return GenericPath.fromTrustedString(m_path ~ subpath.m_path);
|
||||
|
@ -595,6 +832,12 @@ struct GenericPath(F) {
|
|||
{
|
||||
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.
|
||||
void opOpAssign(string op : "~", T)(T op) { this = this ~ op; }
|
||||
|
@ -610,8 +853,6 @@ struct GenericPath(F) {
|
|||
}
|
||||
|
||||
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("",'/'), PosixPath.Segment("hello",'/'), PosixPath.Segment("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")]));
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
import std.algorithm.comparison : equal;
|
||||
|
||||
{
|
||||
auto unc = "\\\\server\\share\\path";
|
||||
auto uncp = WindowsPath(unc);
|
||||
|
@ -704,6 +953,55 @@ unittest
|
|||
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 {
|
||||
import std.array : appender;
|
||||
auto app = appender!(PosixPath[]);
|
||||
|
@ -714,7 +1012,6 @@ unittest
|
|||
}
|
||||
|
||||
unittest {
|
||||
import std.algorithm.comparison : equal;
|
||||
import std.exception : assertThrown, assertNotThrown;
|
||||
|
||||
assertThrown!PathValidationException(WindowsPath.Segment("foo/bar"));
|
||||
|
@ -727,6 +1024,20 @@ unittest {
|
|||
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 {
|
||||
assert(!PosixPath("").hasParentPath);
|
||||
assert(!PosixPath("/").hasParentPath);
|
||||
|
@ -754,6 +1065,10 @@ unittest {
|
|||
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.
|
||||
class PathValidationException : Exception {
|
||||
this(string text, string file = __FILE__, size_t line = cast(size_t)__LINE__, Throwable next = null)
|
||||
|
@ -925,7 +1240,6 @@ struct WindowsPathFormat {
|
|||
}
|
||||
|
||||
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); }}
|
||||
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)
|
||||
{
|
||||
@nogc {
|
||||
assert(segment.length == 0 || segment[$-1] != '/');
|
||||
return segment;
|
||||
}
|
||||
|
||||
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); }}
|
||||
assert(decodeSingleSegment("foo") == "foo");
|
||||
assert(decodeSingleSegment("fo%20o") == "fo%20o");
|
||||
|
@ -1002,8 +1315,7 @@ struct WindowsPathFormat {
|
|||
|
||||
static string encodeSegment(string segment)
|
||||
{
|
||||
assert(segment.length > 0, "Path segment string must not be empty.");
|
||||
assert(segment[$-1] != '/');
|
||||
assert(segment.length == 0 || segment[$-1] != '/');
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
|
@ -1132,7 +1444,6 @@ struct PosixPathFormat {
|
|||
}
|
||||
|
||||
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); }}
|
||||
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)
|
||||
{
|
||||
@nogc {
|
||||
assert(segment.length == 0 || segment[$-1] != '/');
|
||||
return segment;
|
||||
}
|
||||
|
||||
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); }}
|
||||
assert(decodeSingleSegment("foo") == "foo");
|
||||
assert(decodeSingleSegment("fo%20o\\") == "fo%20o\\");
|
||||
|
@ -1154,8 +1464,7 @@ struct PosixPathFormat {
|
|||
|
||||
static string encodeSegment(string segment)
|
||||
{
|
||||
assert(segment.length > 0, "Path segment string must not be empty.");
|
||||
assert(segment[$-1] != '/');
|
||||
assert(segment.length == 0 || segment[$-1] != '/');
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
|
@ -1173,7 +1482,9 @@ struct InetPathFormat {
|
|||
foreach (e; segments) {
|
||||
if (!first || lastsep) dst.put('/');
|
||||
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;
|
||||
}
|
||||
if (lastsep) dst.put('/');
|
||||
|
@ -1309,18 +1620,26 @@ struct InetPathFormat {
|
|||
deprecated("Use decodeSingleSegment instead.")
|
||||
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.utf : byCodeUnit;
|
||||
|
||||
if (!segment.length) return only(S.fromTrustedString(null));
|
||||
char sep = '\0';
|
||||
if (segment[$-1] == '/') {
|
||||
sep = '/';
|
||||
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 {
|
||||
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); }}
|
||||
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 ")]));
|
||||
}
|
||||
|
||||
static string decodeSingleSegment(string segment)
|
||||
{
|
||||
import std.array : appender;
|
||||
static auto decodeSingleSegment(string segment)
|
||||
@nogc {
|
||||
import std.string : indexOf;
|
||||
|
||||
static int hexDigit(char ch) @safe nothrow @nogc {
|
||||
|
@ -1340,32 +1658,48 @@ struct InetPathFormat {
|
|||
else return ch - 'A' + 10;
|
||||
}
|
||||
|
||||
static string urlDecode(string s) @safe nothrow {
|
||||
auto idx = s.indexOf('%');
|
||||
if (idx < 0) return s;
|
||||
static struct R {
|
||||
@safe pure nothrow @nogc:
|
||||
|
||||
auto ret = appender!string;
|
||||
ret.put(s[0 .. idx]);
|
||||
|
||||
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]);
|
||||
private {
|
||||
string m_str;
|
||||
size_t m_index;
|
||||
}
|
||||
|
||||
return ret.data;
|
||||
this(string s)
|
||||
{
|
||||
m_str = s;
|
||||
}
|
||||
|
||||
@property bool empty() const { return m_index >= m_str.length; }
|
||||
|
||||
@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++;
|
||||
}
|
||||
}
|
||||
|
||||
assert(segment.length == 0 || segment[$-1] != '/');
|
||||
return urlDecode(segment);
|
||||
return R(segment);
|
||||
}
|
||||
|
||||
unittest {
|
||||
assert(decodeSingleSegment("foo") == "foo");
|
||||
assert(decodeSingleSegment("fo%20o\\") == "fo o\\");
|
||||
assert(decodeSingleSegment("foo%20") == "foo ");
|
||||
scope (failure) assert(false);
|
||||
|
||||
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
|
||||
assert(NativePath("").bySegment.empty);
|
||||
assert(NativePath("").bySegment2.empty);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue