nixfiles/libs/core/lib/string/default.nix
Sebastian Walz 9f7b02e1cd
Tohu vaBohu
2023-04-03 14:38:02 +02:00

776 lines
25 KiB
Nix

{ debug, derivation, expression, integer, intrinsics, library, list, set, type, ... } @ libs:
let
veryDeep
= {
depth = 10;
panic = false;
};
DoNotFollow
= type "DoNotFollow"
{
from = value: DoNotFollow.instanciate { inherit value; };
};
combine = list.combine (a: b: "${a}${b}");
hexChars = [ "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "a" "b" "c" "d" "e" "f" ];
hexPairs = combine hexChars hexChars;
appendContext
= intrinsics.appendContext;
ascii = list.generate (index: getChar index) 128;
lowerAscii = list.generate (index: getChar ( 97 + index)) 26;
upperAscii = list.generate (index: getChar ( 65 + index)) 26;
char
= {
backspace = getChar' "0008";
carriageReturn = "\r";
delete = getChar' "007f";
escape = getChar' "001b";
horizontalTab = "\t";
lineFeed = "\n";
null = "";
};
concat#: [ string ] -> string
= concatWith "";
concatIndexMapped#: F -> [ T ] -> string
# where
# F: int -> T -> string,
# T: Any
= convert:
concatIndexMappedWith convert "";
concatIndexMappedWith#: F -> string -> [ T ] -> string
# where
# F: int -> T -> string,
# T: Any:
= convert:
seperator:
parts:
concatWith seperator (list.imap convert parts);
concatLines#: [ string ] -> string
= concatWith "\n";
concatMapped#: F -> [ T ] -> string
# where
# F: T -> string,
# T: Any
= convert:
concatMappedWith convert "";
concatMapped'#: F -> [ T ] -> string
# where
# F: string -> T -> string,
# T: Any
= convert:
concatMappedWith' convert "";
concatMappedLines#: F -> [ T ] -> string
# where
# F: T -> string,
# T: Any
= convert:
concatMappedWith convert "\n";
concatMappedLines'#: F -> [ T ] -> string
# where
# F: string -> T -> string,
# T: Any
= convert:
concatMappedWith' convert "\n";
concatMappedWith#: F -> string -> [ T ] -> string
# where
# F: T -> string,
# T: Any
= convert:
seperator:
parts:
concatWith seperator (list.map convert parts);
concatMappedWith'#: F -> string -> [ T ] -> string
# where
# F: string -> T -> string,
# T: Any
= convert:
seperator:
parts:
concatWith seperator (set.mapToList convert parts);
concatMappedWithFinal#: F -> string -> string -> [ T ] -> string
# where
# F: T -> string,
# T: Any
= convert:
seperator:
final:
parts:
concatWithFinal
seperator
final
(list.map convert parts);
concatMappedWithFinal'#: F -> string -> string -> [ T ] -> string
# where
# F: string -> T -> string,
# T: Any
= convert:
seperator:
final:
parts:
concatWithFinal
seperator
final
(set.mapToList convert parts);
concatWith#: string -> [ string ] -> string
= intrinsics.concatStringsSep
or (
seperator:
parts:
list.fold
(
result:
entry:
"${result}${seperator}${entry}"
)
(list.head parts)
(list.tail parts)
);
concatWithFinal#: string -> string -> [ string ] -> string
= seperator:
final:
parts:
if list.length parts > 1
then
"${concatWith seperator (list.body parts)}${final}${list.foot parts}"
else
list.head parts;
concatWords#: [ string ] -> string
= concatWith " ";
discardContext
= intrinsics.unsafeDiscardStringContext;
discardOutputDependency
= intrinsics.unsafeDiscardOutputDependency;
escape
= replace
[ "\"" "\\" "\n" "\r" "\t" char.escape ]
[ "\\\"" "\\\\" "\\n" "\\r" "\\t" "\\e" ];
escapeKey
= key:
if (match "[A-Za-z_][-'0-9A-Za-z_]*" key) != null
then
key
else
"\"${escape key}\"";
from'#: T -> string
# where T: Any
= { legacy, display, depth, maxDepth, nice, showType, trace, attrPath ? [], typo ? null } @ args:
input:
(
if trace
then
intrinsics.trace
(
let
attrPath'
= concatMapped
(
key:
if integer.isInstanceOf key
then
"[${integer.toString key}]"
else
".${key}"
)
attrPath;
in
"${if typo != null then "<${typo}>" else ""}(${if display then "debug" else "string"})${attrPath'}"
)
else
x: x
)
(
let
inherit(expression.tryEval input) success value;
in
if maxDepth != null
&& depth >= maxDepth
then
if !showType
then
""
else if !success
then
"<panic>"
else
type.matchPrimitiveOrDefault value
{
bool = if value then "true" else "false";
float = intrinsics.toString value;
int = integer.toString value;
}
(type.format value)
else if depth >= veryDeep.depth
then
debug.panic
"from'"
{
text = "Very deep o.O";
when = veryDeep.panic;
data
= concatMapped
(
key:
if integer.isInstanceOf key
then
"[${integer.toString key}]"
else
".${key}"
)
attrPath;
}
"<very deep (over ${toString depth})>"
else if !success
then
debug.panic
"from'"
{
text = "Panic occured while evaluation input value.";
when = !display || legacy;
}
"<panic>"
else
type.matchPrimitive value
{
bool
= if legacy
then
if value
then
"1"
else
""
else
if value
then
"true"
else
"false";
float = intrinsics.toString value;
int = integer.toString value;
lambda
= if display
then
if legacy
then
"<CODE>"
else
let
mapArguments
= { ... } @ data:
set.mapToList
(
key:
value:
if value
then
"${escapeKey key}?"
else
escapeKey key
)
data;
arguments = intrinsics.functionArgs value;
in
if arguments != {}
then
"{ ${concatWith ", " (mapArguments arguments)} }: "
else
"_: "
else
debug.panic "from'" "cannot coerse a function to a string";
list
= if value == []
then
if legacy
then
""
else
"[]"
else
let
indent = repeat " " depth;
body
= list.imap
(
index:
value:
let
value'
= from'
{
inherit display legacy maxDepth nice showType trace typo;
depth = depth + 1;
attrPath = attrPath ++ [ index ];
}
value;
in
if this != value
then
"${value'}"
else
"<repeating>"
)
value;
this = value;
in
if legacy
then
"[ ${concatMappedWith (value: "(${value})") " " body} ]"
else if nice
then
"[\n${indent} ${concatWith ",\n${indent} " body}\n${indent}]"
else
"[ ${concatWith ", " body} ]";
null
= if legacy
then
""
else
"null";
path = toString value;#"${value}";
set
= if value == {}
then
"{}"
else if display
then
let
body
= set.mapToList format
(
if value.__public__ or null != null
then
set.filterByName
value
value.__public__
else
set.remove
value
[ "__public__" "__type__" "__variant__" ]
);
format
= key:
value:
let
key' = escapeKey key;
value'
= from'
{
inherit legacy display nice showType;
maxDepth
= if !hasUnfix
then
maxDepth
else
depth;
trace
= trace
|| (key' == "lib");
depth = depth + 1;
attrPath = attrPath ++ [ key' ];
typo
= if type.getType value != null
then
typeName
else
typo;
}
value;
in
if key' != "lib"
then
if value != this
then
"${key'} = ${value'};"
else
"${key'} = <repeating>;"
else
"<lib>";
hasUnfix = value ? __unfix__;
indent = repeat " " depth;
niceText = "{\n${indent} ${concatWith "\n${indent} " body}\n${indent}}";
this = value;
typeName = type.format value;
in
if debug.Debug.isInstanceOf value
then
"<Debug>"
else if derivation.isInstanceOf' value
then
"<Derivation ${value.name}>"
else if library.isInstanceOf value
then
"<Library ${value.getVariant}>"
else if DoNotFollow.isInstanceOf value
then
"<skipped>"
else if value._type or null != null
then
"<${value._type}>"
else if nice && !legacy
then
if type.getType value != null
then
"<${typeName} ${niceText}>"
else
niceText
else if legacy
|| type.getType value == null
then
"{ ${concatWith " " body} }"
else
"<${typeName} { ${concatWith " " body} }>"
else if value ? outPath
then
value.outPath
else if value ? __toString
then
value.__toString value
else
debug.panic "from'"
{
text = "cannot coerse this set to a string";
data = value;
};
string
= let
value' = escape value;
in
if nice
then
let
indent = repeat " " depth;
lines = splitLines value;
lines'
= concatMapped
(
line:
if match "[ \n\r\t]*" line != null
then
"\n${indent}"
else
"\n${indent} ${replace [ "\${" "''" ] [ "''\${" "'''" ] line}"
)
lines;
in
if list.length lines > 1
then
"''${lines'}''"
else
"\"${value'}\""
else if display || depth > 0
then
"\"${value'}\""
else
value;
}
);
from#: T -> string
# where T: Any
= from' { display = false; legacy = false; maxDepth = null; depth = 0; nice = false; showType = false; trace = false; };
getByte#: string -> int
= text: getByte' (slice 0 1 text);
getByte'#: string -> int
= let
get#: string -> int -> string
= text: index: slice index 1 text;
head#: string -> char
= text: get text 0;
bytes
= intrinsics.listToAttrs
(
( list.generate ( value: { name = getChar value; inherit value; } 127 ) )
++ [
{ name = head ( getChar' "0080" ); value = 194; }
{ name = head ( getChar' "00c0" ); value = 195; }
]
++ (
list.combine
(
a: b:
{
name = head ( getChar' "0${list.get hexChars a}${list.get hexChars (4 * b)}0" );
value = 192 + 4 * a + b;
}
)
( list.range 1 7 )
( list.range 0 3 )
)
++ (
list.map
(
a:
{
name = head ( getChar' "${list.get hexChars a}800" );
value = 224 + a;
}
)
( list.range 0 15 )
)
++ (
list.combine
(
a:
b:
{
name = get ( getChar' "00${list.get hexChars (8 + a)}${list.get hexChars b}" ) 1;
value = 128 + 16 * a + b;
}
)
( list.range 0 3 )
( list.range 0 15 )
)
);
in
bytes.${char};
getChar'#: string -> char
= index: expression.fromJSON "\"\\u${index}\"";
getChar#: int -> char
= index: list.get ( list.map getChar' (combine hexPairs hexPairs) ) index;
getContext
= intrinsics.getContext;
getFinalChar
= self:
slice ((length self) - 1) 1 self;
hasContext
= intrinsics.hasContext;
hash#: string -> string -> string
= intrinsics.hashString;
isEmpty#: string -> bool
= text: text == "";
isInstanceOf = intrinsics.isString or (value: type.getPrimitive value == "string");
length#: string -> int
= intrinsics.stringLength
or (
text:
let
rest = slice 1 9223372036854775807 text;
in
if text == "" then 0
else ( length rest ) + 1
);
lengthUTF8#: string -> int
= text: list.length ( toUTF8characters text );
match#: string -> string -> [ T ]
# where T: null | string | [ T ]
= intrinsics.match;
orNull
= value:
isInstanceOf value || value == null;
repeat#: string -> int -> string
= text:
multiplier:
concat ( list.generate (_: text) multiplier );
replace#: [ string ] -> [ string ] -> string -> string
= intrinsics.replaceStrings; /* should be possible to construct, but ahhh */
replace'
= { ... } @ substitutions:
replace
(set.names substitutions)
(set.values substitutions);
slice#: int -> int -> string -> string
= intrinsics.substring;
split#: string -> string -> [ T ]
# where T: null | string | [ T ]
= intrinsics.split;
splitAt#: string -> string -> [ string ]
= regex:
text:
list.filter
( line: isInstanceOf line )
( split regex text );
splitAt'#: string -> string -> [ string ]
= regex:
text:
list.filter
( line: isInstanceOf line && line != "")
( split regex text );
splitLines#: string -> [ string ]
= splitAt "\n";
splitSpaces
= splitAt "([[:space:]]+)";
splitTabs#: string -> [ string ]
= splitAt "\t";
toBytes#: string -> [ u8 ]
= text: list.map getByte' (toCharacters text);
toCharacters#: string -> [ asciiChar ]
= text:
list.generate (index: slice index 1 text) (length text);
toPath#: string -> path
= path: "./${path}";
toLowerCase#: string -> string
= let
caseMap
= set.pair
( upperAscii ++ [ "Ä" "Ö" "Ü" "" ] )
( lowerAscii ++ [ "ä" "ö" "ü" "ß" ] );
in
text:
list.fold
(
text:
char:
"${text}${caseMap.${char} or char}"
)
""
( toUTF8characters text );
toString#: T -> string
= intrinsics.toString
or (
from' { display = false; legacy = true; maxDepth = null; depth = 0; nice = false; showType = false; trace = false; }
);
toTrace#: T -> string
# where T: Any
= maxDepth: nice: showType: trace: from' { display = true; legacy = false; inherit maxDepth nice showType trace; depth = 0; };
toTraceDeep#: T -> string
# where T: Any
= toTrace null;
toTraceShallow#: T -> string
# where T: Any
= toTrace 1;
toUpperCase#: string -> string
= let
# The german letter ß (sz) cannot be at the start of a word and
# therefore does not have a capital form.
# However in uppercase text, the letter ẞ is allowed,
# but the unicode standard still defaults to SS.
# I do not care about that, I prefer ẞ instead.
caseMap
= set.pair
( lowerAscii ++ [ "ä" "ö" "ü" "ß" ] )
( upperAscii ++ [ "Ä" "Ö" "Ü" "" ] );
in
text:
concatMapped
(char: "${caseMap.${char} or char}")
(toUTF8characters text);
toUTF8characters#: string -> [ utf8char ]
= text:
let
this
= list.fold
(
{ result, text }:
character:
if character <= char.delete
then
{
text = "";
result
= result
++ ( if text != "" then [ text ] else [ ] )
++ [ character ];
}
else
{
# Does not validate! E.g. C2 A3 A3 would be considered one char, even if this is invalid utf8!
text = "${text}${character}";
inherit result;
}
)
{
text = "";
result = [ ];
}
(toCharacters text);
in
this.result
++ (
if this.text != ""
then
[ this.text ]
else
[ ]
);
trim = library.import ./trim.nix libs;
in
type "string"
{
inherit DoNotFollow;
inherit appendContext ascii
char
concat concatLines
concatIndexMapped concatIndexMappedWith
concatMapped concatMapped'
concatMappedLines concatMappedLines'
concatMappedWith concatMappedWith'
concatMappedWithFinal concatMappedWithFinal'
concatWith concatWithFinal concatWords
discardContext discardOutputDependency
escapeKey
from from'
getByte getChar getContext getFinalChar
hasContext hash
isEmpty isInstanceOf
length lengthUTF8
match
orNull
replace replace' repeat
slice split splitAt splitAt'
splitLines splitSpaces
toBytes toCharacters toLowerCase toPath toString toTrace toTraceDeep toTraceShallow toUpperCase toUTF8characters trim;
inherit(trim) ltrim ltrim' rtrim rtrim' trim';
isPrimitive = true;
}