Rework the path segment encoding/decoding API.
- Deprecates Format.decodeSegment (returning a range of GemericPath.Segment) and added decodeSingleSegment instead, which returns a string instead. - Adds Format.encodeSegment to encode a single path segment
This commit is contained in:
parent
591ab4a944
commit
32858b7aee
|
@ -214,7 +214,6 @@ struct GenericPath(F) {
|
|||
|
||||
private {
|
||||
string m_name;
|
||||
string m_encodedName;
|
||||
char m_separator = 0;
|
||||
}
|
||||
|
||||
|
@ -319,7 +318,7 @@ struct GenericPath(F) {
|
|||
|
||||
private {
|
||||
string m_path;
|
||||
ReturnType!(Format.decodeSegment!Segment) m_fronts;
|
||||
Segment m_front;
|
||||
}
|
||||
|
||||
private this(string path)
|
||||
|
@ -327,34 +326,37 @@ struct GenericPath(F) {
|
|||
m_path = path;
|
||||
if (m_path.length) {
|
||||
auto ap = Format.getAbsolutePrefix(m_path);
|
||||
if (ap.length) {
|
||||
m_fronts = Format.decodeSegment!Segment(ap);
|
||||
m_path = m_path[ap.length .. $];
|
||||
assert(!m_fronts.empty);
|
||||
} else readFront();
|
||||
} else assert(m_fronts.empty);
|
||||
if (ap.length && !Format.isSeparator(ap[0]))
|
||||
m_front = Segment.fromTrustedString(null, '/');
|
||||
else readFront();
|
||||
}
|
||||
}
|
||||
|
||||
@property bool empty() const nothrow @nogc { return m_path.length == 0 && m_fronts.empty; }
|
||||
@property bool empty() const nothrow @nogc { return m_path.length == 0 && m_front == Segment.init; }
|
||||
|
||||
@property PathRange save() { return this; }
|
||||
|
||||
@property Segment front() { return m_fronts.front; }
|
||||
@property Segment front() { return m_front; }
|
||||
|
||||
void popFront()
|
||||
nothrow {
|
||||
assert(!m_fronts.empty);
|
||||
m_fronts.popFront();
|
||||
if (m_fronts.empty && m_path.length)
|
||||
readFront();
|
||||
assert(m_front != Segment.init);
|
||||
if (m_path.length) readFront();
|
||||
else m_front = Segment.init;
|
||||
}
|
||||
|
||||
private void readFront()
|
||||
{
|
||||
auto n = Format.getFrontNode(m_path);
|
||||
m_fronts = Format.decodeSegment!Segment(n);
|
||||
m_path = m_path[n.length .. $];
|
||||
assert(!m_fronts.empty);
|
||||
|
||||
char sep = '\0';
|
||||
if (Format.isSeparator(n[$-1])) {
|
||||
sep = n[$-1];
|
||||
n = n[0 .. $-1];
|
||||
}
|
||||
m_front = Segment.fromTrustedString(Format.decodeSingleSegment(n), sep);
|
||||
assert(m_front != Segment.init);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -452,13 +454,13 @@ struct GenericPath(F) {
|
|||
/// Returns the trailing segment of the path.
|
||||
@property Segment head()
|
||||
const {
|
||||
auto s = Format.decodeSegment!Segment(Format.getBackNode(m_path));
|
||||
auto ret = s.front;
|
||||
while (!s.empty) {
|
||||
s.popFront();
|
||||
if (!s.empty) ret = s.front;
|
||||
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 ret;
|
||||
return Segment.fromTrustedString(Format.decodeSingleSegment(n), sep);
|
||||
}
|
||||
|
||||
/** Determines if the `parentPath` property is valid.
|
||||
|
@ -893,6 +895,7 @@ struct WindowsPathFormat {
|
|||
assert(getBackNode("foo\\") == "foo\\");
|
||||
}
|
||||
|
||||
deprecated("Use decodeSingleSegment instead.")
|
||||
static auto decodeSegment(S)(string segment)
|
||||
{
|
||||
static struct R {
|
||||
|
@ -931,6 +934,21 @@ struct WindowsPathFormat {
|
|||
assert(decodeSegment!Segment("bar:\\").equal([Segment("",'/'), Segment("bar:", '\\')]));
|
||||
}
|
||||
|
||||
static string decodeSingleSegment(string segment)
|
||||
{
|
||||
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");
|
||||
assert(decodeSingleSegment("C:") == "C:");
|
||||
assert(decodeSingleSegment("bar:") == "bar:");
|
||||
}
|
||||
|
||||
static string validatePath(string path)
|
||||
@nogc {
|
||||
import std.algorithm.comparison : among;
|
||||
|
@ -981,6 +999,13 @@ struct WindowsPathFormat {
|
|||
assert(validatePath("foo\\b:ar") !is null);
|
||||
assert(validatePath("foo\\bar:\\baz") !is null);
|
||||
}
|
||||
|
||||
static string encodeSegment(string segment)
|
||||
{
|
||||
assert(segment.length > 0, "Path segment string must not be empty.");
|
||||
assert(segment[$-1] != '/');
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1095,6 +1120,7 @@ struct PosixPathFormat {
|
|||
assert(validatePath("foo\0bar") !is null);
|
||||
}
|
||||
|
||||
deprecated("Use decodeSingleSegment instead.")
|
||||
static auto decodeSegment(S)(string segment)
|
||||
{
|
||||
assert(segment.length > 0, "Path segment string must not be empty.");
|
||||
|
@ -1112,6 +1138,26 @@ struct PosixPathFormat {
|
|||
assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
|
||||
assert(decodeSegment!Segment("fo%20o\\").equal([Segment("fo%20o\\")]));
|
||||
}
|
||||
|
||||
static string decodeSingleSegment(string segment)
|
||||
{
|
||||
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\\");
|
||||
}
|
||||
|
||||
static string encodeSegment(string segment)
|
||||
{
|
||||
assert(segment.length > 0, "Path segment string must not be empty.");
|
||||
assert(segment[$-1] != '/');
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1122,28 +1168,12 @@ struct PosixPathFormat {
|
|||
struct InetPathFormat {
|
||||
static void toString(I, O)(I segments, O dst)
|
||||
{
|
||||
import std.format : formattedWrite;
|
||||
|
||||
char lastsep = '\0';
|
||||
bool first = true;
|
||||
foreach (e; segments) {
|
||||
if (!first || lastsep) dst.put('/');
|
||||
else first = false;
|
||||
foreach (char c; e.name) {
|
||||
switch (c) {
|
||||
default:
|
||||
dst.formattedWrite("%%%02X", c);
|
||||
break;
|
||||
case 'a': .. case 'z':
|
||||
case 'A': .. case 'Z':
|
||||
case '0': .. case '9':
|
||||
case '-', '.', '_', '~':
|
||||
case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':
|
||||
case ':', '@':
|
||||
dst.put(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
encodeSegment(dst, e.name);
|
||||
lastsep = e.separator;
|
||||
}
|
||||
if (lastsep) dst.put('/');
|
||||
|
@ -1203,10 +1233,13 @@ struct InetPathFormat {
|
|||
|
||||
static string getBackNode(string path)
|
||||
@nogc {
|
||||
import std.string : lastIndexOf;
|
||||
|
||||
if (!path.length) return path;
|
||||
foreach_reverse (i; 0 .. path.length-1)
|
||||
if (path[i] == '/')
|
||||
return path[i+1 .. $];
|
||||
ptrdiff_t idx;
|
||||
try idx = path[0 .. $-1].lastIndexOf('/');
|
||||
catch (Exception e) assert(false, e.msg);
|
||||
if (idx >= 0) return path[idx+1 .. $];
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -1273,11 +1306,31 @@ struct InetPathFormat {
|
|||
assert(validatePath("föö") !is null);
|
||||
}
|
||||
|
||||
deprecated("Use decodeSingleSegment instead.")
|
||||
static auto decodeSegment(S)(string segment)
|
||||
{
|
||||
import std.array : appender;
|
||||
import std.format : formattedRead;
|
||||
import std.range : only;
|
||||
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));
|
||||
}
|
||||
|
||||
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", '/')]));
|
||||
assert(decodeSegment!Segment("fo%20o\\").equal([Segment("fo o\\")]));
|
||||
assert(decodeSegment!Segment("foo%20").equal([Segment("foo ")]));
|
||||
}
|
||||
|
||||
static string decodeSingleSegment(string segment)
|
||||
{
|
||||
import std.array : appender;
|
||||
import std.string : indexOf;
|
||||
|
||||
static int hexDigit(char ch) @safe nothrow @nogc {
|
||||
|
@ -1305,19 +1358,67 @@ struct InetPathFormat {
|
|||
return ret.data;
|
||||
}
|
||||
|
||||
if (!segment.length) return only(S.fromTrustedString(null));
|
||||
if (segment[$-1] == '/')
|
||||
return only(S.fromTrustedString(urlDecode(segment[0 .. $-1]), '/'));
|
||||
return only(S.fromTrustedString(urlDecode(segment)));
|
||||
assert(segment.length == 0 || segment[$-1] != '/');
|
||||
return urlDecode(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(decodeSegment!Segment("foo").equal([Segment("foo")]));
|
||||
assert(decodeSegment!Segment("foo/").equal([Segment("foo", '/')]));
|
||||
assert(decodeSegment!Segment("fo%20o\\").equal([Segment("fo o\\")]));
|
||||
assert(decodeSegment!Segment("foo%20").equal([Segment("foo ")]));
|
||||
assert(decodeSingleSegment("foo") == "foo");
|
||||
assert(decodeSingleSegment("fo%20o\\") == "fo o\\");
|
||||
assert(decodeSingleSegment("foo%20") == "foo ");
|
||||
}
|
||||
|
||||
|
||||
static string encodeSegment(string segment)
|
||||
{
|
||||
import std.array : appender;
|
||||
|
||||
foreach (i, char c; segment) {
|
||||
switch (c) {
|
||||
default:
|
||||
auto ret = appender!string;
|
||||
ret.put(segment[0 .. i]);
|
||||
encodeSegment(ret, segment[i .. $]);
|
||||
return ret.data;
|
||||
case 'a': .. case 'z':
|
||||
case 'A': .. case 'Z':
|
||||
case '0': .. case '9':
|
||||
case '-', '.', '_', '~':
|
||||
case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':
|
||||
case ':', '@':
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
unittest {
|
||||
assert(encodeSegment("foo") == "foo");
|
||||
assert(encodeSegment("foo bar") == "foo%20bar");
|
||||
}
|
||||
|
||||
static void encodeSegment(R)(ref R dst, string segment)
|
||||
{
|
||||
static immutable char[16] digit = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
|
||||
foreach (char c; segment) {
|
||||
switch (c) {
|
||||
default:
|
||||
dst.put('%');
|
||||
dst.put(digit[uint(c) / 16]);
|
||||
dst.put(digit[uint(c) % 16]);
|
||||
break;
|
||||
case 'a': .. case 'z':
|
||||
case 'A': .. case 'Z':
|
||||
case '0': .. case '9':
|
||||
case '-', '.', '_', '~':
|
||||
case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':
|
||||
case ':', '@':
|
||||
dst.put(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue