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:
Sönke Ludwig 2019-09-17 14:32:16 +02:00
parent 591ab4a944
commit 32858b7aee

View file

@ -214,7 +214,6 @@ struct GenericPath(F) {
private { private {
string m_name; string m_name;
string m_encodedName;
char m_separator = 0; char m_separator = 0;
} }
@ -319,7 +318,7 @@ struct GenericPath(F) {
private { private {
string m_path; string m_path;
ReturnType!(Format.decodeSegment!Segment) m_fronts; Segment m_front;
} }
private this(string path) private this(string path)
@ -327,34 +326,37 @@ struct GenericPath(F) {
m_path = path; m_path = path;
if (m_path.length) { if (m_path.length) {
auto ap = Format.getAbsolutePrefix(m_path); auto ap = Format.getAbsolutePrefix(m_path);
if (ap.length) { if (ap.length && !Format.isSeparator(ap[0]))
m_fronts = Format.decodeSegment!Segment(ap); m_front = Segment.fromTrustedString(null, '/');
m_path = m_path[ap.length .. $]; else readFront();
assert(!m_fronts.empty); }
} else readFront();
} else assert(m_fronts.empty);
} }
@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 PathRange save() { return this; }
@property Segment front() { return m_fronts.front; } @property Segment front() { return m_front; }
void popFront() void popFront()
nothrow { nothrow {
assert(!m_fronts.empty); assert(m_front != Segment.init);
m_fronts.popFront(); if (m_path.length) readFront();
if (m_fronts.empty && m_path.length) else m_front = Segment.init;
readFront();
} }
private void readFront() private void readFront()
{ {
auto n = Format.getFrontNode(m_path); auto n = Format.getFrontNode(m_path);
m_fronts = Format.decodeSegment!Segment(n);
m_path = m_path[n.length .. $]; 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. /// Returns the trailing segment of the path.
@property Segment head() @property Segment head()
const { const {
auto s = Format.decodeSegment!Segment(Format.getBackNode(m_path)); auto n = Format.getBackNode(m_path);
auto ret = s.front; char sep = '\0';
while (!s.empty) { if (n.length > 0 && Format.isSeparator(n[$-1])) {
s.popFront(); sep = n[$-1];
if (!s.empty) ret = s.front; n = n[0 .. $-1];
} }
return ret; return Segment.fromTrustedString(Format.decodeSingleSegment(n), sep);
} }
/** Determines if the `parentPath` property is valid. /** Determines if the `parentPath` property is valid.
@ -893,6 +895,7 @@ struct WindowsPathFormat {
assert(getBackNode("foo\\") == "foo\\"); assert(getBackNode("foo\\") == "foo\\");
} }
deprecated("Use decodeSingleSegment instead.")
static auto decodeSegment(S)(string segment) static auto decodeSegment(S)(string segment)
{ {
static struct R { static struct R {
@ -931,6 +934,21 @@ struct WindowsPathFormat {
assert(decodeSegment!Segment("bar:\\").equal([Segment("",'/'), Segment("bar:", '\\')])); 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) static string validatePath(string path)
@nogc { @nogc {
import std.algorithm.comparison : among; import std.algorithm.comparison : among;
@ -981,6 +999,13 @@ struct WindowsPathFormat {
assert(validatePath("foo\\b:ar") !is null); assert(validatePath("foo\\b:ar") !is null);
assert(validatePath("foo\\bar:\\baz") !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); assert(validatePath("foo\0bar") !is null);
} }
deprecated("Use decodeSingleSegment instead.")
static auto decodeSegment(S)(string segment) static auto decodeSegment(S)(string segment)
{ {
assert(segment.length > 0, "Path segment string must not be empty."); 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("foo/").equal([Segment("foo", '/')]));
assert(decodeSegment!Segment("fo%20o\\").equal([Segment("fo%20o\\")])); 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 { struct InetPathFormat {
static void toString(I, O)(I segments, O dst) static void toString(I, O)(I segments, O dst)
{ {
import std.format : formattedWrite;
char lastsep = '\0'; char lastsep = '\0';
bool first = true; bool first = true;
foreach (e; segments) { foreach (e; segments) {
if (!first || lastsep) dst.put('/'); if (!first || lastsep) dst.put('/');
else first = false; else first = false;
foreach (char c; e.name) { encodeSegment(dst, 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;
}
}
lastsep = e.separator; lastsep = e.separator;
} }
if (lastsep) dst.put('/'); if (lastsep) dst.put('/');
@ -1203,10 +1233,13 @@ struct InetPathFormat {
static string getBackNode(string path) static string getBackNode(string path)
@nogc { @nogc {
import std.string : lastIndexOf;
if (!path.length) return path; if (!path.length) return path;
foreach_reverse (i; 0 .. path.length-1) ptrdiff_t idx;
if (path[i] == '/') try idx = path[0 .. $-1].lastIndexOf('/');
return path[i+1 .. $]; catch (Exception e) assert(false, e.msg);
if (idx >= 0) return path[idx+1 .. $];
return path; return path;
} }
@ -1273,11 +1306,31 @@ struct InetPathFormat {
assert(validatePath("föö") !is null); assert(validatePath("föö") !is null);
} }
deprecated("Use decodeSingleSegment instead.")
static auto decodeSegment(S)(string segment) static auto decodeSegment(S)(string segment)
{ {
import std.array : appender;
import std.format : formattedRead;
import std.range : only; 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; import std.string : indexOf;
static int hexDigit(char ch) @safe nothrow @nogc { static int hexDigit(char ch) @safe nothrow @nogc {
@ -1305,19 +1358,67 @@ struct InetPathFormat {
return ret.data; return ret.data;
} }
if (!segment.length) return only(S.fromTrustedString(null)); assert(segment.length == 0 || segment[$-1] != '/');
if (segment[$-1] == '/') return urlDecode(segment);
return only(S.fromTrustedString(urlDecode(segment[0 .. $-1]), '/'));
return only(S.fromTrustedString(urlDecode(segment)));
} }
unittest { unittest {
import std.algorithm.comparison : equal; assert(decodeSingleSegment("foo") == "foo");
struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }} assert(decodeSingleSegment("fo%20o\\") == "fo o\\");
assert(decodeSegment!Segment("foo").equal([Segment("foo")])); assert(decodeSingleSegment("foo%20") == "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 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;
}
}
} }
} }