import { IndexOfLongestString, TupleOf } from './Array'; import { Extends, Or } from './Boolean'; import { Add, Decrement, Increment, IsGreaterThan, IsLessOrEqual, Subtract } from './Number'; import { Keys } from './misc'; import { Head as ArrayHead } from 'ts-toolbelt/out/List/Head'; import { Tail as ArrayTail } from 'ts-toolbelt/out/List/Tail'; import { Length } from 'ts-toolbelt/out/String/Length'; import { Literal } from 'ts-toolbelt/out/String/_Internal'; import { ListOf } from 'ts-toolbelt/out/Union/ListOf'; import { AnyKey, IsNever } from 'tsdef'; import { Primitive } from 'utility-types'; /** * a type that can be converted to a string in a template literal type */ export type TemplateLiteralStringable = Exclude; /** * creates a stringified version of `T` * @example * type Foo = ToString<1|2> //'1'|'2' */ export type ToString = `${T}`; /** * a URI that starts with the given `Protocol` * @example * const foo: UriString<'http'> = 'foo' //error * const bar: UriString<'http'> = 'http://foo.com' //no error */ export type UriString = `${Protocol}://${Domain | IP}${string}`; /** * a URL with either the http or https protocol. * @example * const foo: UrlString = 'foo://bar' //error * const bar: UrlString = 'http://foo' //no error */ export type UrlString = UriString<'http' | 'https'>; /** a domain name */ export type Domain = `${string}.${string}`; export type IPv4 = Join, '.'>; export type IPv6 = Join, ':'>; /** an IP address */ export type IP = IPv4 | IPv6; /** an email address */ export type Email = `${string}@${Domain}`; export type GUID = Join, '-'>; /** * the name of a file with an `Extension`. * if `Extension` is not provided an empty string then it's treated as a file with no extension * @example * type File: FileName //string, anything goes * type Image: FileName<'png'|'jpg'> // `${string}.png` | `${string}.jpg` */ export type FileName = `${string}${IsNever}`; type DuplicateStringTailRec = N extends 0 ? Result : DuplicateStringTailRec, `${Result}${T}`>; /** * duplicates a string a given number of times * @example * type Foo = DuplicateString<'foo', 3> //'foofoofoo' */ export type DuplicateString = DuplicateStringTailRec; /** * anything that can be represented as a string (ie. has a {@link toString} method) */ export interface Stringable { toString: () => string; } /** * gets the first character in a string */ export type Head = Str extends `${infer R}${string}` ? R : never; /** * removes the first character off the start of a string */ export type Tail = TrimStart; type _TrimStart = Iterator extends Index ? Str : _TrimStart}${infer R}` ? R : never, Index, Increment>; /** * trims the characters up to `Index` off the start of `String` (inclusive) * @example * type Foo = TrimStart<'foobar', 2> //'bar' */ export type TrimStart = Or | Extends> extends true ? string : _TrimStart; /** * trims the characters past `Index` off the end of `String` (exclusive) * @example * type Foo = TrimEnd<'foobar', 2> //'foo' */ export type TrimEnd = Substring; /** * gets the characters of `String` between `StartIndex` (inclusive) and `EndIndex` (exclusive) */ export type Substring = Or | Extends> extends true ? string : TrimStart extends `${infer R}${TrimStart}` ? R : never; export type CharAt = Head>; /** * `true` if `String` contains `Substring`, else `false` */ export type Includes = Str extends `${string}${Substring}${string}` ? true : false; type _IndexOf = Substring>> extends Substr ? CurrentIndex : _IndexOf>; /** * gets the index of a `Substring` within a `String`. returns `-1` if it's not present */ export type IndexOf = string extends Str | Substring ? number : Includes extends true ? _IndexOf : -1; type _Replace = Str extends `${infer BS}${Find}${infer AS}` ? Replace<`${BS}${ReplaceWith}${AS}`, Find, ReplaceWith> : Str; /** * replaces all instances of `Find` with `ReplaceWith`. the type equivalent of {@link String.prototype.replace} * * modified version of [`String/Replace` from `ts-toolbelt`](https://millsp.github.io/ts-toolbelt/modules/string_replace.html) * that also allows `undefined` and `null` (ie. anything that template literal types allow) */ export declare type Replace = _Replace extends infer X extends string ? X : never; /** * replaces the first instance of `Find` with `ReplaceWith`. the type equivalent of {@link String.prototype.replace} */ export type ReplaceOne = Str extends `${infer Start}${Find}${infer End}` ? `${Start}${ReplaceWith}${End}` : Str; /** * checks whether the length of type `String` is **greater than or equal to** the length of type `Length` */ export type LengthGreaterOrEqual = CharAt> extends never ? false : true; /** * checks whether the length of type `String` is **greater than** the length of type `Length` */ export type LengthGreaterThan = LengthGreaterOrEqual>; type CaseInsensitiveTailRec = Value extends '' ? Result : CaseInsensitiveTailRec, `${Result}${Uppercase> | Lowercase>}`>; /** * creates a union of every possible capitalization of the given string * * only works on short strings * @example * type Foo = CaseInsensitive<'abc'> //"abc" | "Abc" | "ABc" | "ABC" | "AbC" | "aBc" | "aBC" | "abC" */ export type CaseInsensitive = CaseInsensitiveTailRec; type _DuplicateStringUntilLength = Length extends Size ? CurrentString : LengthGreaterThan<`${CurrentString}${Str}`, Size> extends true ? TrimEnd<`${CurrentString}${Str}`, Size> : _DuplicateStringUntilLength; /** * like {@link DuplicateString} except it duplicates until the exact provided `Size`, instead of a number of repetitions * @example * type Foo = DuplicateStringUntilLength<'abc', 8> //'abcabcab' */ export type DuplicateStringUntilLength = _DuplicateStringUntilLength; /** * the type equivalent of {@link String.prototype.padStart} */ export type PadStart = `${TemplateLiteralStringable}` extends Str | PadString ? string : { [Key in Str]: number extends Size ? string : `${DuplicateStringUntilLength>>}${Key}`; }[Str]; /** * takes a `String` and a `MatchString` (ideally a union) and returns the string in the union that `String` started with * @example * type Foo: MatchStart<'foo', 'bar' | 'foo' | 'baz'> */ export type MatchStart = string extends Str | MatchString ? string : Str extends `${MatchString}${infer End}` ? Str extends `${infer Start}${End}` ? Start : never : never; /** * `true` if `Full` starts with the given `CheckStart`, else `false` */ export type StartsWith = string extends Full | CheckStart ? boolean : Full extends `${CheckStart}${string}` ? true : false; /** * `true` if `Full` ends with the given `CheckEnd`, else `false` */ export type EndsWith = string extends Full | CheckEnd ? boolean : Full extends `${string}${CheckEnd}` ? true : false; type _Join, D extends string, Result extends string> = T extends [] ? Result : T extends [Literal] ? `${Result}${T[0]}` : T extends [Literal, ...infer R] ? _Join : string; /** * Concat many literals together * * like `ts-toolbelt`'s `Join` but tail-recursive, to allow for a higher stack depth in ts 4.5 * @param T to concat * @param D to delimit * @see https://github.com/millsp/ts-toolbelt/issues/255 */ export type Join, D extends string = ''> = _Join extends infer X extends string ? X : never; /** a map of values where the keys are to be replaced by the values in {@link ReplaceValuesWithMap} */ type ReplaceValuesMap = Record, unknown>; type _TokenizeString = '' extends Value ? Tokens : LongestString>> extends infer Token ? Token extends string ? _TokenizeString>, Map, [...Tokens, Token]> : IndexOf> extends infer NextTokenIndex ? NextTokenIndex extends -1 ? [...Tokens, Value] : _TokenizeString>>, Map, [ ...Tokens, TrimEnd ]> : never : never; type _ReplaceValuesWithMap = string[] extends InputTokens ? InputTokens : InputTokens extends [] ? OutputTokens : _ReplaceValuesWithMap, Map, [ ...(OutputTokens extends string[] ? OutputTokens : never), ArrayHead extends Keys ? Map[ArrayHead] : ArrayHead ]>; /** * replaces all instances in `Value` of the first string with the second string with each tuple in `Map` * @example * type Foo = ReplaceValuesWithMap<'foobarbaz', {foo: 'bar', baz: 'qux'}> // "barbarqux" */ export type ReplaceValuesWithMap = Join< /** @ts-expect-error -- TODO: figure out what causes this, its a stack depth error which is apparently very dangerous to suppress */ _ReplaceValuesWithMap<_TokenizeString, Map, []> extends infer Strings ? Strings extends ReadonlyArray ? Strings : never : never>; /** * a stringified version of {@link Enumerate} * * the advantage of this one is that it works for much higher values, as it currently doesn't seem possible to do this * with numbers */ export type EnumerateAsString = Keys>; /** * a stringified version of {@link RangeType} * * the advantage of this one is that it works for much higher values, as it currently doesn't seem possible to do this * with numbers */ export type RangeAsString = Exclude, EnumerateAsString> | ToString; /** gets the longest string in a union of strings */ export type LongestString = ListOf[IndexOfLongestString>]; type SplitByUnionTailRec = Value extends '' ? never : IndexOf extends -1 ? CurrentResult | Value : IndexOf extends 0 ? SplitByUnionTailRec>, SplitBy, CurrentResult> : SplitByUnionTailRec, IndexOf>>, SplitBy, CurrentResult | TrimEnd>>; /** * like `Split` from `ts-toolbelt` but works properly with unions * @example * type Foo = SplitByUnion<'foo,bar.baz', '.' | ','> //'foo'|'bar'|'baz' */ export type SplitByUnion = SplitByUnionTailRec; type SplitByLengthTailRec = Length extends Len ? [...Result, T] : SplitByLengthTailRec, Len, [...Result, TrimEnd]>; /** * splits a string into an array of strings with a specified length * @example * type Foo = SplitByLength<'foobarbaz', 3> //['foo', 'bar', 'baz'] */ export type SplitByLength = SplitByLengthTailRec; /** * truncates a string to the specified `MaxLength`, concatenating an `Ellipsis` if the string is too long */ export type Truncate = IsGreaterThan, MaxLength> extends true ? never : IsLessOrEqual, MaxLength> extends true ? Str : `${Substring>>}${Ellipsis}`; /** * if the given `Str` doesn't already start with the given `Prefix`, then append the prefix to the start of the string. * * if the `Str` already starts with the `Prefix`, then returns the string as is * @example * type Foo = MakeStartsWith<'foo', '.'> // '.foo' * type Bar = MakeStartsWith<'.foo', '.'> // '.foo' */ export type MakeStartsWith = string extends Str | Prefix ? string : StartsWith extends true ? Str : `${Prefix}${Str}`; /** * if the given `Str` doesn't already end with the given `Suffix`, then append the suffix to the end of the string. * * if the `Str` already ends with the `Suffix`, then returns the string as is * @example * type Foo = MakeEndsWith<'foo', '.'> // 'foo.' * type Bar = MakeEndsWith<'foo.', '.'> // 'foo.' */ export type MakeEndsWith = string extends Str | Suffix ? string : EndsWith extends true ? Str : `${Str}${Suffix}`; /** * gets the string to the left of the given `Substring` * @example * type Foo = LeftOf<'foo.bar', '.'> //'foo' */ export type LeftOf = TrimEnd>; /** * gets the string to the right of the given `Substring` * @example * type Foo = RightOf<'foo.bar', '.'> //'bar' */ export type RightOf = TrimStart, Length>>; /** * gets the string between the given `Start` and `End` types * @example * MidOf<'foo(bar)baz', '(', ')'> //'bar' */ export type MidOf = RightOf, Start>; type CountInStringTailRec = IndexOf extends -1 ? Result : CountInStringTailRec, Substring, Increment>; /** * counts how many instances of the given `Substring` are in `Str` * @example * CountInString<'1,2,3,4', ','> //3 */ export type CountInString = string extends Str | Substring ? number : CountInStringTailRec; export type Trim = StartsWith extends infer StartsWithSpace ? boolean extends StartsWithSpace ? Str : StartsWithSpace extends true ? Trim> : EndsWith extends true ? Trim, 1>>> : Str : never; export type RemovePrefix = string extends Prefix ? string : T extends `${Prefix}${infer Result}` ? Result : T; export {}; //# sourceMappingURL=String.d.ts.map