diff options
| author | Silvan Mosberger <silvan.mosberger@moduscreate.com> | 2025-04-02 18:21:52 +0200 |
|---|---|---|
| committer | Silvan Mosberger <silvan.mosberger@moduscreate.com> | 2025-04-02 18:30:54 +0200 |
| commit | e52d633a638c607533ececf4eb9653ec1f93e3d4 (patch) | |
| tree | f755439479cc7675d38a678a8f057563fb7cf453 /lib/strings.nix | |
| parent | llvmPackages_{12,13,14,15,16,17,18,19,20,git}.libcxx: nixfmt (diff) | |
| parent | Merge master into staging-next (diff) | |
| download | nixpkgs-e52d633a638c607533ececf4eb9653ec1f93e3d4.tar.gz | |
Merge remote-tracking branch 'upstream/staging-next' into staging
Diffstat (limited to 'lib/strings.nix')
| -rw-r--r-- | lib/strings.nix | 754 |
1 files changed, 414 insertions, 340 deletions
diff --git a/lib/strings.nix b/lib/strings.nix index 1452995fbce7..f8b01ec1ebc5 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -8,7 +8,7 @@ let inherit (lib.trivial) warnIf; -asciiTable = import ./ascii-table.nix; + asciiTable = import ./ascii-table.nix; in @@ -66,7 +66,6 @@ rec { /** Map a function over a list and concatenate the resulting strings. - # Inputs `f` @@ -98,7 +97,6 @@ rec { Like `concatMapStrings` except that the f functions also gets the position as a parameter. - # Inputs `f` @@ -129,7 +127,6 @@ rec { /** Place an element between each element of a list - # Inputs `separator` @@ -156,11 +153,16 @@ rec { ::: */ intersperse = - separator: - list: - if list == [] || length list == 1 - then list - else tail (lib.concatMap (x: [separator x]) list); + separator: list: + if list == [ ] || length list == 1 then + list + else + tail ( + lib.concatMap (x: [ + separator + x + ]) list + ); /** Concatenate a list of strings with a separator between each element @@ -197,7 +199,6 @@ rec { result with the specified separator interspersed between elements. - # Inputs `sep` @@ -227,15 +228,13 @@ rec { ::: */ concatMapStringsSep = - sep: - f: - list: concatStringsSep sep (map f list); + sep: f: list: + concatStringsSep sep (map f list); /** Same as `concatMapStringsSep`, but the mapping function additionally receives the position of its argument. - # Inputs `sep` @@ -265,9 +264,8 @@ rec { ::: */ concatImapStringsSep = - sep: - f: - list: concatStringsSep sep (lib.imap1 f list); + sep: f: list: + concatStringsSep sep (lib.imap1 f list); /** Like [`concatMapStringsSep`](#function-library-lib.strings.concatMapStringsSep) @@ -338,7 +336,6 @@ rec { Repeat a string `n` times, and concatenate the parts into a new string. - # Inputs `n` @@ -502,9 +499,7 @@ rec { ::: */ makeSearchPath = - subDir: - paths: - concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths)); + subDir: paths: concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths)); /** Construct a Unix-style search path by appending the given @@ -513,7 +508,6 @@ rec { If no output by the given name is found, fallback to `.out` and then to the default. - # Inputs `output` @@ -543,9 +537,8 @@ rec { ::: */ makeSearchPathOutput = - output: - subDir: - pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs); + output: subDir: pkgs: + makeSearchPath subDir (map (lib.getOutput output) pkgs); /** Construct a library search path (such as RPATH) containing the @@ -640,7 +633,6 @@ rec { /** Normalize path, removing extraneous /s - # Inputs `s` @@ -663,26 +655,22 @@ rec { ::: */ - normalizePath = s: - warnIf - (isPath s) + normalizePath = + s: + warnIf (isPath s) '' lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported. Path values are always normalised in Nix, so there's no need to call this function on them. This function also copies the path to the Nix store and returns the store path, the same as "''${path}" will, which may not be what you want. This behavior is deprecated and will throw an error in the future.'' ( - builtins.foldl' - (x: y: if y == "/" && hasSuffix "/" x then x else x+y) - "" - (stringToCharacters s) + builtins.foldl' (x: y: if y == "/" && hasSuffix "/" x then x else x + y) "" (stringToCharacters s) ); /** Depending on the boolean `cond', return either the given string or the empty string. Useful to concatenate against a bigger string. - # Inputs `cond` @@ -710,14 +698,11 @@ rec { ::: */ - optionalString = - cond: - string: if cond then string else ""; + optionalString = cond: string: if cond then string else ""; /** Determine whether a string has given prefix. - # Inputs `pref` @@ -746,12 +731,10 @@ rec { ::: */ hasPrefix = - pref: - str: + pref: str: # Before 23.05, paths would be copied to the store before converting them # to strings and comparing. This was surprising and confusing. - warnIf - (isPath pref) + warnIf (isPath pref) '' lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported. There is almost certainly a bug in the calling code, since this function always returns `false` in such a case. @@ -763,7 +746,6 @@ rec { /** Determine whether a string has given suffix. - # Inputs `suffix` @@ -792,30 +774,24 @@ rec { ::: */ hasSuffix = - suffix: - content: + suffix: content: let lenContent = stringLength content; lenSuffix = stringLength suffix; in # Before 23.05, paths would be copied to the store before converting them # to strings and comparing. This was surprising and confusing. - warnIf - (isPath suffix) + warnIf (isPath suffix) '' lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported. There is almost certainly a bug in the calling code, since this function always returns `false` in such a case. This function also copies the path to the Nix store, which may not be what you want. This behavior is deprecated and will throw an error in the future.'' - ( - lenContent >= lenSuffix - && substring (lenContent - lenSuffix) lenContent content == suffix - ); + (lenContent >= lenSuffix && substring (lenContent - lenSuffix) lenContent content == suffix); /** Determine whether a string contains the given infix - # Inputs `infix` @@ -847,11 +823,11 @@ rec { ::: */ - hasInfix = infix: content: + hasInfix = + infix: content: # Before 23.05, paths would be copied to the store before converting them # to strings and comparing. This was surprising and confusing. - warnIf - (isPath infix) + warnIf (isPath infix) '' lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported. There is almost certainly a bug in the calling code, since this function always returns `false` in such a case. @@ -868,7 +844,6 @@ rec { Also note that Nix treats strings as a list of bytes and thus doesn't handle unicode. - # Inputs `s` @@ -895,14 +870,12 @@ rec { ::: */ - stringToCharacters = s: - genList (p: substring p 1 s) (stringLength s); + stringToCharacters = s: genList (p: substring p 1 s) (stringLength s); /** Manipulate a string character by character and replace them by strings before concatenating the results. - # Inputs `f` @@ -932,14 +905,12 @@ rec { # Function to map over each individual character f: # Input string - s: concatStrings ( - map f (stringToCharacters s) - ); + s: + concatStrings (map f (stringToCharacters s)); /** Convert char to ascii value, must be in printable range - # Inputs `c` @@ -970,7 +941,6 @@ rec { Escape occurrence of the elements of `list` in `string` by prefixing it with a backslash. - # Inputs `list` @@ -1003,7 +973,6 @@ rec { converting to its ASCII value and prefixing it with \\x. Only works for printable ascii characters. - # Inputs `list` @@ -1029,7 +998,7 @@ rec { ::: */ - escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list); + escapeC = list: replaceStrings list (map (c: "\\x${toLower (lib.toHexString (charToInt c))}") list); /** Escape the `string` so it can be safely placed inside a URL @@ -1057,17 +1026,86 @@ rec { ::: */ - escapeURL = let - unreserved = [ "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "-" "_" "." "~" ]; - toEscape = builtins.removeAttrs asciiTable unreserved; - in - replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape); + escapeURL = + let + unreserved = [ + "A" + "B" + "C" + "D" + "E" + "F" + "G" + "H" + "I" + "J" + "K" + "L" + "M" + "N" + "O" + "P" + "Q" + "R" + "S" + "T" + "U" + "V" + "W" + "X" + "Y" + "Z" + "a" + "b" + "c" + "d" + "e" + "f" + "g" + "h" + "i" + "j" + "k" + "l" + "m" + "n" + "o" + "p" + "q" + "r" + "s" + "t" + "u" + "v" + "w" + "x" + "y" + "z" + "0" + "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "-" + "_" + "." + "~" + ]; + toEscape = builtins.removeAttrs asciiTable unreserved; + in + replaceStrings (builtins.attrNames toEscape) ( + lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape + ); /** Quote `string` to be used safely within the Bourne shell if it has any special characters. - # Inputs `string` @@ -1090,13 +1128,15 @@ rec { ::: */ - escapeShellArg = arg: + escapeShellArg = + arg: let string = toString arg; in - if match "[[:alnum:],._+:@%/-]+" string == null - then "'${replaceStrings ["'"] ["'\\''"] string}'" - else string; + if match "[[:alnum:],._+:@%/-]+" string == null then + "'${replaceStrings [ "'" ] [ "'\\''" ] string}'" + else + string; /** Quote all arguments that have special characters to be safely passed to the @@ -1129,7 +1169,6 @@ rec { /** Test whether the given `name` is a valid POSIX shell variable name. - # Inputs `name` @@ -1167,7 +1206,6 @@ rec { Strings are translated into POSIX sh-compatible code; lists and attribute sets assume a shell that understands Bash syntax (e.g. Bash or ZSH). - # Inputs `name` @@ -1195,25 +1233,23 @@ rec { ::: */ - toShellVar = name: value: + toShellVar = + name: value: lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" ( - if isAttrs value && ! isStringLike value then - "declare -A ${name}=(${ - concatStringsSep " " (lib.mapAttrsToList (n: v: - "[${escapeShellArg n}]=${escapeShellArg v}" - ) value) - })" - else if isList value then - "declare -a ${name}=(${escapeShellArgs value})" - else - "${name}=${escapeShellArg value}" + if isAttrs value && !isStringLike value then + "declare -A ${name}=(${ + concatStringsSep " " (lib.mapAttrsToList (n: v: "[${escapeShellArg n}]=${escapeShellArg v}") value) + })" + else if isList value then + "declare -a ${name}=(${escapeShellArgs value})" + else + "${name}=${escapeShellArg value}" ); /** Translate an attribute set `vars` into corresponding shell variable declarations using `toShellVar`. - # Inputs `vars` @@ -1270,7 +1306,7 @@ rec { ::: */ - escapeNixString = s: escape ["$"] (toJSON s); + escapeNixString = s: escape [ "$" ] (toJSON s); /** Turn a string `s` into an exact regular expression @@ -1302,7 +1338,6 @@ rec { /** Quotes a string `s` if it can't be used as an identifier directly. - # Inputs `s` @@ -1327,10 +1362,10 @@ rec { ::: */ - escapeNixIdentifier = s: + escapeNixIdentifier = + s: # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91 - if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null - then s else escapeNixString s; + if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null then s else escapeNixString s; /** Escapes a string `s` such that it is safe to include verbatim in an XML @@ -1358,9 +1393,10 @@ rec { ::: */ - escapeXML = builtins.replaceStrings - ["\"" "'" "<" ">" "&"] - [""" "'" "<" ">" "&"]; + escapeXML = + builtins.replaceStrings + [ "\"" "'" "<" ">" "&" ] + [ """ "'" "<" ">" "&" ]; # warning added 12-12-2022 replaceChars = lib.warn "lib.replaceChars is a deprecated alias of lib.replaceStrings." builtins.replaceStrings; @@ -1404,7 +1440,6 @@ rec { `s` : The string to convert to upper-case. - # Type ``` @@ -1449,7 +1484,8 @@ rec { ::: */ - toSentenceCase = str: + toSentenceCase = + str: lib.throwIfNot (isString str) "toSentenceCase does only accepts string values, but got ${typeOf str}" ( @@ -1473,7 +1509,6 @@ rec { attribute, the derivation will properly populate the inputDrvs and inputSrcs. - # Inputs `src` @@ -1544,12 +1579,14 @@ rec { ::: */ - splitString = sep: s: + splitString = + sep: s: let - splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s)); + splits = builtins.filter builtins.isString ( + builtins.split (escapeRegex (toString sep)) (toString s) + ); in - map (addContextFrom s) splits; - + map (addContextFrom s) splits; /** Return a string without the specified prefix, if the prefix matches. @@ -1582,30 +1619,29 @@ rec { ::: */ removePrefix = - prefix: - str: + prefix: str: # Before 23.05, paths would be copied to the store before converting them # to strings and comparing. This was surprising and confusing. - warnIf - (isPath prefix) + warnIf (isPath prefix) '' lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported. There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case. This function also copies the path to the Nix store, which may not be what you want. This behavior is deprecated and will throw an error in the future.'' - (let - preLen = stringLength prefix; - in - if substring 0 preLen str == prefix then - # -1 will take the string until the end - substring preLen (-1) str - else - str); + ( + let + preLen = stringLength prefix; + in + if substring 0 preLen str == prefix then + # -1 will take the string until the end + substring preLen (-1) str + else + str + ); /** Return a string without the specified suffix, if the suffix matches. - # Inputs `suffix` @@ -1634,30 +1670,29 @@ rec { ::: */ removeSuffix = - suffix: - str: + suffix: str: # Before 23.05, paths would be copied to the store before converting them # to strings and comparing. This was surprising and confusing. - warnIf - (isPath suffix) + warnIf (isPath suffix) '' lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported. There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case. This function also copies the path to the Nix store, which may not be what you want. This behavior is deprecated and will throw an error in the future.'' - (let - sufLen = stringLength suffix; - sLen = stringLength str; - in - if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then - substring 0 (sLen - sufLen) str - else - str); + ( + let + sufLen = stringLength suffix; + sLen = stringLength str; + in + if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then + substring 0 (sLen - sufLen) str + else + str + ); /** Return true if string `v1` denotes a version older than `v2`. - # Inputs `v1` @@ -1690,7 +1725,6 @@ rec { /** Return true if string v1 denotes a version equal to or newer than v2. - # Inputs `v1` @@ -1738,7 +1772,6 @@ rec { getName :: String | Derivation -> String ``` - # Examples :::{.example} ## `lib.strings.getName` usage example @@ -1752,19 +1785,17 @@ rec { ::: */ - getName = let - parse = drv: (parseDrvName drv).name; - in x: - if isString x - then parse x - else x.pname or (parse x.name); + getName = + let + parse = drv: (parseDrvName drv).name; + in + x: if isString x then parse x else x.pname or (parse x.name); /** This function takes an argument `x` that's either a derivation or a derivation's "name" attribute and extracts the version part from that argument. - # Inputs `x` @@ -1789,19 +1820,17 @@ rec { ::: */ - getVersion = let - parse = drv: (parseDrvName drv).version; - in x: - if isString x - then parse x - else x.version or (parse x.name); + getVersion = + let + parse = drv: (parseDrvName drv).version; + in + x: if isString x then parse x else x.version or (parse x.name); /** Extract name and version from a URL as shown in the examples. Separator `sep` is used to determine the end of the extension. - # Inputs `url` @@ -1829,12 +1858,15 @@ rec { ::: */ - nameFromURL = url: sep: + nameFromURL = + url: sep: let components = splitString "/" url; filename = lib.last components; name = head (splitString sep filename); - in assert name != filename; name; + in + assert name != filename; + name; /** Create a `"-D<feature>:<type>=<value>"` string that can be passed to typical @@ -1871,9 +1903,18 @@ rec { ::: */ - cmakeOptionType = let - types = [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" "LIST" ]; - in type: feature: value: + cmakeOptionType = + let + types = [ + "BOOL" + "FILEPATH" + "PATH" + "STRING" + "INTERNAL" + "LIST" + ]; + in + type: feature: value: assert (elem (toUpper type) types); assert (isString feature); assert (isString value); @@ -1883,7 +1924,6 @@ rec { Create a -D<condition>={TRUE,FALSE} string that can be passed to typical CMake invocations. - # Inputs `condition` @@ -1909,7 +1949,8 @@ rec { ::: */ - cmakeBool = condition: flag: + cmakeBool = + condition: flag: assert (lib.isString condition); assert (lib.isBool flag); cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag)); @@ -1919,7 +1960,6 @@ rec { CMake invocations. This is the most typical usage, so it deserves a special case. - # Inputs `feature` @@ -1928,7 +1968,6 @@ rec { `value` : The desired value - # Type ``` @@ -1946,7 +1985,8 @@ rec { ::: */ - cmakeFeature = feature: value: + cmakeFeature = + feature: value: assert (lib.isString feature); assert (lib.isString value); cmakeOptionType "string" feature value; @@ -1955,7 +1995,6 @@ rec { Create a -D<feature>=<value> string that can be passed to typical Meson invocations. - # Inputs `feature` @@ -1981,7 +2020,8 @@ rec { ::: */ - mesonOption = feature: value: + mesonOption = + feature: value: assert (lib.isString feature); assert (lib.isString value); "-D${feature}=${value}"; @@ -1990,7 +2030,6 @@ rec { Create a -D<condition>={true,false} string that can be passed to typical Meson invocations. - # Inputs `condition` @@ -2018,7 +2057,8 @@ rec { ::: */ - mesonBool = condition: flag: + mesonBool = + condition: flag: assert (lib.isString condition); assert (lib.isBool flag); mesonOption condition (lib.boolToString flag); @@ -2027,7 +2067,6 @@ rec { Create a -D<feature>={enabled,disabled} string that can be passed to typical Meson invocations. - # Inputs `feature` @@ -2055,7 +2094,8 @@ rec { ::: */ - mesonEnable = feature: flag: + mesonEnable = + feature: flag: assert (lib.isString feature); assert (lib.isBool flag); mesonOption feature (if flag then "enabled" else "disabled"); @@ -2064,7 +2104,6 @@ rec { Create an --{enable,disable}-<feature> string that can be passed to standard GNU Autoconf scripts. - # Inputs `flag` @@ -2092,7 +2131,8 @@ rec { ::: */ - enableFeature = flag: feature: + enableFeature = + flag: feature: assert lib.isBool flag; assert lib.isString feature; # e.g. passing openssl instead of "openssl" "--${if flag then "enable" else "disable"}-${feature}"; @@ -2101,7 +2141,6 @@ rec { Create an --{enable-<feature>=<value>,disable-<feature>} string that can be passed to standard GNU Autoconf scripts. - # Inputs `flag` @@ -2132,14 +2171,14 @@ rec { ::: */ - enableFeatureAs = flag: feature: value: + enableFeatureAs = + flag: feature: value: enableFeature flag feature + optionalString flag "=${value}"; /** Create an --{with,without}-<feature> string that can be passed to standard GNU Autoconf scripts. - # Inputs `flag` @@ -2148,7 +2187,6 @@ rec { `feature` : 2\. Function argument - # Type ``` @@ -2168,7 +2206,8 @@ rec { ::: */ - withFeature = flag: feature: + withFeature = + flag: feature: assert isString feature; # e.g. passing openssl instead of "openssl" "--${if flag then "with" else "without"}-${feature}"; @@ -2176,7 +2215,6 @@ rec { Create an --{with-<feature>=<value>,without-<feature>} string that can be passed to standard GNU Autoconf scripts. - # Inputs `flag` @@ -2194,7 +2232,6 @@ rec { withFeatureAs :: bool -> string -> string -> string ``` - # Examples :::{.example} ## `lib.strings.withFeatureAs` usage example @@ -2208,7 +2245,8 @@ rec { ::: */ - withFeatureAs = flag: feature: value: + withFeatureAs = + flag: feature: value: withFeature flag feature + optionalString flag "=${value}"; /** @@ -2218,7 +2256,6 @@ rec { This function will fail if the input string is longer than the requested length. - # Inputs `width` @@ -2247,21 +2284,19 @@ rec { ::: */ - fixedWidthString = width: filler: str: + fixedWidthString = + width: filler: str: let strw = lib.stringLength str; reqWidth = width - (lib.stringLength filler); in - assert lib.assertMsg (strw <= width) - "fixedWidthString: requested string length (${ - toString width}) must not be shorter than actual length (${ - toString strw})"; - if strw == width then str else filler + fixedWidthString reqWidth filler str; + assert lib.assertMsg (strw <= width) + "fixedWidthString: requested string length (${toString width}) must not be shorter than actual length (${toString strw})"; + if strw == width then str else filler + fixedWidthString reqWidth filler str; /** Format a number adding leading zeroes up to fixed width. - # Inputs `width` @@ -2293,13 +2328,11 @@ rec { Convert a float to a string, but emit a warning when precision is lost during the conversion - # Inputs `float` : 1\. Function argument - # Type ``` @@ -2320,11 +2353,13 @@ rec { ::: */ - floatToString = float: let - result = toString float; - precise = float == fromJSON result; - in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}" - result; + floatToString = + float: + let + result = toString float; + precise = float == fromJSON result; + in + lib.warnIf (!precise) "Imprecise conversion from float to string ${result}" result; /** Check whether a value `val` can be coerced to a string. @@ -2345,9 +2380,10 @@ rec { isCoercibleToString :: a -> bool ``` */ - isCoercibleToString = lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305) - "lib.strings.isCoercibleToString is deprecated in favor of either isStringLike or isConvertibleWithToString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles." - isConvertibleWithToString; + isCoercibleToString = + lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305) + "lib.strings.isCoercibleToString is deprecated in favor of either isStringLike or isConvertibleWithToString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles." + isConvertibleWithToString; /** Check whether a list or other value `x` can be passed to toString. @@ -2366,12 +2402,16 @@ rec { isConvertibleWithToString :: a -> bool ``` */ - isConvertibleWithToString = let - types = [ "null" "int" "float" "bool" ]; - in x: - isStringLike x || - elem (typeOf x) types || - (isList x && lib.all isConvertibleWithToString x); + isConvertibleWithToString = + let + types = [ + "null" + "int" + "float" + "bool" + ]; + in + x: isStringLike x || elem (typeOf x) types || (isList x && lib.all isConvertibleWithToString x); /** Check whether a value can be coerced to a string. @@ -2380,7 +2420,6 @@ rec { String-like values can be used without explicit conversion in string interpolations and in most functions that expect a string. - # Inputs `x` @@ -2392,16 +2431,11 @@ rec { isStringLike :: a -> bool ``` */ - isStringLike = x: - isString x || - isPath x || - x ? outPath || - x ? __toString; + isStringLike = x: isString x || isPath x || x ? outPath || x ? __toString; /** Check whether a value `x` is a store path. - # Inputs `x` @@ -2430,17 +2464,22 @@ rec { ::: */ - isStorePath = x: + isStorePath = + x: if isStringLike x then - let str = toString x; in + let + str = toString x; + in substring 0 1 str == "/" - && (dirOf str == storeDir + && ( + dirOf str == storeDir # Match contentâaddressed derivations, which _currently_ do not have a # store directory prefix. # This is a workaround for https://github.com/NixOS/nix/issues/12361 # which was needed during the experimental phase of ca-derivations and # should be removed once the issue has been resolved. - || builtins.match "/[0-9a-z]{52}" str != null) + || builtins.match "/[0-9a-z]{52}" str != null + ) else false; @@ -2494,7 +2533,7 @@ rec { strippedInput = matchStripInput str; # RegEx: Match a leading '0' then one or more digits. - isLeadingZero = matchLeadingZero (head strippedInput) == []; + isLeadingZero = matchLeadingZero (head strippedInput) == [ ]; # Attempt to parse input parsedInput = fromJSON (head strippedInput); @@ -2502,18 +2541,18 @@ rec { generalError = "toInt: Could not convert ${escapeNixString str} to int."; in - # Error on presence of non digit characters. - if strippedInput == null - then throw generalError - # Error on presence of leading zero/octal ambiguity. - else if isLeadingZero - then throw "toInt: Ambiguity in interpretation of ${escapeNixString str} between octal and zero padded integer." - # Error if parse function fails. - else if !isInt parsedInput - then throw generalError - # Return result. - else parsedInput; - + # Error on presence of non digit characters. + if strippedInput == null then + throw generalError + # Error on presence of leading zero/octal ambiguity. + else if isLeadingZero then + throw "toInt: Ambiguity in interpretation of ${escapeNixString str} between octal and zero padded integer." + # Error if parse function fails. + else if !isInt parsedInput then + throw generalError + # Return result. + else + parsedInput; /** Parse a string as a base 10 int. This supports parsing of zero-padded integers. @@ -2565,7 +2604,7 @@ rec { strippedInput = matchStripInput str; # RegEx: Match at least one '0'. - isZero = matchZero (head strippedInput) == []; + isZero = matchZero (head strippedInput) == [ ]; # Attempt to parse input parsedInput = fromJSON (head strippedInput); @@ -2573,17 +2612,18 @@ rec { generalError = "toIntBase10: Could not convert ${escapeNixString str} to int."; in - # Error on presence of non digit characters. - if strippedInput == null - then throw generalError - # In the special case zero-padded zero (00000), return early. - else if isZero - then 0 - # Error if parse function fails. - else if !isInt parsedInput - then throw generalError - # Return result. - else parsedInput; + # Error on presence of non digit characters. + if strippedInput == null then + throw generalError + # In the special case zero-padded zero (00000), return early. + else if isZero then + 0 + # Error if parse function fails. + else if !isInt parsedInput then + throw generalError + # Return result. + else + parsedInput; /** Read a list of paths from `file`, relative to the `rootPath`. @@ -2628,20 +2668,20 @@ rec { ::: */ - readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead." - (rootPath: file: - let - lines = lib.splitString "\n" (readFile file); - removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line)); - relativePaths = removeComments lines; - absolutePaths = map (path: rootPath + "/${path}") relativePaths; - in - absolutePaths); + readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead." ( + rootPath: file: + let + lines = lib.splitString "\n" (readFile file); + removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line)); + relativePaths = removeComments lines; + absolutePaths = map (path: rootPath + "/${path}") relativePaths; + in + absolutePaths + ); /** Read the contents of a file removing the trailing \n - # Inputs `file` @@ -2668,7 +2708,6 @@ rec { */ fileContents = file: removeSuffix "\n" (readFile file); - /** Creates a valid derivation name from a potentially invalid one. @@ -2699,29 +2738,31 @@ rec { ::: */ sanitizeDerivationName = - let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*"; - in - string: - # First detect the common case of already valid strings, to speed those up - if stringLength string <= 207 && okRegex string != null - then unsafeDiscardStringContext string - else lib.pipe string [ - # Get rid of string context. This is safe under the assumption that the - # resulting string is only used as a derivation name - unsafeDiscardStringContext - # Strip all leading "." - (x: elemAt (match "\\.*(.*)" x) 0) - # Split out all invalid characters - # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112 - # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125 - (split "[^[:alnum:]+._?=-]+") - # Replace invalid character ranges with a "-" - (concatMapStrings (s: if lib.isList s then "-" else s)) - # Limit to 211 characters (minus 4 chars for ".drv") - (x: substring (lib.max (stringLength x - 207) 0) (-1) x) - # If the result is empty, replace it with "unknown" - (x: if stringLength x == 0 then "unknown" else x) - ]; + let + okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*"; + in + string: + # First detect the common case of already valid strings, to speed those up + if stringLength string <= 207 && okRegex string != null then + unsafeDiscardStringContext string + else + lib.pipe string [ + # Get rid of string context. This is safe under the assumption that the + # resulting string is only used as a derivation name + unsafeDiscardStringContext + # Strip all leading "." + (x: elemAt (match "\\.*(.*)" x) 0) + # Split out all invalid characters + # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112 + # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125 + (split "[^[:alnum:]+._?=-]+") + # Replace invalid character ranges with a "-" + (concatMapStrings (s: if lib.isList s then "-" else s)) + # Limit to 211 characters (minus 4 chars for ".drv") + (x: substring (lib.max (stringLength x - 207) 0) (-1) x) + # If the result is empty, replace it with "unknown" + (x: if stringLength x == 0 then "unknown" else x) + ]; /** Computes the Levenshtein distance between two strings `a` and `b`. @@ -2729,7 +2770,6 @@ rec { Complexity O(n*m) where n and m are the lengths of the strings. Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742 - # Inputs `a` @@ -2759,29 +2799,29 @@ rec { ::: */ - levenshtein = a: b: let - # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1) - arr = lib.genList (i: - lib.genList (j: - dist i j - ) (stringLength b + 1) - ) (stringLength a + 1); - d = x: y: lib.elemAt (lib.elemAt arr x) y; - dist = i: j: - let c = if substring (i - 1) 1 a == substring (j - 1) 1 b - then 0 else 1; - in - if j == 0 then i - else if i == 0 then j - else lib.min - ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1)) - ( d (i - 1) (j - 1) + c ); - in d (stringLength a) (stringLength b); + levenshtein = + a: b: + let + # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1) + arr = lib.genList (i: lib.genList (j: dist i j) (stringLength b + 1)) (stringLength a + 1); + d = x: y: lib.elemAt (lib.elemAt arr x) y; + dist = + i: j: + let + c = if substring (i - 1) 1 a == substring (j - 1) 1 b then 0 else 1; + in + if j == 0 then + i + else if i == 0 then + j + else + lib.min (lib.min (d (i - 1) j + 1) (d i (j - 1) + 1)) (d (i - 1) (j - 1) + c); + in + d (stringLength a) (stringLength b); /** Returns the length of the prefix that appears in both strings `a` and `b`. - # Inputs `a` @@ -2796,16 +2836,24 @@ rec { commonPrefixLength :: string -> string -> int ``` */ - commonPrefixLength = a: b: + commonPrefixLength = + a: b: let m = lib.min (stringLength a) (stringLength b); - go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i; - in go 0; + go = + i: + if i >= m then + m + else if substring i 1 a == substring i 1 b then + go (i + 1) + else + i; + in + go 0; /** Returns the length of the suffix common to both strings `a` and `b`. - # Inputs `a` @@ -2820,11 +2868,20 @@ rec { commonSuffixLength :: string -> string -> int ``` */ - commonSuffixLength = a: b: + commonSuffixLength = + a: b: let m = lib.min (stringLength a) (stringLength b); - go = i: if i >= m then m else if substring (stringLength a - i - 1) 1 a == substring (stringLength b - i - 1) 1 b then go (i + 1) else i; - in go 0; + go = + i: + if i >= m then + m + else if substring (stringLength a - i - 1) 1 a == substring (stringLength b - i - 1) 1 b then + go (i + 1) + else + i; + in + go 0; /** Returns whether the levenshtein distance between two strings `a` and `b` is at most some value `k`. @@ -2867,64 +2924,81 @@ rec { ::: */ - levenshteinAtMost = let - infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1; + levenshteinAtMost = + let + infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1; - # This function takes two strings stripped by their common pre and suffix, - # and returns whether they differ by at most two by Levenshtein distance. - # Because of this stripping, if they do indeed differ by at most two edits, - # we know that those edits were (if at all) done at the start or the end, - # while the middle has to have stayed the same. This fact is used in the - # implementation. - infixDifferAtMost2 = x: y: - let - xlen = stringLength x; - ylen = stringLength y; - # This function is only called with |x| >= |y| and |x| - |y| <= 2, so - # diff is one of 0, 1 or 2 - diff = xlen - ylen; + # This function takes two strings stripped by their common pre and suffix, + # and returns whether they differ by at most two by Levenshtein distance. + # Because of this stripping, if they do indeed differ by at most two edits, + # we know that those edits were (if at all) done at the start or the end, + # while the middle has to have stayed the same. This fact is used in the + # implementation. + infixDifferAtMost2 = + x: y: + let + xlen = stringLength x; + ylen = stringLength y; + # This function is only called with |x| >= |y| and |x| - |y| <= 2, so + # diff is one of 0, 1 or 2 + diff = xlen - ylen; - # Infix of x and y, stripped by the left and right most character - xinfix = substring 1 (xlen - 2) x; - yinfix = substring 1 (ylen - 2) y; + # Infix of x and y, stripped by the left and right most character + xinfix = substring 1 (xlen - 2) x; + yinfix = substring 1 (ylen - 2) y; - # x and y but a character deleted at the left or right - xdelr = substring 0 (xlen - 1) x; - xdell = substring 1 (xlen - 1) x; - ydelr = substring 0 (ylen - 1) y; - ydell = substring 1 (ylen - 1) y; - in + # x and y but a character deleted at the left or right + xdelr = substring 0 (xlen - 1) x; + xdell = substring 1 (xlen - 1) x; + ydelr = substring 0 (ylen - 1) y; + ydell = substring 1 (ylen - 1) y; + in # A length difference of 2 can only be gotten with 2 delete edits, # which have to have happened at the start and end of x # Example: "abcdef" -> "bcde" - if diff == 2 then xinfix == y + if diff == 2 then + xinfix == y # A length difference of 1 can only be gotten with a deletion on the # right and a replacement on the left or vice versa. # Example: "abcdef" -> "bcdez" or "zbcde" - else if diff == 1 then xinfix == ydelr || xinfix == ydell + else if diff == 1 then + xinfix == ydelr || xinfix == ydell # No length difference can either happen through replacements on both # sides, or a deletion on the left and an insertion on the right or # vice versa # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde" - else xinfix == yinfix || xdelr == ydell || xdell == ydelr; + else + xinfix == yinfix || xdelr == ydell || xdell == ydelr; - in k: if k <= 0 then a: b: a == b else - let f = a: b: - let - alen = stringLength a; - blen = stringLength b; - prelen = commonPrefixLength a b; - suflen = commonSuffixLength a b; - presuflen = prelen + suflen; - ainfix = substring prelen (alen - presuflen) a; - binfix = substring prelen (blen - presuflen) b; - in - # Make a be the bigger string - if alen < blen then f b a - # If a has over k more characters than b, even with k deletes on a, b can't be reached - else if alen - blen > k then false - else if k == 1 then infixDifferAtMost1 ainfix binfix - else if k == 2 then infixDifferAtMost2 ainfix binfix - else levenshtein ainfix binfix <= k; - in f; + in + k: + if k <= 0 then + a: b: a == b + else + let + f = + a: b: + let + alen = stringLength a; + blen = stringLength b; + prelen = commonPrefixLength a b; + suflen = commonSuffixLength a b; + presuflen = prelen + suflen; + ainfix = substring prelen (alen - presuflen) a; + binfix = substring prelen (blen - presuflen) b; + in + # Make a be the bigger string + if alen < blen then + f b a + # If a has over k more characters than b, even with k deletes on a, b can't be reached + else if alen - blen > k then + false + else if k == 1 then + infixDifferAtMost1 ainfix binfix + else if k == 2 then + infixDifferAtMost2 ainfix binfix + else + levenshtein ainfix binfix <= k; + in + f; } |
