Permalink
Fetching contributors…
Cannot retrieve contributors at this time
6963 lines (6123 sloc) 206 KB
// Written in the D programming language.
/**
String handling functions.
$(SCRIPT inhibitQuickIndex = 1;)
$(DIVC quickindex,
$(BOOKTABLE ,
$(TR $(TH Category) $(TH Functions) )
$(TR $(TDNW Searching)
$(TD
$(MYREF column)
$(MYREF indexOf)
$(MYREF indexOfAny)
$(MYREF indexOfNeither)
$(MYREF lastIndexOf)
$(MYREF lastIndexOfAny)
$(MYREF lastIndexOfNeither)
)
)
$(TR $(TDNW Comparison)
$(TD
$(MYREF isNumeric)
)
)
$(TR $(TDNW Mutation)
$(TD
$(MYREF capitalize)
)
)
$(TR $(TDNW Pruning and Filling)
$(TD
$(MYREF center)
$(MYREF chomp)
$(MYREF chompPrefix)
$(MYREF chop)
$(MYREF detabber)
$(MYREF detab)
$(MYREF entab)
$(MYREF entabber)
$(MYREF leftJustify)
$(MYREF outdent)
$(MYREF rightJustify)
$(MYREF strip)
$(MYREF stripLeft)
$(MYREF stripRight)
$(MYREF wrap)
)
)
$(TR $(TDNW Substitution)
$(TD
$(MYREF abbrev)
$(MYREF soundex)
$(MYREF soundexer)
$(MYREF succ)
$(MYREF tr)
$(MYREF translate)
)
)
$(TR $(TDNW Miscellaneous)
$(TD
$(MYREF assumeUTF)
$(MYREF fromStringz)
$(MYREF lineSplitter)
$(MYREF representation)
$(MYREF splitLines)
$(MYREF toStringz)
)
)))
Objects of types $(D _string), $(D wstring), and $(D dstring) are value types
and cannot be mutated element-by-element. For using mutation during building
strings, use $(D char[]), $(D wchar[]), or $(D dchar[]). The $(D xxxstring)
types are preferable because they don't exhibit undesired aliasing, thus
making code more robust.
The following functions are publicly imported:
$(BOOKTABLE ,
$(TR $(TH Module) $(TH Functions) )
$(LEADINGROW Publicly imported functions)
$(TR $(TD std.algorithm)
$(TD
$(REF_SHORT cmp, std,algorithm,comparison)
$(REF_SHORT count, std,algorithm,searching)
$(REF_SHORT endsWith, std,algorithm,searching)
$(REF_SHORT startsWith, std,algorithm,searching)
))
$(TR $(TD std.array)
$(TD
$(REF_SHORT join, std,array)
$(REF_SHORT replace, std,array)
$(REF_SHORT replaceInPlace, std,array)
$(REF_SHORT split, std,array)
$(REF_SHORT empty, std,array)
))
$(TR $(TD std.format)
$(TD
$(REF_SHORT format, std,format)
$(REF_SHORT sformat, std,format)
))
$(TR $(TD std.uni)
$(TD
$(REF_SHORT icmp, std,uni)
$(REF_SHORT toLower, std,uni)
$(REF_SHORT toLowerInPlace, std,uni)
$(REF_SHORT toUpper, std,uni)
$(REF_SHORT toUpperInPlace, std,uni)
))
)
There is a rich set of functions for _string handling defined in other modules.
Functions related to Unicode and ASCII are found in $(MREF std, uni)
and $(MREF std, ascii), respectively. Other functions that have a
wider generality than just strings can be found in $(MREF std, algorithm)
and $(MREF std, range).
See_Also:
$(LIST
$(MREF std, algorithm) and
$(MREF std, range)
for generic range algorithms
,
$(MREF std, ascii)
for functions that work with ASCII strings
,
$(MREF std, uni)
for functions that work with unicode strings
)
Copyright: Copyright Digital Mars 2007-.
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
Authors: $(HTTP digitalmars.com, Walter Bright),
$(HTTP erdani.org, Andrei Alexandrescu),
Jonathan M Davis,
and David L. 'SpottedTiger' Davis
Source: $(PHOBOSSRC std/_string.d)
*/
module std.string;
version (unittest)
{
private:
struct TestAliasedString
{
string get() @safe @nogc pure nothrow { return _s; }
alias get this;
@disable this(this);
string _s;
}
bool testAliasedString(alias func, Args...)(string s, Args args)
{
import std.algorithm.comparison : equal;
auto a = func(TestAliasedString(s), args);
auto b = func(s, args);
static if (is(typeof(equal(a, b))))
{
// For ranges, compare contents instead of object identity.
return equal(a, b);
}
else
{
return a == b;
}
}
}
public import std.format : format, sformat;
import std.typecons : Flag, Yes, No;
public import std.uni : icmp, toLower, toLowerInPlace, toUpper, toUpperInPlace;
import std.meta; // AliasSeq, staticIndexOf
import std.range.primitives; // back, ElementEncodingType, ElementType, front,
// hasLength, hasSlicing, isBidirectionalRange, isForwardRange, isInfinite,
// isInputRange, isOutputRange, isRandomAccessRange, popBack, popFront, put,
// save;
import std.traits; // isConvertibleToString, isNarrowString, isSomeChar,
// isSomeString, StringTypeOf, Unqual
//public imports for backward compatibility
public import std.algorithm.comparison : cmp;
public import std.algorithm.searching : startsWith, endsWith, count;
public import std.array : join, replace, replaceInPlace, split, empty;
/* ************* Exceptions *************** */
/++
Exception thrown on errors in std.string functions.
+/
class StringException : Exception
{
import std.exception : basicExceptionCtors;
///
mixin basicExceptionCtors;
}
/++
Params:
cString = A null-terminated c-style string.
Returns: A D-style array of $(D char) referencing the same string. The
returned array will retain the same type qualifiers as the input.
$(RED Important Note:) The returned array is a slice of the original buffer.
The original data is not changed and not copied.
+/
inout(char)[] fromStringz(inout(char)* cString) @nogc @system pure nothrow {
import core.stdc.string : strlen;
return cString ? cString[0 .. strlen(cString)] : null;
}
///
@system pure unittest
{
assert(fromStringz(null) == null);
assert(fromStringz("foo") == "foo");
}
/++
Params:
s = A D-style string.
Returns: A C-style null-terminated string equivalent to $(D s). $(D s)
must not contain embedded $(D '\0')'s as any C function will treat the
first $(D '\0') that it sees as the end of the string. If $(D s.empty) is
$(D true), then a string containing only $(D '\0') is returned.
$(RED Important Note:) When passing a $(D char*) to a C function, and the C
function keeps it around for any reason, make sure that you keep a
reference to it in your D code. Otherwise, it may become invalid during a
garbage collection cycle and cause a nasty bug when the C code tries to use
it.
+/
immutable(char)* toStringz(const(char)[] s) @trusted pure nothrow
out (result)
{
import core.stdc.string : strlen, memcmp;
if (result)
{
auto slen = s.length;
while (slen > 0 && s[slen-1] == 0) --slen;
assert(strlen(result) == slen);
assert(result[0 .. slen] == s[0 .. slen]);
}
}
do
{
import std.exception : assumeUnique;
/+ Unfortunately, this isn't reliable.
We could make this work if string literals are put
in read-only memory and we test if s[] is pointing into
that.
/* Peek past end of s[], if it's 0, no conversion necessary.
* Note that the compiler will put a 0 past the end of static
* strings, and the storage allocator will put a 0 past the end
* of newly allocated char[]'s.
*/
char* p = &s[0] + s.length;
if (*p == 0)
return s;
+/
// Need to make a copy
auto copy = new char[s.length + 1];
copy[0 .. s.length] = s[];
copy[s.length] = 0;
return &assumeUnique(copy)[0];
}
/++ Ditto +/
immutable(char)* toStringz(in string s) @trusted pure nothrow
{
if (s.empty) return "".ptr;
/* Peek past end of s[], if it's 0, no conversion necessary.
* Note that the compiler will put a 0 past the end of static
* strings, and the storage allocator will put a 0 past the end
* of newly allocated char[]'s.
*/
immutable p = s.ptr + s.length;
// Is p dereferenceable? A simple test: if the p points to an
// address multiple of 4, then conservatively assume the pointer
// might be pointing to a new block of memory, which might be
// unreadable. Otherwise, it's definitely pointing to valid
// memory.
if ((cast(size_t) p & 3) && *p == 0)
return &s[0];
return toStringz(cast(const char[]) s);
}
///
pure nothrow @system unittest
{
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
}
/**
Flag indicating whether a search is case-sensitive.
*/
alias CaseSensitive = Flag!"caseSensitive";
/++
Searches for character in range.
Params:
s = string or InputRange of characters to search in correct UTF format
c = character to search for
startIdx = starting index to a well-formed code point
cs = $(D Yes.caseSensitive) or $(D No.caseSensitive)
Returns:
the index of the first occurrence of $(D c) in $(D s) with
respect to the start index $(D startIdx). If $(D c)
is not found, then $(D -1) is returned.
If $(D c) is found the value of the returned index is at least
$(D startIdx).
If the parameters are not valid UTF, the result will still
be in the range [-1 .. s.length], but will not be reliable otherwise.
Throws:
If the sequence starting at $(D startIdx) does not represent a well
formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
See_Also: $(REF countUntil, std,algorithm,searching)
+/
ptrdiff_t indexOf(Range)(Range s, in dchar c,
in CaseSensitive cs = Yes.caseSensitive)
if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
!isConvertibleToString!Range)
{
static import std.ascii;
static import std.uni;
import std.utf : byDchar, byCodeUnit, UTFException, codeLength;
alias Char = Unqual!(ElementEncodingType!Range);
if (cs == Yes.caseSensitive)
{
static if (Char.sizeof == 1 && isSomeString!Range)
{
if (std.ascii.isASCII(c) && !__ctfe)
{ // Plain old ASCII
static ptrdiff_t trustedmemchr(Range s, char c) @trusted
{
import core.stdc.string : memchr;
const p = cast(const(Char)*)memchr(s.ptr, c, s.length);
return p ? p - s.ptr : -1;
}
return trustedmemchr(s, cast(char) c);
}
}
static if (Char.sizeof == 1)
{
if (c <= 0x7F)
{
ptrdiff_t i;
foreach (const c2; s)
{
if (c == c2)
return i;
++i;
}
}
else
{
ptrdiff_t i;
foreach (const c2; s.byDchar())
{
if (c == c2)
return i;
i += codeLength!Char(c2);
}
}
}
else static if (Char.sizeof == 2)
{
if (c <= 0xFFFF)
{
ptrdiff_t i;
foreach (const c2; s)
{
if (c == c2)
return i;
++i;
}
}
else if (c <= 0x10FFFF)
{
// Encode UTF-16 surrogate pair
const wchar c1 = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
const wchar c2 = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
ptrdiff_t i;
for (auto r = s.byCodeUnit(); !r.empty; r.popFront())
{
if (c1 == r.front)
{
r.popFront();
if (r.empty) // invalid UTF - missing second of pair
break;
if (c2 == r.front)
return i;
++i;
}
++i;
}
}
}
else static if (Char.sizeof == 4)
{
ptrdiff_t i;
foreach (const c2; s)
{
if (c == c2)
return i;
++i;
}
}
else
static assert(0);
return -1;
}
else
{
if (std.ascii.isASCII(c))
{ // Plain old ASCII
immutable c1 = cast(char) std.ascii.toLower(c);
ptrdiff_t i;
foreach (const c2; s.byCodeUnit())
{
if (c1 == std.ascii.toLower(c2))
return i;
++i;
}
}
else
{ // c is a universal character
immutable c1 = std.uni.toLower(c);
ptrdiff_t i;
foreach (const c2; s.byDchar())
{
if (c1 == std.uni.toLower(c2))
return i;
i += codeLength!Char(c2);
}
}
}
return -1;
}
/// Ditto
ptrdiff_t indexOf(Range)(Range s, in dchar c, in size_t startIdx,
in CaseSensitive cs = Yes.caseSensitive)
if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
!isConvertibleToString!Range)
{
static if (isSomeString!(typeof(s)) ||
(hasSlicing!(typeof(s)) && hasLength!(typeof(s))))
{
if (startIdx < s.length)
{
ptrdiff_t foundIdx = indexOf(s[startIdx .. $], c, cs);
if (foundIdx != -1)
{
return foundIdx + cast(ptrdiff_t) startIdx;
}
}
}
else
{
foreach (i; 0 .. startIdx)
{
if (s.empty)
return -1;
s.popFront();
}
ptrdiff_t foundIdx = indexOf(s, c, cs);
if (foundIdx != -1)
{
return foundIdx + cast(ptrdiff_t) startIdx;
}
}
return -1;
}
///
@safe pure unittest
{
import std.typecons : No;
string s = "Hello World";
assert(indexOf(s, 'W') == 6);
assert(indexOf(s, 'Z') == -1);
assert(indexOf(s, 'w', No.caseSensitive) == 6);
}
///
@safe pure unittest
{
import std.typecons : No;
string s = "Hello World";
assert(indexOf(s, 'W', 4) == 6);
assert(indexOf(s, 'Z', 100) == -1);
assert(indexOf(s, 'w', 3, No.caseSensitive) == 6);
}
ptrdiff_t indexOf(Range)(auto ref Range s, in dchar c,
in CaseSensitive cs = Yes.caseSensitive)
if (isConvertibleToString!Range)
{
return indexOf!(StringTypeOf!Range)(s, c, cs);
}
ptrdiff_t indexOf(Range)(auto ref Range s, in dchar c, in size_t startIdx,
in CaseSensitive cs = Yes.caseSensitive)
if (isConvertibleToString!Range)
{
return indexOf!(StringTypeOf!Range)(s, c, startIdx, cs);
}
@safe pure unittest
{
assert(testAliasedString!indexOf("std/string.d", '/'));
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
import std.traits : EnumMembers;
import std.utf : byChar, byWchar, byDchar;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{{
assert(indexOf(cast(S) null, cast(dchar)'a') == -1);
assert(indexOf(to!S("def"), cast(dchar)'a') == -1);
assert(indexOf(to!S("abba"), cast(dchar)'a') == 0);
assert(indexOf(to!S("def"), cast(dchar)'f') == 2);
assert(indexOf(to!S("def"), cast(dchar)'a', No.caseSensitive) == -1);
assert(indexOf(to!S("def"), cast(dchar)'a', No.caseSensitive) == -1);
assert(indexOf(to!S("Abba"), cast(dchar)'a', No.caseSensitive) == 0);
assert(indexOf(to!S("def"), cast(dchar)'F', No.caseSensitive) == 2);
assert(indexOf(to!S("ödef"), 'ö', No.caseSensitive) == 0);
S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
assert(indexOf("def", cast(char)'f', No.caseSensitive) == 2);
assert(indexOf(sPlts, cast(char)'P', No.caseSensitive) == 23);
assert(indexOf(sPlts, cast(char)'R', No.caseSensitive) == 2);
}}
foreach (cs; EnumMembers!CaseSensitive)
{
assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', cs) == 9);
assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', cs) == 7);
assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', cs) == 6);
assert(indexOf("hello\U00010143\u0100\U00010143".byChar, '\u0100', cs) == 9);
assert(indexOf("hello\U00010143\u0100\U00010143".byWchar, '\u0100', cs) == 7);
assert(indexOf("hello\U00010143\u0100\U00010143".byDchar, '\u0100', cs) == 6);
assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, 'l', cs) == 2);
assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, '\u0100', cs) == 7);
assert(indexOf("hello\U0000EFFF\u0100\U00010143".byChar, '\u0100', cs) == 8);
assert(indexOf("hello\U00010100".byWchar, '\U00010100', cs) == 5);
assert(indexOf("hello\U00010100".byWchar, '\U00010101', cs) == -1);
}
char[10] fixedSizeArray = "0123456789";
assert(indexOf(fixedSizeArray, '2') == 2);
});
}
@safe pure unittest
{
assert(testAliasedString!indexOf("std/string.d", '/', 3));
}
@safe pure unittest
{
import std.conv : to;
import std.traits : EnumMembers;
import std.utf : byCodeUnit, byChar, byWchar;
assert("hello".byCodeUnit.indexOf(cast(dchar)'l', 1) == 2);
assert("hello".byWchar.indexOf(cast(dchar)'l', 1) == 2);
assert("hello".byWchar.indexOf(cast(dchar)'l', 6) == -1);
static foreach (S; AliasSeq!(string, wstring, dstring))
{{
assert(indexOf(cast(S) null, cast(dchar)'a', 1) == -1);
assert(indexOf(to!S("def"), cast(dchar)'a', 1) == -1);
assert(indexOf(to!S("abba"), cast(dchar)'a', 1) == 3);
assert(indexOf(to!S("def"), cast(dchar)'f', 1) == 2);
assert((to!S("def")).indexOf(cast(dchar)'a', 1,
No.caseSensitive) == -1);
assert(indexOf(to!S("def"), cast(dchar)'a', 1,
No.caseSensitive) == -1);
assert(indexOf(to!S("def"), cast(dchar)'a', 12,
No.caseSensitive) == -1);
assert(indexOf(to!S("AbbA"), cast(dchar)'a', 2,
No.caseSensitive) == 3);
assert(indexOf(to!S("def"), cast(dchar)'F', 2, No.caseSensitive) == 2);
S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
assert(indexOf("def", cast(char)'f', cast(uint) 2,
No.caseSensitive) == 2);
assert(indexOf(sPlts, cast(char)'P', 12, No.caseSensitive) == 23);
assert(indexOf(sPlts, cast(char)'R', cast(ulong) 1,
No.caseSensitive) == 2);
}}
foreach (cs; EnumMembers!CaseSensitive)
{
assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', 2, cs)
== 9);
assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', 3, cs)
== 7);
assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', 6, cs)
== 6);
}
}
/++
Searches for substring in $(D s).
Params:
s = string or ForwardRange of characters to search in correct UTF format
sub = substring to search for
startIdx = the index into s to start searching from
cs = $(D Yes.caseSensitive) or $(D No.caseSensitive)
Returns:
the index of the first occurrence of $(D sub) in $(D s) with
respect to the start index $(D startIdx). If $(D sub) is not found,
then $(D -1) is returned.
If the arguments are not valid UTF, the result will still
be in the range [-1 .. s.length], but will not be reliable otherwise.
If $(D sub) is found the value of the returned index is at least
$(D startIdx).
Throws:
If the sequence starting at $(D startIdx) does not represent a well
formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
Bugs:
Does not work with case insensitive strings where the mapping of
tolower and toupper is not 1:1.
+/
ptrdiff_t indexOf(Range, Char)(Range s, const(Char)[] sub,
in CaseSensitive cs = Yes.caseSensitive)
if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
isSomeChar!Char)
{
alias Char1 = Unqual!(ElementEncodingType!Range);
static if (isSomeString!Range)
{
import std.algorithm.searching : find;
const(Char1)[] balance;
if (cs == Yes.caseSensitive)
{
balance = find(s, sub);
}
else
{
balance = find!
((a, b) => toLower(a) == toLower(b))
(s, sub);
}
return () @trusted { return balance.empty ? -1 : balance.ptr - s.ptr; } ();
}
else
{
if (s.empty)
return -1;
if (sub.empty)
return 0; // degenerate case
import std.utf : byDchar, codeLength;
auto subr = sub.byDchar; // decode sub[] by dchar's
dchar sub0 = subr.front; // cache first character of sub[]
subr.popFront();
// Special case for single character search
if (subr.empty)
return indexOf(s, sub0, cs);
if (cs == No.caseSensitive)
sub0 = toLower(sub0);
/* Classic double nested loop search algorithm
*/
ptrdiff_t index = 0; // count code unit index into s
for (auto sbydchar = s.byDchar(); !sbydchar.empty; sbydchar.popFront())
{
dchar c2 = sbydchar.front;
if (cs == No.caseSensitive)
c2 = toLower(c2);
if (c2 == sub0)
{
auto s2 = sbydchar.save; // why s must be a forward range
foreach (c; subr.save)
{
s2.popFront();
if (s2.empty)
return -1;
if (cs == Yes.caseSensitive ? c != s2.front
: toLower(c) != toLower(s2.front)
)
goto Lnext;
}
return index;
}
Lnext:
index += codeLength!Char1(c2);
}
return -1;
}
}
/// Ditto
ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive)
@safe
if (isSomeChar!Char1 && isSomeChar!Char2)
{
if (startIdx < s.length)
{
ptrdiff_t foundIdx = indexOf(s[startIdx .. $], sub, cs);
if (foundIdx != -1)
{
return foundIdx + cast(ptrdiff_t) startIdx;
}
}
return -1;
}
///
@safe pure unittest
{
import std.typecons : No;
string s = "Hello World";
assert(indexOf(s, "Wo", 4) == 6);
assert(indexOf(s, "Zo", 100) == -1);
assert(indexOf(s, "wo", 3, No.caseSensitive) == 6);
}
///
@safe pure unittest
{
import std.typecons : No;
string s = "Hello World";
assert(indexOf(s, "Wo") == 6);
assert(indexOf(s, "Zo") == -1);
assert(indexOf(s, "wO", No.caseSensitive) == 6);
}
ptrdiff_t indexOf(Range, Char)(auto ref Range s, const(Char)[] sub,
in CaseSensitive cs = Yes.caseSensitive)
if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
isSomeChar!Char) &&
is(StringTypeOf!Range))
{
return indexOf!(StringTypeOf!Range)(s, sub, cs);
}
@safe pure unittest
{
assert(testAliasedString!indexOf("std/string.d", "string"));
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
import std.traits : EnumMembers;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{{
assert(indexOf(cast(S) null, to!T("a")) == -1);
assert(indexOf(to!S("def"), to!T("a")) == -1);
assert(indexOf(to!S("abba"), to!T("a")) == 0);
assert(indexOf(to!S("def"), to!T("f")) == 2);
assert(indexOf(to!S("dfefffg"), to!T("fff")) == 3);
assert(indexOf(to!S("dfeffgfff"), to!T("fff")) == 6);
assert(indexOf(to!S("dfeffgfff"), to!T("a"), No.caseSensitive) == -1);
assert(indexOf(to!S("def"), to!T("a"), No.caseSensitive) == -1);
assert(indexOf(to!S("abba"), to!T("a"), No.caseSensitive) == 0);
assert(indexOf(to!S("def"), to!T("f"), No.caseSensitive) == 2);
assert(indexOf(to!S("dfefffg"), to!T("fff"), No.caseSensitive) == 3);
assert(indexOf(to!S("dfeffgfff"), to!T("fff"), No.caseSensitive) == 6);
S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
S sMars = "Who\'s \'My Favorite Maritian?\'";
assert(indexOf(sMars, to!T("MY fAVe"), No.caseSensitive) == -1);
assert(indexOf(sMars, to!T("mY fAVOriTe"), No.caseSensitive) == 7);
assert(indexOf(sPlts, to!T("mArS:"), No.caseSensitive) == 0);
assert(indexOf(sPlts, to!T("rOcK"), No.caseSensitive) == 17);
assert(indexOf(sPlts, to!T("Un."), No.caseSensitive) == 41);
assert(indexOf(sPlts, to!T(sPlts), No.caseSensitive) == 0);
assert(indexOf("\u0100", to!T("\u0100"), No.caseSensitive) == 0);
// Thanks to Carlos Santander B. and zwang
assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y",
to!T("page-break-before"), No.caseSensitive) == -1);
}}
foreach (cs; EnumMembers!CaseSensitive)
{
assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"), cs) == 9);
assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"), cs) == 7);
assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"), cs) == 6);
}
}
});
}
@safe pure @nogc nothrow
unittest
{
import std.traits : EnumMembers;
import std.utf : byWchar;
foreach (cs; EnumMembers!CaseSensitive)
{
assert(indexOf("".byWchar, "", cs) == -1);
assert(indexOf("hello".byWchar, "", cs) == 0);
assert(indexOf("hello".byWchar, "l", cs) == 2);
assert(indexOf("heLLo".byWchar, "LL", cs) == 2);
assert(indexOf("hello".byWchar, "lox", cs) == -1);
assert(indexOf("hello".byWchar, "betty", cs) == -1);
assert(indexOf("hello\U00010143\u0100*\U00010143".byWchar, "\u0100*", cs) == 7);
}
}
@safe pure unittest
{
import std.conv : to;
import std.traits : EnumMembers;
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{{
assert(indexOf(cast(S) null, to!T("a"), 1337) == -1);
assert(indexOf(to!S("def"), to!T("a"), 0) == -1);
assert(indexOf(to!S("abba"), to!T("a"), 2) == 3);
assert(indexOf(to!S("def"), to!T("f"), 1) == 2);
assert(indexOf(to!S("dfefffg"), to!T("fff"), 1) == 3);
assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 5) == 6);
assert(indexOf(to!S("dfeffgfff"), to!T("a"), 1, No.caseSensitive) == -1);
assert(indexOf(to!S("def"), to!T("a"), 2, No.caseSensitive) == -1);
assert(indexOf(to!S("abba"), to!T("a"), 3, No.caseSensitive) == 3);
assert(indexOf(to!S("def"), to!T("f"), 1, No.caseSensitive) == 2);
assert(indexOf(to!S("dfefffg"), to!T("fff"), 2, No.caseSensitive) == 3);
assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 4, No.caseSensitive) == 6);
assert(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, No.caseSensitive) == 9,
to!string(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, No.caseSensitive))
~ " " ~ S.stringof ~ " " ~ T.stringof);
S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
S sMars = "Who\'s \'My Favorite Maritian?\'";
assert(indexOf(sMars, to!T("MY fAVe"), 10,
No.caseSensitive) == -1);
assert(indexOf(sMars, to!T("mY fAVOriTe"), 4, No.caseSensitive) == 7);
assert(indexOf(sPlts, to!T("mArS:"), 0, No.caseSensitive) == 0);
assert(indexOf(sPlts, to!T("rOcK"), 12, No.caseSensitive) == 17);
assert(indexOf(sPlts, to!T("Un."), 32, No.caseSensitive) == 41);
assert(indexOf(sPlts, to!T(sPlts), 0, No.caseSensitive) == 0);
assert(indexOf("\u0100", to!T("\u0100"), 0, No.caseSensitive) == 0);
// Thanks to Carlos Santander B. and zwang
assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y",
to!T("page-break-before"), 10, No.caseSensitive) == -1);
// In order for indexOf with and without index to be consistent
assert(indexOf(to!S(""), to!T("")) == indexOf(to!S(""), to!T(""), 0));
}}
foreach (cs; EnumMembers!CaseSensitive)
{
assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"),
3, cs) == 9);
assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"),
3, cs) == 7);
assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"),
3, cs) == 6);
}
}
}
/++
Params:
s = string to search
c = character to search for
startIdx = the index into s to start searching from
cs = $(D Yes.caseSensitive) or $(D No.caseSensitive)
Returns:
The index of the last occurrence of $(D c) in $(D s). If $(D c) is not
found, then $(D -1) is returned. The $(D startIdx) slices $(D s) in
the following way $(D s[0 .. startIdx]). $(D startIdx) represents a
codeunit index in $(D s).
Throws:
If the sequence ending at $(D startIdx) does not represent a well
formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
$(D cs) indicates whether the comparisons are case sensitive.
+/
ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c,
in CaseSensitive cs = Yes.caseSensitive) @safe pure
if (isSomeChar!Char)
{
static import std.ascii, std.uni;
import std.utf : canSearchInCodeUnits;
if (cs == Yes.caseSensitive)
{
if (canSearchInCodeUnits!Char(c))
{
foreach_reverse (i, it; s)
{
if (it == c)
{
return i;
}
}
}
else
{
foreach_reverse (i, dchar it; s)
{
if (it == c)
{
return i;
}
}
}
}
else
{
if (std.ascii.isASCII(c))
{
immutable c1 = std.ascii.toLower(c);
foreach_reverse (i, it; s)
{
immutable c2 = std.ascii.toLower(it);
if (c1 == c2)
{
return i;
}
}
}
else
{
immutable c1 = std.uni.toLower(c);
foreach_reverse (i, dchar it; s)
{
immutable c2 = std.uni.toLower(it);
if (c1 == c2)
{
return i;
}
}
}
}
return -1;
}
/// Ditto
ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, in size_t startIdx,
in CaseSensitive cs = Yes.caseSensitive) @safe pure
if (isSomeChar!Char)
{
if (startIdx <= s.length)
{
return lastIndexOf(s[0u .. startIdx], c, cs);
}
return -1;
}
///
@safe pure unittest
{
import std.typecons : No;
string s = "Hello World";
assert(lastIndexOf(s, 'l') == 9);
assert(lastIndexOf(s, 'Z') == -1);
assert(lastIndexOf(s, 'L', No.caseSensitive) == 9);
}
///
@safe pure unittest
{
import std.typecons : No;
string s = "Hello World";
assert(lastIndexOf(s, 'l', 4) == 3);
assert(lastIndexOf(s, 'Z', 1337) == -1);
assert(lastIndexOf(s, 'L', 7, No.caseSensitive) == 3);
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
import std.traits : EnumMembers;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{{
assert(lastIndexOf(cast(S) null, 'a') == -1);
assert(lastIndexOf(to!S("def"), 'a') == -1);
assert(lastIndexOf(to!S("abba"), 'a') == 3);
assert(lastIndexOf(to!S("def"), 'f') == 2);
assert(lastIndexOf(to!S("ödef"), 'ö') == 0);
assert(lastIndexOf(cast(S) null, 'a', No.caseSensitive) == -1);
assert(lastIndexOf(to!S("def"), 'a', No.caseSensitive) == -1);
assert(lastIndexOf(to!S("AbbA"), 'a', No.caseSensitive) == 3);
assert(lastIndexOf(to!S("def"), 'F', No.caseSensitive) == 2);
assert(lastIndexOf(to!S("ödef"), 'ö', No.caseSensitive) == 0);
assert(lastIndexOf(to!S("i\u0100def"), to!dchar("\u0100"),
No.caseSensitive) == 1);
S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
assert(lastIndexOf(to!S("def"), 'f', No.caseSensitive) == 2);
assert(lastIndexOf(sPlts, 'M', No.caseSensitive) == 34);
assert(lastIndexOf(sPlts, 'S', No.caseSensitive) == 40);
}}
foreach (cs; EnumMembers!CaseSensitive)
{
assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4);
assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2);
assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1);
}
});
}
@safe pure unittest
{
import std.conv : to;
import std.traits : EnumMembers;
static foreach (S; AliasSeq!(string, wstring, dstring))
{{
assert(lastIndexOf(cast(S) null, 'a') == -1);
assert(lastIndexOf(to!S("def"), 'a') == -1);
assert(lastIndexOf(to!S("abba"), 'a', 3) == 0);
assert(lastIndexOf(to!S("deff"), 'f', 3) == 2);
assert(lastIndexOf(cast(S) null, 'a', No.caseSensitive) == -1);
assert(lastIndexOf(to!S("def"), 'a', No.caseSensitive) == -1);
assert(lastIndexOf(to!S("AbbAa"), 'a', to!ushort(4), No.caseSensitive) == 3,
to!string(lastIndexOf(to!S("AbbAa"), 'a', 4, No.caseSensitive)));
assert(lastIndexOf(to!S("def"), 'F', 3, No.caseSensitive) == 2);
S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
assert(lastIndexOf(to!S("def"), 'f', 4, No.caseSensitive) == -1);
assert(lastIndexOf(sPlts, 'M', sPlts.length -2, No.caseSensitive) == 34);
assert(lastIndexOf(sPlts, 'S', sPlts.length -2, No.caseSensitive) == 40);
}}
foreach (cs; EnumMembers!CaseSensitive)
{
assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4);
assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2);
assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1);
}
}
/++
Params:
s = string to search
sub = substring to search for
startIdx = the index into s to start searching from
cs = $(D Yes.caseSensitive) or $(D No.caseSensitive)
Returns:
the index of the last occurrence of $(D sub) in $(D s). If $(D sub) is
not found, then $(D -1) is returned. The $(D startIdx) slices $(D s)
in the following way $(D s[0 .. startIdx]). $(D startIdx) represents a
codeunit index in $(D s).
Throws:
If the sequence ending at $(D startIdx) does not represent a well
formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
$(D cs) indicates whether the comparisons are case sensitive.
+/
ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
in CaseSensitive cs = Yes.caseSensitive) @safe pure
if (isSomeChar!Char1 && isSomeChar!Char2)
{
import std.algorithm.searching : endsWith;
import std.conv : to;
import std.range.primitives : walkLength;
static import std.uni;
import std.utf : strideBack;
if (sub.empty)
return -1;
if (walkLength(sub) == 1)
return lastIndexOf(s, sub.front, cs);
if (cs == Yes.caseSensitive)
{
static if (is(Unqual!Char1 == Unqual!Char2))
{
import core.stdc.string : memcmp;
immutable c = sub[0];
for (ptrdiff_t i = s.length - sub.length; i >= 0; --i)
{
if (s[i] == c)
{
if (__ctfe)
{
foreach (j; 1 .. sub.length)
{
if (s[i + j] != sub[j])
continue;
}
return i;
}
else
{
auto trustedMemcmp(in void* s1, in void* s2, size_t n) @trusted
{
return memcmp(s1, s2, n);
}
if (trustedMemcmp(&s[i + 1], &sub[1],
(sub.length - 1) * Char1.sizeof) == 0)
return i;
}
}
}
}
else
{
for (size_t i = s.length; !s.empty;)
{
if (s.endsWith(sub))
return cast(ptrdiff_t) i - to!(const(Char1)[])(sub).length;
i -= strideBack(s, i);
s = s[0 .. i];
}
}
}
else
{
for (size_t i = s.length; !s.empty;)
{
if (endsWith!((a, b) => std.uni.toLower(a) == std.uni.toLower(b))
(s, sub))
{
return cast(ptrdiff_t) i - to!(const(Char1)[])(sub).length;
}
i -= strideBack(s, i);
s = s[0 .. i];
}
}
return -1;
}
/// Ditto
ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) @safe pure
if (isSomeChar!Char1 && isSomeChar!Char2)
{
if (startIdx <= s.length)
{
return lastIndexOf(s[0u .. startIdx], sub, cs);
}
return -1;
}
///
@safe pure unittest
{
import std.typecons : No;
string s = "Hello World";
assert(lastIndexOf(s, "ll") == 2);
assert(lastIndexOf(s, "Zo") == -1);
assert(lastIndexOf(s, "lL", No.caseSensitive) == 2);
}
///
@safe pure unittest
{
import std.typecons : No;
string s = "Hello World";
assert(lastIndexOf(s, "ll", 4) == 2);
assert(lastIndexOf(s, "Zo", 128) == -1);
assert(lastIndexOf(s, "lL", 3, No.caseSensitive) == -1);
}
@safe pure unittest
{
import std.conv : to;
static foreach (S; AliasSeq!(string, wstring, dstring))
{{
auto r = to!S("").lastIndexOf("hello");
assert(r == -1, to!string(r));
r = to!S("hello").lastIndexOf("");
assert(r == -1, to!string(r));
r = to!S("").lastIndexOf("");
assert(r == -1, to!string(r));
}}
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
import std.traits : EnumMembers;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{{
enum typeStr = S.stringof ~ " " ~ T.stringof;
assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("c")) == 6, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd")) == 6, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef")) == 8, typeStr);
assert(lastIndexOf(to!S("abcdefCdef"), to!T("c")) == 2, typeStr);
assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd")) == 2, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("x")) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy")) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("")) == -1, typeStr);
assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö")) == 0, typeStr);
assert(lastIndexOf(cast(S) null, to!T("a"), No.caseSensitive) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), No.caseSensitive) == 6, typeStr);
assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), No.caseSensitive) == 6, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"), No.caseSensitive) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy"), No.caseSensitive) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), No.caseSensitive) == -1, typeStr);
assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö"), No.caseSensitive) == 0, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), No.caseSensitive) == 6, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), No.caseSensitive) == 6, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), No.caseSensitive) == 7, typeStr);
assert(lastIndexOf(to!S("ödfeffgfff"), to!T("ö"), Yes.caseSensitive) == 0);
S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
S sMars = "Who\'s \'My Favorite Maritian?\'";
assert(lastIndexOf(sMars, to!T("RiTE maR"), No.caseSensitive) == 14, typeStr);
assert(lastIndexOf(sPlts, to!T("FOuRTh"), No.caseSensitive) == 10, typeStr);
assert(lastIndexOf(sMars, to!T("whO\'s \'MY"), No.caseSensitive) == 0, typeStr);
assert(lastIndexOf(sMars, to!T(sMars), No.caseSensitive) == 0, typeStr);
}}
foreach (cs; EnumMembers!CaseSensitive)
{
enum csString = to!string(cs);
assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), cs) == 4, csString);
assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), cs) == 2, csString);
assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), cs) == 1, csString);
}
}
});
}
@safe pure unittest // issue13529
{
import std.conv : to;
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{{
enum typeStr = S.stringof ~ " " ~ T.stringof;
auto idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö ö"));
assert(idx != -1, to!string(idx) ~ " " ~ typeStr);
idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö öd"));
assert(idx == -1, to!string(idx) ~ " " ~ typeStr);
}}
}
}
@safe pure unittest
{
import std.conv : to;
import std.traits : EnumMembers;
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{{
enum typeStr = S.stringof ~ " " ~ T.stringof;
assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 5) == 2, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 3) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6) == 4, typeStr ~
format(" %u", lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6)));
assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5) == 2, typeStr);
assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd"), 3) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdefx"), to!T("x"), 1) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdefxy"), to!T("xy"), 6) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 8) == -1, typeStr);
assert(lastIndexOf(to!S("öafö"), to!T("ö"), 3) == 0, typeStr ~
to!string(lastIndexOf(to!S("öafö"), to!T("ö"), 3))); //BUG 10472
assert(lastIndexOf(cast(S) null, to!T("a"), 1, No.caseSensitive) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5, No.caseSensitive) == 2, typeStr);
assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 4, No.caseSensitive) == 2, typeStr ~
" " ~ to!string(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 3, No.caseSensitive)));
assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"),3 , No.caseSensitive) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdefXY"), to!T("xy"), 4, No.caseSensitive) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 7, No.caseSensitive) == -1, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 4, No.caseSensitive) == 2, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 4, No.caseSensitive) == 2, typeStr);
assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), 6, No.caseSensitive) == 3, typeStr);
assert(lastIndexOf(to!S(""), to!T(""), 0) == lastIndexOf(to!S(""), to!T("")), typeStr);
}}
foreach (cs; EnumMembers!CaseSensitive)
{
enum csString = to!string(cs);
assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), 6, cs) == 4, csString);
assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), 6, cs) == 2, csString);
assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), 3, cs) == 1, csString);
}
}
}
private ptrdiff_t indexOfAnyNeitherImpl(bool forward, bool any, Char, Char2)(
const(Char)[] haystack, const(Char2)[] needles,
in CaseSensitive cs = Yes.caseSensitive) @safe pure
if (isSomeChar!Char && isSomeChar!Char2)
{
import std.algorithm.searching : canFind, findAmong;
if (cs == Yes.caseSensitive)
{
static if (forward)
{
static if (any)
{
size_t n = haystack.findAmong(needles).length;
return n ? haystack.length - n : -1;
}
else
{
foreach (idx, dchar hay; haystack)
{
if (!canFind(needles, hay))
{
return idx;
}
}
}
}
else
{
static if (any)
{
import std.range : retro;
import std.utf : strideBack;
size_t n = haystack.retro.findAmong(needles).source.length;
if (n)
{
return n - haystack.strideBack(n);
}
}
else
{
foreach_reverse (idx, dchar hay; haystack)
{
if (!canFind(needles, hay))
{
return idx;
}
}
}
}
}
else
{
import std.range.primitives : walkLength;
if (needles.length <= 16 && needles.walkLength(17))
{
size_t si = 0;
dchar[16] scratch = void;
foreach ( dchar c; needles)
{
scratch[si++] = toLower(c);
}
static if (forward)
{
foreach (i, dchar c; haystack)
{
if (canFind(scratch[0 .. si], toLower(c)) == any)
{
return i;
}
}
}
else
{
foreach_reverse (i, dchar c; haystack)
{
if (canFind(scratch[0 .. si], toLower(c)) == any)
{
return i;
}
}
}
}
else
{
static bool f(dchar a, dchar b)
{
return toLower(a) == b;
}
static if (forward)
{
foreach (i, dchar c; haystack)
{
if (canFind!f(needles, toLower(c)) == any)
{
return i;
}
}
}
else
{
foreach_reverse (i, dchar c; haystack)
{
if (canFind!f(needles, toLower(c)) == any)
{
return i;
}
}
}
}
}
return -1;
}
/**
Returns the index of the first occurrence of any of the elements in $(D
needles) in $(D haystack). If no element of $(D needles) is found,
then $(D -1) is returned. The $(D startIdx) slices $(D haystack) in the
following way $(D haystack[startIdx .. $]). $(D startIdx) represents a
codeunit index in $(D haystack). If the sequence ending at $(D startIdx)
does not represent a well formed codepoint, then a $(REF UTFException, std,utf)
may be thrown.
Params:
haystack = String to search for needles in.
needles = Strings to search for in haystack.
startIdx = slices haystack like this $(D haystack[startIdx .. $]). If
the startIdx is greater equal the length of haystack the functions
returns $(D -1).
cs = Indicates whether the comparisons are case sensitive.
*/
ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles,
in CaseSensitive cs = Yes.caseSensitive) @safe pure
if (isSomeChar!Char && isSomeChar!Char2)
{
return indexOfAnyNeitherImpl!(true, true)(haystack, needles, cs);
}
/// Ditto
ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles,
in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) @safe pure
if (isSomeChar!Char && isSomeChar!Char2)
{
if (startIdx < haystack.length)
{
ptrdiff_t foundIdx = indexOfAny(haystack[startIdx .. $], needles, cs);
if (foundIdx != -1)
{
return foundIdx + cast(ptrdiff_t) startIdx;
}
}
return -1;
}
///
@safe pure unittest
{
import std.conv : to;
ptrdiff_t i = "helloWorld".indexOfAny("Wr");
assert(i == 5);
i = "öällo world".indexOfAny("lo ");
assert(i == 4, to!string(i));
}
///
@safe pure unittest
{
import std.conv : to;
ptrdiff_t i = "helloWorld".indexOfAny("Wr", 4);
assert(i == 5);
i = "Foo öällo world".indexOfAny("lh", 3);
assert(i == 8, to!string(i));
}
@safe pure unittest
{
import std.conv : to;
static foreach (S; AliasSeq!(string, wstring, dstring))
{{
auto r = to!S("").indexOfAny("hello");
assert(r == -1, to!string(r));
r = to!S("hello").indexOfAny("");
assert(r == -1, to!string(r));
r = to!S("").indexOfAny("");
assert(r == -1, to!string(r));
}}
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{
assert(indexOfAny(cast(S) null, to!T("a")) == -1);
assert(indexOfAny(to!S("def"), to!T("rsa")) == -1);
assert(indexOfAny(to!S("abba"), to!T("a")) == 0);
assert(indexOfAny(to!S("def"), to!T("f")) == 2);
assert(indexOfAny(to!S("dfefffg"), to!T("fgh")) == 1);
assert(indexOfAny(to!S("dfeffgfff"), to!T("feg")) == 1);
assert(indexOfAny(to!S("zfeffgfff"), to!T("ACDC"),
No.caseSensitive) == -1);
assert(indexOfAny(to!S("def"), to!T("MI6"),
No.caseSensitive) == -1);
assert(indexOfAny(to!S("abba"), to!T("DEA"),
No.caseSensitive) == 0);
assert(indexOfAny(to!S("def"), to!T("FBI"), No.caseSensitive) == 2);
assert(indexOfAny(to!S("dfefffg"), to!T("NSA"), No.caseSensitive)
== -1);
assert(indexOfAny(to!S("dfeffgfff"), to!T("BND"),
No.caseSensitive) == 0);
assert(indexOfAny(to!S("dfeffgfff"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"),
No.caseSensitive) == 0);
assert(indexOfAny("\u0100", to!T("\u0100"), No.caseSensitive) == 0);
}
}
}
);
}
@safe pure unittest
{
import std.conv : to;
import std.traits : EnumMembers;
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{
assert(indexOfAny(cast(S) null, to!T("a"), 1337) == -1);
assert(indexOfAny(to!S("def"), to!T("AaF"), 0) == -1);
assert(indexOfAny(to!S("abba"), to!T("NSa"), 2) == 3);
assert(indexOfAny(to!S("def"), to!T("fbi"), 1) == 2);
assert(indexOfAny(to!S("dfefffg"), to!T("foo"), 2) == 3);
assert(indexOfAny(to!S("dfeffgfff"), to!T("fsb"), 5) == 6);
assert(indexOfAny(to!S("dfeffgfff"), to!T("NDS"), 1,
No.caseSensitive) == -1);
assert(indexOfAny(to!S("def"), to!T("DRS"), 2,
No.caseSensitive) == -1);
assert(indexOfAny(to!S("abba"), to!T("SI"), 3,
No.caseSensitive) == -1);
assert(indexOfAny(to!S("deO"), to!T("ASIO"), 1,
No.caseSensitive) == 2);
assert(indexOfAny(to!S("dfefffg"), to!T("fbh"), 2,
No.caseSensitive) == 3);
assert(indexOfAny(to!S("dfeffgfff"), to!T("fEe"), 4,
No.caseSensitive) == 4);
assert(indexOfAny(to!S("dfeffgffföä"), to!T("föä"), 9,
No.caseSensitive) == 9);
assert(indexOfAny("\u0100", to!T("\u0100"), 0,
No.caseSensitive) == 0);
}
foreach (cs; EnumMembers!CaseSensitive)
{
assert(indexOfAny("hello\U00010143\u0100\U00010143",
to!S("e\u0100"), 3, cs) == 9);
assert(indexOfAny("hello\U00010143\u0100\U00010143"w,
to!S("h\u0100"), 3, cs) == 7);
assert(indexOfAny("hello\U00010143\u0100\U00010143"d,
to!S("l\u0100"), 5, cs) == 6);
}
}
}
/**
Returns the index of the last occurrence of any of the elements in $(D
needles) in $(D haystack). If no element of $(D needles) is found,
then $(D -1) is returned. The $(D stopIdx) slices $(D haystack) in the
following way $(D s[0 .. stopIdx]). $(D stopIdx) represents a codeunit
index in $(D haystack). If the sequence ending at $(D startIdx) does not
represent a well formed codepoint, then a $(REF UTFException, std,utf) may be
thrown.
Params:
haystack = String to search for needles in.
needles = Strings to search for in haystack.
stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]). If
the stopIdx is greater equal the length of haystack the functions
returns $(D -1).
cs = Indicates whether the comparisons are case sensitive.
*/
ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack,
const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive)
@safe pure
if (isSomeChar!Char && isSomeChar!Char2)
{
return indexOfAnyNeitherImpl!(false, true)(haystack, needles, cs);
}
/// Ditto
ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack,
const(Char2)[] needles, in size_t stopIdx,
in CaseSensitive cs = Yes.caseSensitive) @safe pure
if (isSomeChar!Char && isSomeChar!Char2)
{
if (stopIdx <= haystack.length)
{
return lastIndexOfAny(haystack[0u .. stopIdx], needles, cs);
}
return -1;
}
///
@safe pure unittest
{
ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo");
assert(i == 8);
i = "Foo öäöllo world".lastIndexOfAny("öF");
assert(i == 8);
}
///
@safe pure unittest
{
import std.conv : to;
ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo", 4);
assert(i == 3);
i = "Foo öäöllo world".lastIndexOfAny("öF", 3);
assert(i == 0);
}
@safe pure unittest
{
import std.conv : to;
static foreach (S; AliasSeq!(string, wstring, dstring))
{{
auto r = to!S("").lastIndexOfAny("hello");
assert(r == -1, to!string(r));
r = to!S("hello").lastIndexOfAny("");
assert(r == -1, to!string(r));
r = to!S("").lastIndexOfAny("");
assert(r == -1, to!string(r));
}}
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{{
assert(lastIndexOfAny(cast(S) null, to!T("a")) == -1);
assert(lastIndexOfAny(to!S("def"), to!T("rsa")) == -1);
assert(lastIndexOfAny(to!S("abba"), to!T("a")) == 3);
assert(lastIndexOfAny(to!S("def"), to!T("f")) == 2);
assert(lastIndexOfAny(to!S("dfefffg"), to!T("fgh")) == 6);
ptrdiff_t oeIdx = 9;
if (is(S == wstring) || is(S == dstring))
{
oeIdx = 8;
}
auto foundOeIdx = lastIndexOfAny(to!S("dfeffgföf"), to!T("feg"));
assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
assert(lastIndexOfAny(to!S("zfeffgfff"), to!T("ACDC"),
No.caseSensitive) == -1);
assert(lastIndexOfAny(to!S("def"), to!T("MI6"),
No.caseSensitive) == -1);
assert(lastIndexOfAny(to!S("abba"), to!T("DEA"),
No.caseSensitive) == 3);
assert(lastIndexOfAny(to!S("def"), to!T("FBI"),
No.caseSensitive) == 2);
assert(lastIndexOfAny(to!S("dfefffg"), to!T("NSA"),
No.caseSensitive) == -1);
oeIdx = 2;
if (is(S == wstring) || is(S == dstring))
{
oeIdx = 1;
}
assert(lastIndexOfAny(to!S("ödfeffgfff"), to!T("BND"),
No.caseSensitive) == oeIdx);
assert(lastIndexOfAny("\u0100", to!T("\u0100"),
No.caseSensitive) == 0);
}}
}
}
);
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{{
enum typeStr = S.stringof ~ " " ~ T.stringof;
assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337) == -1,
typeStr);
assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("c"), 7) == 6,
typeStr);
assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("cd"), 5) == 3,
typeStr);
assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("ef"), 6) == 5,
typeStr);
assert(lastIndexOfAny(to!S("abcdefCdef"), to!T("c"), 8) == 2,
typeStr);
assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("x"), 7) == -1,
typeStr);
assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("xy"), 4) == -1,
typeStr);
assert(lastIndexOfAny(to!S("öabcdefcdef"), to!T("ö"), 2) == 0,
typeStr);
assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337,
No.caseSensitive) == -1, typeStr);
assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("C"), 7,
No.caseSensitive) == 6, typeStr);
assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("cd"), 5,
No.caseSensitive) == 3, typeStr);
assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("EF"), 6,
No.caseSensitive) == 5, typeStr);
assert(lastIndexOfAny(to!S("ABCDEFcDEF"), to!T("C"), 8,
No.caseSensitive) == 6, typeStr);
assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("x"), 7,
No.caseSensitive) == -1, typeStr);
assert(lastIndexOfAny(to!S("abCdefcdef"), to!T("XY"), 4,
No.caseSensitive) == -1, typeStr);
assert(lastIndexOfAny(to!S("ÖABCDEFCDEF"), to!T("ö"), 2,
No.caseSensitive) == 0, typeStr);
}}
}
}
);
}
/**
Returns the index of the first occurrence of any character not an elements
in $(D needles) in $(D haystack). If all element of $(D haystack) are
element of $(D needles) $(D -1) is returned.
Params:
haystack = String to search for needles in.
needles = Strings to search for in haystack.
startIdx = slices haystack like this $(D haystack[startIdx .. $]). If
the startIdx is greater equal the length of haystack the functions
returns $(D -1).
cs = Indicates whether the comparisons are case sensitive.
*/
ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack,
const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive)
@safe pure
if (isSomeChar!Char && isSomeChar!Char2)
{
return indexOfAnyNeitherImpl!(true, false)(haystack, needles, cs);
}
/// Ditto
ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack,
const(Char2)[] needles, in size_t startIdx,
in CaseSensitive cs = Yes.caseSensitive)
@safe pure
if (isSomeChar!Char && isSomeChar!Char2)
{
if (startIdx < haystack.length)
{
ptrdiff_t foundIdx = indexOfAnyNeitherImpl!(true, false)(
haystack[startIdx .. $], needles, cs);
if (foundIdx != -1)
{
return foundIdx + cast(ptrdiff_t) startIdx;
}
}
return -1;
}
///
@safe pure unittest
{
assert(indexOfNeither("abba", "a", 2) == 2);
assert(indexOfNeither("def", "de", 1) == 2);
assert(indexOfNeither("dfefffg", "dfe", 4) == 6);
}
///
@safe pure unittest
{
assert(indexOfNeither("def", "a") == 0);
assert(indexOfNeither("def", "de") == 2);
assert(indexOfNeither("dfefffg", "dfe") == 6);
}
@safe pure unittest
{
import std.conv : to;
static foreach (S; AliasSeq!(string, wstring, dstring))
{{
auto r = to!S("").indexOfNeither("hello");
assert(r == -1, to!string(r));
r = to!S("hello").indexOfNeither("");
assert(r == 0, to!string(r));
r = to!S("").indexOfNeither("");
assert(r == -1, to!string(r));
}}
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{
assert(indexOfNeither(cast(S) null, to!T("a")) == -1);
assert(indexOfNeither("abba", "a") == 1);
assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"),
No.caseSensitive) == 0);
assert(indexOfNeither(to!S("def"), to!T("D"),
No.caseSensitive) == 1);
assert(indexOfNeither(to!S("ABca"), to!T("a"),
No.caseSensitive) == 1);
assert(indexOfNeither(to!S("def"), to!T("f"),
No.caseSensitive) == 0);
assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"),
No.caseSensitive) == 6);
if (is(S == string))
{
assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
No.caseSensitive) == 8,
to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
No.caseSensitive)));
}
else
{
assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
No.caseSensitive) == 7,
to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
No.caseSensitive)));
}
}
}
}
);
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{
assert(indexOfNeither(cast(S) null, to!T("a"), 1) == -1);
assert(indexOfNeither(to!S("def"), to!T("a"), 1) == 1,
to!string(indexOfNeither(to!S("def"), to!T("a"), 1)));
assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"), 4,
No.caseSensitive) == 4);
assert(indexOfNeither(to!S("def"), to!T("D"), 2,
No.caseSensitive) == 2);
assert(indexOfNeither(to!S("ABca"), to!T("a"), 3,
No.caseSensitive) == -1);
assert(indexOfNeither(to!S("def"), to!T("tzf"), 2,
No.caseSensitive) == -1);
assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"), 5,
No.caseSensitive) == 6);
if (is(S == string))
{
assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2,
No.caseSensitive) == 3, to!string(indexOfNeither(
to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive)));
}
else
{
assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2,
No.caseSensitive) == 2, to!string(indexOfNeither(
to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive)));
}
}
}
}
);
}
/**
Returns the last index of the first occurence of any character that is not
an elements in $(D needles) in $(D haystack). If all element of
$(D haystack) are element of $(D needles) $(D -1) is returned.
Params:
haystack = String to search for needles in.
needles = Strings to search for in haystack.
stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]) If
the stopIdx is greater equal the length of haystack the functions
returns $(D -1).
cs = Indicates whether the comparisons are case sensitive.
*/
ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack,
const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive)
@safe pure
if (isSomeChar!Char && isSomeChar!Char2)
{
return indexOfAnyNeitherImpl!(false, false)(haystack, needles, cs);
}
/// Ditto
ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack,
const(Char2)[] needles, in size_t stopIdx,
in CaseSensitive cs = Yes.caseSensitive)
@safe pure
if (isSomeChar!Char && isSomeChar!Char2)
{
if (stopIdx < haystack.length)
{
return indexOfAnyNeitherImpl!(false, false)(haystack[0 .. stopIdx],
needles, cs);
}
return -1;
}
///
@safe pure unittest
{
assert(lastIndexOfNeither("abba", "a") == 2);
assert(lastIndexOfNeither("def", "f") == 1);
}
///
@safe pure unittest
{
assert(lastIndexOfNeither("def", "rsa", 3) == -1);
assert(lastIndexOfNeither("abba", "a", 2) == 1);
}
@safe pure unittest
{
import std.conv : to;
static foreach (S; AliasSeq!(string, wstring, dstring))
{{
auto r = to!S("").lastIndexOfNeither("hello");
assert(r == -1, to!string(r));
r = to!S("hello").lastIndexOfNeither("");
assert(r == 4, to!string(r));
r = to!S("").lastIndexOfNeither("");
assert(r == -1, to!string(r));
}}
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{{
assert(lastIndexOfNeither(cast(S) null, to!T("a")) == -1);
assert(lastIndexOfNeither(to!S("def"), to!T("rsa")) == 2);
assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2);
ptrdiff_t oeIdx = 8;
if (is(S == string))
{
oeIdx = 9;
}
auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg"));
assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"),
No.caseSensitive) == 5);
assert(lastIndexOfNeither(to!S("def"), to!T("MI6"),
No.caseSensitive) == 2, to!string(lastIndexOfNeither(to!S("def"),
to!T("MI6"), No.caseSensitive)));
assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"),
No.caseSensitive) == 6, to!string(lastIndexOfNeither(
to!S("abbadeafsb"), to!T("fSb"), No.caseSensitive)));
assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"),
No.caseSensitive) == 1);
assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"),
No.caseSensitive) == 6);
assert(lastIndexOfNeither(to!S("dfeffgfffö"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"),
No.caseSensitive) == 8, to!string(lastIndexOfNeither(to!S("dfeffgfffö"),
to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), No.caseSensitive)));
}}
}
}
);
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring))
{
static foreach (T; AliasSeq!(string, wstring, dstring))
{{
assert(lastIndexOfNeither(cast(S) null, to!T("a"), 1337) == -1);
assert(lastIndexOfNeither(to!S("def"), to!T("f")) == 1);
assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2);
ptrdiff_t oeIdx = 4;
if (is(S == string))
{
oeIdx = 5;
}
auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg"),
7);
assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"), 6,
No.caseSensitive) == 5);
assert(lastIndexOfNeither(to!S("def"), to!T("MI6"), 2,
No.caseSensitive) == 1, to!string(lastIndexOfNeither(to!S("def"),
to!T("MI6"), 2, No.caseSensitive)));
assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"), 6,
No.caseSensitive) == 5, to!string(lastIndexOfNeither(
to!S("abbadeafsb"), to!T("fSb"), 6, No.caseSensitive)));
assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"), 3,
No.caseSensitive) == 1);
assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"), 2,
No.caseSensitive) == 1, to!string(lastIndexOfNeither(
to!S("dfefffg"), to!T("NSA"), 2, No.caseSensitive)));
}}
}
}
);
}
/**
* Returns the _representation of a string, which has the same type
* as the string except the character type is replaced by $(D ubyte),
* $(D ushort), or $(D uint) depending on the character width.
*
* Params:
* s = The string to return the _representation of.
*
* Returns:
* The _representation of the passed string.
*/
auto representation(Char)(Char[] s) @safe pure nothrow @nogc
if (isSomeChar!Char)
{
import std.traits : ModifyTypePreservingTQ;
alias ToRepType(T) = AliasSeq!(ubyte, ushort, uint)[T.sizeof / 2];
return cast(ModifyTypePreservingTQ!(ToRepType, Char)[])s;
}
///
@safe pure unittest
{
string s = "hello";
static assert(is(typeof(representation(s)) == immutable(ubyte)[]));
assert(representation(s) is cast(immutable(ubyte)[]) s);
assert(representation(s) == [0x68, 0x65, 0x6c, 0x6c, 0x6f]);
}
@system pure unittest
{
import std.exception : assertCTFEable;
import std.traits : Fields;
import std.typecons : Tuple;
assertCTFEable!(
{
void test(Char, T)(Char[] str)
{
static assert(is(typeof(representation(str)) == T[]));
assert(representation(str) is cast(T[]) str);
}
static foreach (Type; AliasSeq!(Tuple!(char , ubyte ),
Tuple!(wchar, ushort),
Tuple!(dchar, uint )))
{{
alias Char = Fields!Type[0];
alias Int = Fields!Type[1];
enum immutable(Char)[] hello = "hello";
test!( immutable Char, immutable Int)(hello);
test!( const Char, const Int)(hello);
test!( Char, Int)(hello.dup);
test!( shared Char, shared Int)(cast(shared) hello.dup);
test!(const shared Char, const shared Int)(hello);
}}
});
}
/**
* Capitalize the first character of $(D s) and convert the rest of $(D s) to
* lowercase.
*
* Params:
* input = The string to _capitalize.
*
* Returns:
* The capitalized string.
*
* See_Also:
* $(REF asCapitalized, std,uni) for a lazy range version that doesn't allocate memory
*/
S capitalize(S)(S input) @trusted pure
if (isSomeString!S)
{
import std.array : array;
import std.uni : asCapitalized;
import std.utf : byUTF;
return input.asCapitalized.byUTF!(ElementEncodingType!(S)).array;
}
///
pure @safe unittest
{
assert(capitalize("hello") == "Hello");
assert(capitalize("World") == "World");
}
auto capitalize(S)(auto ref S s)
if (!isSomeString!S && is(StringTypeOf!S))
{
return capitalize!(StringTypeOf!S)(s);
}
@safe pure unittest
{
assert(testAliasedString!capitalize("hello"));
}
@safe pure unittest
{
import std.algorithm.comparison : cmp;
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
{{
S s1 = to!S("FoL");
S s2;
s2 = capitalize(s1);
assert(cmp(s2, "Fol") == 0);
assert(s2 !is s1);
s2 = capitalize(s1[0 .. 2]);
assert(cmp(s2, "Fo") == 0);
s1 = to!S("fOl");
s2 = capitalize(s1);
assert(cmp(s2, "Fol") == 0);
assert(s2 !is s1);
s1 = to!S("\u0131 \u0130");
s2 = capitalize(s1);
assert(cmp(s2, "\u0049 i\u0307") == 0);
assert(s2 !is s1);
s1 = to!S("\u017F \u0049");
s2 = capitalize(s1);
assert(cmp(s2, "\u0053 \u0069") == 0);
assert(s2 !is s1);
}}
});
}
/++
Split $(D s) into an array of lines according to the unicode standard using
$(D '\r'), $(D '\n'), $(D "\r\n"), $(REF lineSep, std,uni),
$(REF paraSep, std,uni), $(D U+0085) (NEL), $(D '\v') and $(D '\f')
as delimiters. If $(D keepTerm) is set to $(D KeepTerminator.yes), then the
delimiter is included in the strings returned.
Does not throw on invalid UTF; such is simply passed unchanged
to the output.
Allocates memory; use $(LREF lineSplitter) for an alternative that
does not.
Adheres to $(HTTP www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0).
Params:
s = a string of $(D chars), $(D wchars), or $(D dchars), or any custom
type that casts to a $(D string) type
keepTerm = whether delimiter is included or not in the results
Returns:
array of strings, each element is a line that is a slice of $(D s)
See_Also:
$(LREF lineSplitter)
$(REF splitter, std,algorithm)
$(REF splitter, std,regex)
+/
alias KeepTerminator = Flag!"keepTerminator";
/// ditto
S[] splitLines(S)(S s, in KeepTerminator keepTerm = No.keepTerminator) @safe pure
if (isSomeString!S)
{
import std.array : appender;
import std.uni : lineSep, paraSep;
size_t iStart = 0;
auto retval = appender!(S[])();
for (size_t i; i < s.length; ++i)
{
switch (s[i])
{
case '\v', '\f', '\n':
retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator)]);
iStart = i + 1;
break;
case '\r':
if (i + 1 < s.length && s[i + 1] == '\n')
{
retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 2]);
iStart = i + 2;
++i;
}
else
{
goto case '\n';
}
break;
static if (s[i].sizeof == 1)
{
/* Manually decode:
* lineSep is E2 80 A8
* paraSep is E2 80 A9
*/
case 0xE2:
if (i + 2 < s.length &&
s[i + 1] == 0x80 &&
(s[i + 2] == 0xA8 || s[i + 2] == 0xA9)
)
{
retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 3]);
iStart = i + 3;
i += 2;
}
else
goto default;
break;
/* Manually decode:
* NEL is C2 85
*/
case 0xC2:
if (i + 1 < s.length && s[i + 1] == 0x85)
{
retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 2]);
iStart = i + 2;
i += 1;
}
else
goto default;
break;
}
else
{
case lineSep:
case paraSep:
case '\u0085':
goto case '\n';
}
default:
break;
}
}
if (iStart != s.length)
retval.put(s[iStart .. $]);
return retval.data;
}
///
@safe pure nothrow unittest
{
string s = "Hello\nmy\rname\nis";
assert(splitLines(s) == ["Hello", "my", "name", "is"]);
}
@safe pure nothrow unittest
{
string s = "a\xC2\x86b";
assert(splitLines(s) == [s]);
}
auto splitLines(S)(auto ref S s, in KeepTerminator keepTerm = No.keepTerminator)
if (!isSomeString!S && is(StringTypeOf!S))
{
return splitLines!(StringTypeOf!S)(s, keepTerm);
}
@safe pure nothrow unittest
{
assert(testAliasedString!splitLines("hello\nworld"));
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
{{
auto s = to!S(
"\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\n" ~
"mon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085"
);
auto lines = splitLines(s);
assert(lines.length == 14);
assert(lines[0] == "");
assert(lines[1] == "peter");
assert(lines[2] == "");
assert(lines[3] == "paul");
assert(lines[4] == "jerry");
assert(lines[5] == "ice");
assert(lines[6] == "cream");
assert(lines[7] == "");
assert(lines[8] == "sunday");
assert(lines[9] == "mon\u2030day");
assert(lines[10] == "schadenfreude");
assert(lines[11] == "kindergarten");
assert(lines[12] == "");
assert(lines[13] == "cookies");
ubyte[] u = ['a', 0xFF, 0x12, 'b']; // invalid UTF
auto ulines = splitLines(cast(char[]) u);
assert(cast(ubyte[])(ulines[0]) == u);
lines = splitLines(s, Yes.keepTerminator);
assert(lines.length == 14);
assert(lines[0] == "\r");
assert(lines[1] == "peter\n");
assert(lines[2] == "\r");
assert(lines[3] == "paul\r\n");
assert(lines[4] == "jerry\u2028");
assert(lines[5] == "ice\u2029");
assert(lines[6] == "cream\n");
assert(lines[7] == "\n");
assert(lines[8] == "sunday\n");
assert(lines[9] == "mon\u2030day\n");
assert(lines[10] == "schadenfreude\v");
assert(lines[11] == "kindergarten\f");
assert(lines[12] == "\v");
assert(lines[13] == "cookies\u0085");
s.popBack(); // Lop-off trailing \n
lines = splitLines(s);
assert(lines.length == 14);
assert(lines[9] == "mon\u2030day");
lines = splitLines(s, Yes.keepTerminator);
assert(lines.length == 14);
assert(lines[13] == "cookies");
}}
});
}
private struct LineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range)
{
import std.conv : unsigned;
import std.uni : lineSep, paraSep;
private:
Range _input;
alias IndexType = typeof(unsigned(_input.length));
enum IndexType _unComputed = IndexType.max;
IndexType iStart = _unComputed;
IndexType iEnd = 0;
IndexType iNext = 0;
public:
this(Range input)
{
_input = input;
}
static if (isInfinite!Range)
{
enum bool empty = false;
}
else
{
@property bool empty()
{
return iStart == _unComputed && iNext == _input.length;
}
}
@property typeof(_input) front()
{
if (iStart == _unComputed)
{
iStart = iNext;
Loop:
for (IndexType i = iNext; ; ++i)
{
if (i == _input.length)
{
iEnd = i;
iNext = i;
break Loop;
}
switch (_input[i])
{
case '\v', '\f', '\n':
iEnd = i + (keepTerm == Yes.keepTerminator);
iNext = i + 1;
break Loop;
case '\r':
if (i + 1 < _input.length && _input[i + 1] == '\n')
{
iEnd = i + (keepTerm == Yes.keepTerminator) * 2;
iNext = i + 2;
break Loop;
}
else
{
goto case '\n';
}
static if (_input[i].sizeof == 1)
{
/* Manually decode:
* lineSep is E2 80 A8
* paraSep is E2 80 A9
*/
case 0xE2:
if (i + 2 < _input.length &&
_input[i + 1] == 0x80 &&
(_input[i + 2] == 0xA8 || _input[i + 2] == 0xA9)
)
{
iEnd = i + (keepTerm == Yes.keepTerminator) * 3;
iNext = i + 3;
break Loop;
}
else
goto default;
/* Manually decode:
* NEL is C2 85
*/
case 0xC2:
if (i + 1 < _input.length && _input[i + 1] == 0x85)
{
iEnd = i + (keepTerm == Yes.keepTerminator) * 2;
iNext = i + 2;
break Loop;
}
else
goto default;
}
else
{
case '\u0085':
case lineSep:
case paraSep:
goto case '\n';
}
default:
break;
}
}
}
return _input[iStart .. iEnd];
}
void popFront()
{
if (iStart == _unComputed)
{
assert(!empty);
front;
}
iStart = _unComputed;
}
static if (isForwardRange!Range)
{
@property typeof(this) save()
{
auto ret = this;
ret._input = _input.save;
return ret;
}
}
}
/***********************************
* Split an array or slicable range of characters into a range of lines
using $(D '\r'), $(D '\n'), $(D '\v'), $(D '\f'), $(D "\r\n"),
$(REF lineSep, std,uni), $(REF paraSep, std,uni) and $(D '\u0085') (NEL)
as delimiters. If $(D keepTerm) is set to $(D Yes.keepTerminator), then the
delimiter is included in the slices returned.
Does not throw on invalid UTF; such is simply passed unchanged
to the output.
Adheres to $(HTTP www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0).
Does not allocate memory.
Params:
r = array of $(D chars), $(D wchars), or $(D dchars) or a slicable range
keepTerm = whether delimiter is included or not in the results
Returns:
range of slices of the input range $(D r)
See_Also:
$(LREF splitLines)
$(REF splitter, std,algorithm)
$(REF splitter, std,regex)
*/
auto lineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range)(Range r)
if ((hasSlicing!Range && hasLength!Range && isSomeChar!(ElementType!Range) ||
isSomeString!Range) &&
!isConvertibleToString!Range)
{
return LineSplitter!(keepTerm, Range)(r);
}
///
@safe pure unittest
{
import std.array : array;
string s = "Hello\nmy\rname\nis";
/* notice the call to 'array' to turn the lazy range created by
lineSplitter comparable to the string[] created by splitLines.
*/
assert(lineSplitter(s).array == splitLines(s));
}
auto lineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range)(auto ref Range r)
if (isConvertibleToString!Range)
{
return LineSplitter!(keepTerm, StringTypeOf!Range)(r);
}
@safe pure unittest
{
import std.array : array;
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
{{
auto s = to!S(
"\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\n" ~
"sunday\nmon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085"
);
auto lines = lineSplitter(s).array;
assert(lines.length == 14);
assert(lines[0] == "");
assert(lines[1] == "peter");
assert(lines[2] == "");
assert(lines[3] == "paul");
assert(lines[4] == "jerry");
assert(lines[5] == "ice");
assert(lines[6] == "cream");
assert(lines[7] == "");
assert(lines[8] == "sunday");
assert(lines[9] == "mon\u2030day");
assert(lines[10] == "schadenfreude");
assert(lines[11] == "kindergarten");
assert(lines[12] == "");
assert(lines[13] == "cookies");
ubyte[] u = ['a', 0xFF, 0x12, 'b']; // invalid UTF
auto ulines = lineSplitter(cast(char[]) u).array;
assert(cast(ubyte[])(ulines[0]) == u);
lines = lineSplitter!(Yes.keepTerminator)(s).array;
assert(lines.length == 14);
assert(lines[0] == "\r");
assert(lines[1] == "peter\n");
assert(lines[2] == "\r");
assert(lines[3] == "paul\r\n");
assert(lines[4] == "jerry\u2028");
assert(lines[5] == "ice\u2029");
assert(lines[6] == "cream\n");
assert(lines[7] == "\n");
assert(lines[8] == "sunday\n");
assert(lines[9] == "mon\u2030day\n");
assert(lines[10] == "schadenfreude\v");
assert(lines[11] == "kindergarten\f");
assert(lines[12] == "\v");
assert(lines[13] == "cookies\u0085");
s.popBack(); // Lop-off trailing \n
lines = lineSplitter(s).array;
assert(lines.length == 14);
assert(lines[9] == "mon\u2030day");
lines = lineSplitter!(Yes.keepTerminator)(s).array;
assert(lines.length == 14);
assert(lines[13] == "cookies");
}}
});
}
///
@nogc @safe pure unittest
{
auto s = "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\nmon\u2030day\n";
auto lines = s.lineSplitter();
static immutable witness = ["", "peter", "", "paul", "jerry", "ice", "cream", "", "sunday", "mon\u2030day"];
uint i;
foreach (line; lines)
{
assert(line == witness[i++]);
}
assert(i == witness.length);
}
@nogc @safe pure unittest
{
import std.algorithm.comparison : equal;
auto s = "std/string.d";
auto as = TestAliasedString(s);
assert(equal(s.lineSplitter(), as.lineSplitter()));
}
@safe pure unittest
{
auto s = "line1\nline2";
auto spl0 = s.lineSplitter!(Yes.keepTerminator);
auto spl1 = spl0.save;
spl0.popFront;
assert(spl1.front ~ spl0.front == s);
string r = "a\xC2\x86b";
assert(r.lineSplitter.front == r);
}
/++
Strips leading whitespace (as defined by $(REF isWhite, std,uni)).
Params:
input = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
of characters
Returns: $(D input) stripped of leading whitespace.
Postconditions: $(D input) and the returned value
will share the same tail (see $(REF sameTail, std,array)).
See_Also:
Generic stripping on ranges: $(REF _stripLeft, std, algorithm, mutation)
+/
auto stripLeft(Range)(Range input)
if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
!isInfinite!Range && !isConvertibleToString!Range)
{
static import std.ascii;
static import std.uni;
import std.utf : decodeFront;
while (!input.empty)
{
auto c = input.front;
if (std.ascii.isASCII(c))
{
if (!std.ascii.isWhite(c))
break;
input.popFront();
}
else
{
auto save = input.save;
auto dc = decodeFront(input);
if (!std.uni.isWhite(dc))
return save;
}
}
return input;
}
///
@safe pure unittest
{
import std.uni : lineSep, paraSep;
assert(stripLeft(" hello world ") ==
"hello world ");
assert(stripLeft("\n\t\v\rhello world\n\t\v\r") ==
"hello world\n\t\v\r");
assert(stripLeft("hello world") ==
"hello world");
assert(stripLeft([lineSep] ~ "hello world" ~ lineSep) ==
"hello world" ~ [lineSep]);
assert(stripLeft([paraSep] ~ "hello world" ~ paraSep) ==
"hello world" ~ [paraSep]);
import std.array : array;
import std.utf : byChar;
assert(stripLeft(" hello world "w.byChar).array ==
"hello world ");
}
auto stripLeft(Range)(auto ref Range str)
if (isConvertibleToString!Range)
{
return stripLeft!(StringTypeOf!Range)(str);
}
@safe pure unittest
{
assert(testAliasedString!stripLeft(" hello"));
}
/++
Strips trailing whitespace (as defined by $(REF isWhite, std,uni)).
Params:
str = string or random access range of characters
Returns:
slice of $(D str) stripped of trailing whitespace.
See_Also:
Generic stripping on ranges: $(REF _stripRight, std, algorithm, mutation)
+/
auto stripRight(Range)(Range str)
if (isSomeString!Range ||
isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range &&
!isConvertibleToString!Range &&
isSomeChar!(ElementEncodingType!Range))
{
import std.uni : isWhite;
alias C = Unqual!(ElementEncodingType!(typeof(str)));
static if (isSomeString!(typeof(str)))
{
import std.utf : codeLength;
foreach_reverse (i, dchar c; str)
{
if (!isWhite(c))
return str[0 .. i + codeLength!C(c)];
}
return str[0 .. 0];
}
else
{
size_t i = str.length;
while (i--)
{
static if (C.sizeof == 4)
{
if (isWhite(str[i]))
continue;
break;
}
else static if (C.sizeof == 2)
{
auto c2 = str[i];
if (c2 < 0xD800 || c2 >= 0xE000)
{
if (isWhite(c2))
continue;
}
else if (c2 >= 0xDC00)
{
if (i)
{
immutable c1 = str[i - 1];
if (c1 >= 0xD800 && c1 < 0xDC00)
{
immutable dchar c = ((c1 - 0xD7C0) << 10) + (c2 - 0xDC00);
if (isWhite(c))
{
--i;
continue;
}
}
}
}
break;
}
else static if (C.sizeof == 1)
{
import std.utf : byDchar;
char cx = str[i];
if (cx <= 0x7F)
{
if (isWhite(cx))
continue;
break;
}
else
{
size_t stride = 0;
while (1)
{
++stride;
if (!i || (cx & 0xC0) == 0xC0 || stride == 4)
break;
cx = str[i - 1];
if (!(cx & 0x80))
break;
--i;
}
if (!str[i .. i + stride].byDchar.front.isWhite)
return str[0 .. i + stride];
}
}
else
static assert(0);
}
return str[0 .. i + 1];
}
}
///
@safe pure
unittest
{
import std.uni : lineSep, paraSep;
assert(stripRight(" hello world ") ==
" hello world");
assert(stripRight("\n\t\v\rhello world\n\t\v\r") ==
"\n\t\v\rhello world");
assert(stripRight("hello world") ==
"hello world");
assert(stripRight([lineSep] ~ "hello world" ~ lineSep) ==
[lineSep] ~ "hello world");
assert(stripRight([paraSep] ~ "hello world" ~ paraSep) ==
[paraSep] ~ "hello world");
}
auto stripRight(Range)(auto ref Range str)
if (isConvertibleToString!Range)
{
return stripRight!(StringTypeOf!Range)(str);
}
@safe pure unittest
{
assert(testAliasedString!stripRight("hello "));
}
@safe pure unittest
{
import std.array : array;
import std.uni : lineSep, paraSep;
import std.utf : byChar, byDchar, byUTF, byWchar, invalidUTFstrings;
assert(stripRight(" hello world ".byChar).array == " hello world");
assert(stripRight("\n\t\v\rhello world\n\t\v\r"w.byWchar).array == "\n\t\v\rhello world"w);
assert(stripRight("hello world"d.byDchar).array == "hello world"d);
assert(stripRight("\u2028hello world\u2020\u2028".byChar).array == "\u2028hello world\u2020");
assert(stripRight("hello world\U00010001"w.byWchar).array == "hello world\U00010001"w);
static foreach (C; AliasSeq!(char, wchar, dchar))
{
foreach (s; invalidUTFstrings!C())
{
cast(void) stripRight(s.byUTF!C).array;
}
}
cast(void) stripRight("a\x80".byUTF!char).array;
wstring ws = ['a', cast(wchar) 0xDC00];
cast(void) stripRight(ws.byUTF!wchar).array;
}
/++
Strips both leading and trailing whitespace (as defined by
$(REF isWhite, std,uni)).
Params:
str = string or random access range of characters
Returns:
slice of $(D str) stripped of leading and trailing whitespace.
See_Also:
Generic stripping on ranges: $(REF _strip, std, algorithm, mutation)
+/
auto strip(Range)(Range str)
if (isSomeString!Range ||
isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range &&
!isConvertibleToString!Range &&
isSomeChar!(ElementEncodingType!Range))
{
return stripRight(stripLeft(str));
}
///
@safe pure unittest
{
import std.uni : lineSep, paraSep;
assert(strip(" hello world ") ==
"hello world");
assert(strip("\n\t\v\rhello world\n\t\v\r") ==
"hello world");
assert(strip("hello world") ==
"hello world");
assert(strip([lineSep] ~ "hello world" ~ [lineSep]) ==
"hello world");
assert(strip([paraSep] ~ "hello world" ~ [paraSep]) ==
"hello world");
}
auto strip(Range)(auto ref Range str)
if (isConvertibleToString!Range)
{
return strip!(StringTypeOf!Range)(str);
}
@safe pure unittest
{
assert(testAliasedString!strip(" hello world "));
}
@safe pure unittest
{
import std.algorithm.comparison : equal;
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!( char[], const char[], string,
wchar[], const wchar[], wstring,
dchar[], const dchar[], dstring))
{
assert(equal(stripLeft(to!S(" foo\t ")), "foo\t "));
assert(equal(stripLeft(to!S("\u2008 foo\t \u2007")), "foo\t \u2007"));
assert(equal(stripLeft(to!S("\u0085 μ \u0085 \u00BB \r")), "μ \u0085 \u00BB \r"));
assert(equal(stripLeft(to!S("1")), "1"));
assert(equal(stripLeft(to!S("\U0010FFFE")), "\U0010FFFE"));
assert(equal(stripLeft(to!S("")), ""));
assert(equal(stripRight(to!S(" foo\t ")), " foo"));
assert(equal(stripRight(to!S("\u2008 foo\t \u2007")), "\u2008 foo"));
assert(equal(stripRight(to!S("\u0085 μ \u0085 \u00BB \r")), "\u0085 μ \u0085 \u00BB"));
assert(equal(stripRight(to!S("1")), "1"));
assert(equal(stripRight(to!S("\U0010FFFE")), "\U0010FFFE"));
assert(equal(stripRight(to!S("")), ""));
assert(equal(strip(to!S(" foo\t ")), "foo"));
assert(equal(strip(to!S("\u2008 foo\t \u2007")), "foo"));
assert(equal(strip(to!S("\u0085 μ \u0085 \u00BB \r")), "μ \u0085 \u00BB"));
assert(equal(strip(to!S("\U0010FFFE")), "\U0010FFFE"));
assert(equal(strip(to!S("")), ""));
}
});
}
@safe pure unittest
{
import std.array : sameHead, sameTail;
import std.exception : assertCTFEable;
assertCTFEable!(
{
wstring s = " ";
assert(s.sameTail(s.stripLeft()));
assert(s.sameHead(s.stripRight()));
});
}
/++
If $(D str) ends with $(D delimiter), then $(D str) is returned without
$(D delimiter) on its end. If it $(D str) does $(I not) end with
$(D delimiter), then it is returned unchanged.
If no $(D delimiter) is given, then one trailing $(D '\r'), $(D '\n'),
$(D "\r\n"), $(D '\f'), $(D '\v'), $(REF lineSep, std,uni), $(REF paraSep, std,uni), or $(REF nelSep, std,uni)
is removed from the end of $(D str). If $(D str) does not end with any of those characters,
then it is returned unchanged.
Params:
str = string or indexable range of characters
delimiter = string of characters to be sliced off end of str[]
Returns:
slice of str
+/
Range chomp(Range)(Range str)
if ((isRandomAccessRange!Range && isSomeChar!(ElementEncodingType!Range) ||
isNarrowString!Range) &&
!isConvertibleToString!Range)
{
import std.uni : lineSep, paraSep, nelSep;
if (str.empty)
return str;
alias C = ElementEncodingType!Range;
switch (str[$ - 1])
{
case '\n':
{
if (str.length > 1 && str[$ - 2] == '\r')
return str[0 .. $ - 2];
goto case;
}
case '\r', '\v', '\f':
return str[0 .. $ - 1];
// Pop off the last character if lineSep, paraSep, or nelSep
static if (is(C : const char))
{
/* Manually decode:
* lineSep is E2 80 A8
* paraSep is E2 80 A9
*/
case 0xA8: // Last byte of lineSep
case 0xA9: // Last byte of paraSep
if (str.length > 2 && str[$ - 2] == 0x80 && str[$ - 3] == 0xE2)
return str [0 .. $ - 3];
goto default;
/* Manually decode:
* NEL is C2 85
*/
case 0x85:
if (str.length > 1 && str[$ - 2] == 0xC2)
return str [0 .. $ - 2];
goto default;
}
else
{
case lineSep:
case paraSep:
case nelSep:
return str[0 .. $ - 1];
}
default:
return str;
}
}
/// Ditto
Range chomp(Range, C2)(Range str, const(C2)[] delimiter)
if ((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range) ||
isNarrowString!Range) &&
!isConvertibleToString!Range &&
isSomeChar!C2)
{
if (delimiter.empty)
return chomp(str);
alias C1 = ElementEncodingType!Range;
static if (is(Unqual!C1 == Unqual!C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4)))
{
import std.algorithm.searching : endsWith;
if (str.endsWith(delimiter))
return str[0 .. $ - delimiter.length];
return str;
}
else
{
auto orig = str.save;
static if (isSomeString!Range)
alias C = dchar; // because strings auto-decode
else
alias C = C1; // and ranges do not
foreach_reverse (C c; delimiter)
{
if (str.empty || str.back != c)
return orig;
str.popBack();
}
return str;
}
}
///
@safe pure
unittest
{
import std.uni : lineSep, paraSep, nelSep;
import std.utf : decode;
assert(chomp(" hello world \n\r") == " hello world \n");
assert(chomp(" hello world \r\n") == " hello world ");
assert(chomp(" hello world \f") == " hello world ");
assert(chomp(" hello world \v") == " hello world ");
assert(chomp(" hello world \n\n") == " hello world \n");
assert(chomp(" hello world \n\n ") == " hello world \n\n ");
assert(chomp(" hello world \n\n" ~ [lineSep]) == " hello world \n\n");
assert(chomp(" hello world \n\n" ~ [paraSep]) == " hello world \n\n");
assert(chomp(" hello world \n\n" ~ [ nelSep]) == " hello world \n\n");
assert(chomp(" hello world") == " hello world");
assert(chomp("") == "");
assert(chomp(" hello world", "orld") == " hello w");
assert(chomp(" hello world", " he") == " hello world");
assert(chomp("", "hello") == "");
// Don't decode pointlessly
assert(chomp("hello\xFE", "\r") == "hello\xFE");
}
StringTypeOf!Range chomp(Range)(auto ref Range str)
if (isConvertibleToString!Range)
{
return chomp!(StringTypeOf!Range)(str);
}
StringTypeOf!Range chomp(Range, C2)(auto ref Range str, const(C2)[] delimiter)
if (isConvertibleToString!Range)
{
return chomp!(StringTypeOf!Range, C2)(str, delimiter);
}
@safe pure unittest
{
assert(testAliasedString!chomp(" hello world \n\r"));
assert(testAliasedString!chomp(" hello world", "orld"));
}
@safe pure unittest
{
import std.conv : to;
import std.exception : assertCTFEable;
assertCTFEable!(
{
static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
{