1 /* String manipulation functions. */
5 inherit (builtins) length;
7 inherit (lib.trivial) warnIf;
9 asciiTable = import ./ascii-table.nix;
39 unsafeDiscardStringContext
42 /* Concatenate a list of strings.
44 Type: concatStrings :: [string] -> string
47 concatStrings ["foo" "bar"]
50 concatStrings = builtins.concatStringsSep "";
52 /* Map a function over a list and concatenate the resulting strings.
54 Type: concatMapStrings :: (a -> string) -> [a] -> string
57 concatMapStrings (x: "a" + x) ["foo" "bar"]
60 concatMapStrings = f: list: concatStrings (map f list);
62 /* Like `concatMapStrings` except that the f functions also gets the
63 position as a parameter.
65 Type: concatImapStrings :: (int -> a -> string) -> [a] -> string
68 concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"]
71 concatImapStrings = f: list: concatStrings (lib.imap1 f list);
73 /* Place an element between each element of a list
75 Type: intersperse :: a -> [a] -> [a]
78 intersperse "/" ["usr" "local" "bin"]
79 => ["usr" "/" "local" "/" "bin"].
82 # Separator to add between elements
86 if list == [] || length list == 1
88 else tail (lib.concatMap (x: [separator x]) list);
90 /* Concatenate a list of strings with a separator between each element
92 Type: concatStringsSep :: string -> [string] -> string
95 concatStringsSep "/" ["usr" "local" "bin"]
98 concatStringsSep = builtins.concatStringsSep;
100 /* Maps a function over a list of strings and then concatenates the
101 result with the specified separator interspersed between
104 Type: concatMapStringsSep :: string -> (a -> string) -> [a] -> string
107 concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"]
110 concatMapStringsSep =
111 # Separator to add between elements
113 # Function to map over the list
115 # List of input strings
116 list: concatStringsSep sep (map f list);
118 /* Same as `concatMapStringsSep`, but the mapping function
119 additionally receives the position of its argument.
121 Type: concatIMapStringsSep :: string -> (int -> a -> string) -> [a] -> string
124 concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
127 concatImapStringsSep =
128 # Separator to add between elements
130 # Function that receives elements and their positions
132 # List of input strings
133 list: concatStringsSep sep (lib.imap1 f list);
135 /* Concatenate a list of strings, adding a newline at the end of each one.
136 Defined as `concatMapStrings (s: s + "\n")`.
138 Type: concatLines :: [string] -> string
141 concatLines [ "foo" "bar" ]
144 concatLines = concatMapStrings (s: s + "\n");
147 Replicate a string n times,
148 and concatenate the parts into a new string.
150 Type: replicate :: int -> string -> string
156 => "hellohellohellohellohello"
158 replicate = n: s: concatStrings (lib.lists.replicate n s);
160 /* Construct a Unix-style, colon-separated search path consisting of
161 the given `subDir` appended to each of the given paths.
163 Type: makeSearchPath :: string -> [string] -> string
166 makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
167 => "/root/bin:/usr/bin:/usr/local/bin"
168 makeSearchPath "bin" [""]
172 # Directory name to append
176 concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
178 /* Construct a Unix-style search path by appending the given
179 `subDir` to the specified `output` of each of the packages. If no
180 output by the given name is found, fallback to `.out` and then to
183 Type: string -> string -> [package] -> string
186 makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
187 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
189 makeSearchPathOutput =
190 # Package output to use
192 # Directory name to append
195 pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
197 /* Construct a library search path (such as RPATH) containing the
198 libraries for a set of packages
201 makeLibraryPath [ "/usr" "/usr/local" ]
202 => "/usr/lib:/usr/local/lib"
203 pkgs = import <nixpkgs> { }
204 makeLibraryPath [ pkgs.openssl pkgs.zlib ]
205 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
207 makeLibraryPath = makeSearchPathOutput "lib" "lib";
209 /* Construct a binary search path (such as $PATH) containing the
210 binaries for a set of packages.
213 makeBinPath ["/root" "/usr" "/usr/local"]
214 => "/root/bin:/usr/bin:/usr/local/bin"
216 makeBinPath = makeSearchPathOutput "bin" "bin";
218 /* Normalize path, removing extraneous /s
220 Type: normalizePath :: string -> string
223 normalizePath "/a//b///c/"
230 lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported.
231 Path values are always normalised in Nix, so there's no need to call this function on them.
232 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.
233 This behavior is deprecated and will throw an error in the future.''
236 (x: y: if y == "/" && hasSuffix "/" x then x else x+y)
238 (stringToCharacters s)
241 /* Depending on the boolean `cond', return either the given string
242 or the empty string. Useful to concatenate against a bigger string.
244 Type: optionalString :: bool -> string -> string
247 optionalString true "some-string"
249 optionalString false "some-string"
255 # String to return if condition is true
256 string: if cond then string else "";
258 /* Determine whether a string has given prefix.
260 Type: hasPrefix :: string -> string -> bool
263 hasPrefix "foo" "foobar"
265 hasPrefix "foo" "barfoo"
269 # Prefix to check for
273 # Before 23.05, paths would be copied to the store before converting them
274 # to strings and comparing. This was surprising and confusing.
278 lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported.
279 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
280 This function also copies the path to the Nix store, which may not be what you want.
281 This behavior is deprecated and will throw an error in the future.
282 You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.''
283 (substring 0 (stringLength pref) str == pref);
285 /* Determine whether a string has given suffix.
287 Type: hasSuffix :: string -> string -> bool
290 hasSuffix "foo" "foobar"
292 hasSuffix "foo" "barfoo"
296 # Suffix to check for
301 lenContent = stringLength content;
302 lenSuffix = stringLength suffix;
304 # Before 23.05, paths would be copied to the store before converting them
305 # to strings and comparing. This was surprising and confusing.
309 lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
310 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
311 This function also copies the path to the Nix store, which may not be what you want.
312 This behavior is deprecated and will throw an error in the future.''
314 lenContent >= lenSuffix
315 && substring (lenContent - lenSuffix) lenContent content == suffix
318 /* Determine whether a string contains the given infix
320 Type: hasInfix :: string -> string -> bool
329 hasInfix "foo" "abcd"
332 hasInfix = infix: content:
333 # Before 23.05, paths would be copied to the store before converting them
334 # to strings and comparing. This was surprising and confusing.
338 lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported.
339 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
340 This function also copies the path to the Nix store, which may not be what you want.
341 This behavior is deprecated and will throw an error in the future.''
342 (builtins.match ".*${escapeRegex infix}.*" "${content}" != null);
344 /* Convert a string to a list of characters (i.e. singleton strings).
345 This allows you to, e.g., map a function over each character. However,
346 note that this will likely be horribly inefficient; Nix is not a
347 general purpose programming language. Complex string manipulations
348 should, if appropriate, be done in a derivation.
349 Also note that Nix treats strings as a list of bytes and thus doesn't
352 Type: stringToCharacters :: string -> [string]
355 stringToCharacters ""
357 stringToCharacters "abc"
359 stringToCharacters "🦄"
360 => [ "�" "�" "�" "�" ]
362 stringToCharacters = s:
363 genList (p: substring p 1 s) (stringLength s);
365 /* Manipulate a string character by character and replace them by
366 strings before concatenating the results.
368 Type: stringAsChars :: (string -> string) -> string -> string
371 stringAsChars (x: if x == "a" then "i" else x) "nax"
375 # Function to map over each individual character
379 map f (stringToCharacters s)
382 /* Convert char to ascii value, must be in printable range
384 Type: charToInt :: string -> int
393 charToInt = c: builtins.getAttr c asciiTable;
395 /* Escape occurrence of the elements of `list` in `string` by
396 prefixing it with a backslash.
398 Type: escape :: [string] -> string -> string
401 escape ["(" ")"] "(foo)"
404 escape = list: replaceStrings list (map (c: "\\${c}") list);
406 /* Escape occurrence of the element of `list` in `string` by
407 converting to its ASCII value and prefixing it with \\x.
408 Only works for printable ascii characters.
410 Type: escapeC = [string] -> string -> string
413 escapeC [" "] "foo bar"
417 escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
419 /* Escape the string so it can be safely placed inside a URL
422 Type: escapeURL :: string -> string
425 escapeURL "foo/bar baz"
429 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" "-" "_" "." "~" ];
430 toEscape = builtins.removeAttrs asciiTable unreserved;
432 replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape);
434 /* Quote string to be used safely within the Bourne shell.
436 Type: escapeShellArg :: string -> string
439 escapeShellArg "esc'ape\nme"
440 => "'esc'\\''ape\nme'"
442 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
444 /* Quote all arguments to be safely passed to the Bourne shell.
446 Type: escapeShellArgs :: [string] -> string
449 escapeShellArgs ["one" "two three" "four'five"]
450 => "'one' 'two three' 'four'\\''five'"
452 escapeShellArgs = concatMapStringsSep " " escapeShellArg;
454 /* Test whether the given name is a valid POSIX shell variable name.
459 isValidPosixName "foo_bar000"
461 isValidPosixName "0-bad.jpg"
464 isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
466 /* Translate a Nix value into a shell variable declaration, with proper escaping.
468 The value can be a string (mapped to a regular variable), a list of strings
469 (mapped to a Bash-style array) or an attribute set of strings (mapped to a
470 Bash-style associative array). Note that "string" includes string-coercible
471 values like paths or derivations.
473 Strings are translated into POSIX sh-compatible code; lists and attribute sets
474 assume a shell that understands Bash syntax (e.g. Bash or ZSH).
476 Type: string -> (string | listOf string | attrsOf string) -> string
480 ${toShellVar "foo" "some string"}
481 [[ "$foo" == "some string" ]]
484 toShellVar = name: value:
485 lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
486 if isAttrs value && ! isStringLike value then
487 "declare -A ${name}=(${
488 concatStringsSep " " (lib.mapAttrsToList (n: v:
489 "[${escapeShellArg n}]=${escapeShellArg v}"
492 else if isList value then
493 "declare -a ${name}=(${escapeShellArgs value})"
495 "${name}=${escapeShellArg value}"
498 /* Translate an attribute set into corresponding shell variable declarations
501 Type: attrsOf (string | listOf string | attrsOf string) -> string
508 ${toShellVars { inherit foo bar; }}
509 [[ "$foo" == "$bar" ]]
512 toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
514 /* Turn a string into a Nix expression representing that string
516 Type: string -> string
519 escapeNixString "hello\${}\n"
520 => "\"hello\\\${}\\n\""
522 escapeNixString = s: escape ["$"] (toJSON s);
524 /* Turn a string into an exact regular expression
526 Type: string -> string
529 escapeRegex "[^a-z]*"
532 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
534 /* Quotes a string if it can't be used as an identifier directly.
536 Type: string -> string
539 escapeNixIdentifier "hello"
541 escapeNixIdentifier "0abc"
544 escapeNixIdentifier = s:
545 # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
546 if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
547 then s else escapeNixString s;
549 /* Escapes a string such that it is safe to include verbatim in an XML
552 Type: string -> string
555 escapeXML ''"test" 'test' < & >''
556 => ""test" 'test' < & >"
558 escapeXML = builtins.replaceStrings
559 ["\"" "'" "<" ">" "&"]
560 [""" "'" "<" ">" "&"];
562 # warning added 12-12-2022
563 replaceChars = lib.warn "lib.replaceChars is a deprecated alias of lib.replaceStrings." builtins.replaceStrings;
565 # Case conversion utilities.
566 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
567 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
569 /* Converts an ASCII string to lower-case.
571 Type: toLower :: string -> string
577 toLower = replaceStrings upperChars lowerChars;
579 /* Converts an ASCII string to upper-case.
581 Type: toUpper :: string -> string
587 toUpper = replaceStrings lowerChars upperChars;
589 /* Appends string context from another string. This is an implementation
590 detail of Nix and should be used carefully.
592 Strings in Nix carry an invisible `context` which is a list of strings
593 representing store paths. If the string is later used in a derivation
594 attribute, the derivation will properly populate the inputDrvs and
598 pkgs = import <nixpkgs> { };
599 addContextFrom pkgs.coreutils "bar"
602 addContextFrom = a: b: substring 0 0 a + b;
604 /* Cut a string with a separator and produces a list of strings which
605 were separated by this separator.
608 splitString "." "foo.bar.baz"
609 => [ "foo" "bar" "baz" ]
610 splitString "/" "/usr/local/bin"
611 => [ "" "usr" "local" "bin" ]
613 splitString = sep: s:
615 splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s));
617 map (addContextFrom s) splits;
619 /* Return a string without the specified prefix, if the prefix matches.
621 Type: string -> string -> string
624 removePrefix "foo." "foo.bar.baz"
626 removePrefix "xxx" "foo.bar.baz"
630 # Prefix to remove if it matches
634 # Before 23.05, paths would be copied to the store before converting them
635 # to strings and comparing. This was surprising and confusing.
639 lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported.
640 There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case.
641 This function also copies the path to the Nix store, which may not be what you want.
642 This behavior is deprecated and will throw an error in the future.''
644 preLen = stringLength prefix;
646 if substring 0 preLen str == prefix then
647 # -1 will take the string until the end
648 substring preLen (-1) str
652 /* Return a string without the specified suffix, if the suffix matches.
654 Type: string -> string -> string
657 removeSuffix "front" "homefront"
659 removeSuffix "xxx" "homefront"
663 # Suffix to remove if it matches
667 # Before 23.05, paths would be copied to the store before converting them
668 # to strings and comparing. This was surprising and confusing.
672 lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
673 There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case.
674 This function also copies the path to the Nix store, which may not be what you want.
675 This behavior is deprecated and will throw an error in the future.''
677 sufLen = stringLength suffix;
678 sLen = stringLength str;
680 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
681 substring 0 (sLen - sufLen) str
685 /* Return true if string v1 denotes a version older than v2.
688 versionOlder "1.1" "1.2"
690 versionOlder "1.1" "1.1"
693 versionOlder = v1: v2: compareVersions v2 v1 == 1;
695 /* Return true if string v1 denotes a version equal to or newer than v2.
698 versionAtLeast "1.1" "1.0"
700 versionAtLeast "1.1" "1.1"
702 versionAtLeast "1.1" "1.2"
705 versionAtLeast = v1: v2: !versionOlder v1 v2;
707 /* This function takes an argument that's either a derivation or a
708 derivation's "name" attribute and extracts the name part from that
712 getName "youtube-dl-2016.01.01"
714 getName pkgs.youtube-dl
718 parse = drv: (parseDrvName drv).name;
722 else x.pname or (parse x.name);
724 /* This function takes an argument that's either a derivation or a
725 derivation's "name" attribute and extracts the version part from that
729 getVersion "youtube-dl-2016.01.01"
731 getVersion pkgs.youtube-dl
735 parse = drv: (parseDrvName drv).version;
739 else x.version or (parse x.name);
741 /* Extract name with version from URL. Ask for separator which is
742 supposed to start extension.
745 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
747 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
750 nameFromURL = url: sep:
752 components = splitString "/" url;
753 filename = lib.last components;
754 name = head (splitString sep filename);
755 in assert name != filename; name;
757 /* Create a "-D<feature>:<type>=<value>" string that can be passed to typical
760 Type: cmakeOptionType :: string -> string -> string -> string
762 @param feature The feature to be set
763 @param type The type of the feature to be set, as described in
764 https://cmake.org/cmake/help/latest/command/set.html
765 the possible values (case insensitive) are:
766 BOOL FILEPATH PATH STRING INTERNAL
767 @param value The desired value
770 cmakeOptionType "string" "ENGINE" "sdl2"
771 => "-DENGINE:STRING=sdl2"
773 cmakeOptionType = let
774 types = [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" ];
775 in type: feature: value:
776 assert (elem (toUpper type) types);
777 assert (isString feature);
778 assert (isString value);
779 "-D${feature}:${toUpper type}=${value}";
781 /* Create a -D<condition>={TRUE,FALSE} string that can be passed to typical
784 Type: cmakeBool :: string -> bool -> string
786 @param condition The condition to be made true or false
787 @param flag The controlling flag of the condition
790 cmakeBool "ENABLE_STATIC_LIBS" false
791 => "-DENABLESTATIC_LIBS:BOOL=FALSE"
793 cmakeBool = condition: flag:
794 assert (lib.isString condition);
795 assert (lib.isBool flag);
796 cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag));
798 /* Create a -D<feature>:STRING=<value> string that can be passed to typical
800 This is the most typical usage, so it deserves a special case.
802 Type: cmakeFeature :: string -> string -> string
804 @param condition The condition to be made true or false
805 @param flag The controlling flag of the condition
808 cmakeFeature "MODULES" "badblock"
809 => "-DMODULES:STRING=badblock"
811 cmakeFeature = feature: value:
812 assert (lib.isString feature);
813 assert (lib.isString value);
814 cmakeOptionType "string" feature value;
816 /* Create a -D<feature>=<value> string that can be passed to typical Meson
819 Type: mesonOption :: string -> string -> string
821 @param feature The feature to be set
822 @param value The desired value
825 mesonOption "engine" "opengl"
828 mesonOption = feature: value:
829 assert (lib.isString feature);
830 assert (lib.isString value);
831 "-D${feature}=${value}";
833 /* Create a -D<condition>={true,false} string that can be passed to typical
836 Type: mesonBool :: string -> bool -> string
838 @param condition The condition to be made true or false
839 @param flag The controlling flag of the condition
842 mesonBool "hardened" true
844 mesonBool "static" false
847 mesonBool = condition: flag:
848 assert (lib.isString condition);
849 assert (lib.isBool flag);
850 mesonOption condition (lib.boolToString flag);
852 /* Create a -D<feature>={enabled,disabled} string that can be passed to
853 typical Meson invocations.
855 Type: mesonEnable :: string -> bool -> string
857 @param feature The feature to be enabled or disabled
858 @param flag The controlling flag
861 mesonEnable "docs" true
863 mesonEnable "savage" false
864 => "-Dsavage=disabled"
866 mesonEnable = feature: flag:
867 assert (lib.isString feature);
868 assert (lib.isBool flag);
869 mesonOption feature (if flag then "enabled" else "disabled");
871 /* Create an --{enable,disable}-<feature> string that can be passed to
872 standard GNU Autoconf scripts.
875 enableFeature true "shared"
877 enableFeature false "shared"
878 => "--disable-shared"
880 enableFeature = flag: feature:
881 assert lib.isBool flag;
882 assert lib.isString feature; # e.g. passing openssl instead of "openssl"
883 "--${if flag then "enable" else "disable"}-${feature}";
885 /* Create an --{enable-<feature>=<value>,disable-<feature>} string that can be passed to
886 standard GNU Autoconf scripts.
889 enableFeatureAs true "shared" "foo"
890 => "--enable-shared=foo"
891 enableFeatureAs false "shared" (throw "ignored")
892 => "--disable-shared"
894 enableFeatureAs = flag: feature: value:
895 enableFeature flag feature + optionalString flag "=${value}";
897 /* Create an --{with,without}-<feature> string that can be passed to
898 standard GNU Autoconf scripts.
901 withFeature true "shared"
903 withFeature false "shared"
904 => "--without-shared"
906 withFeature = flag: feature:
907 assert isString feature; # e.g. passing openssl instead of "openssl"
908 "--${if flag then "with" else "without"}-${feature}";
910 /* Create an --{with-<feature>=<value>,without-<feature>} string that can be passed to
911 standard GNU Autoconf scripts.
914 withFeatureAs true "shared" "foo"
915 => "--with-shared=foo"
916 withFeatureAs false "shared" (throw "ignored")
917 => "--without-shared"
919 withFeatureAs = flag: feature: value:
920 withFeature flag feature + optionalString flag "=${value}";
922 /* Create a fixed width string with additional prefix to match
925 This function will fail if the input string is longer than the
928 Type: fixedWidthString :: int -> string -> string -> string
931 fixedWidthString 5 "0" (toString 15)
934 fixedWidthString = width: filler: str:
936 strw = lib.stringLength str;
937 reqWidth = width - (lib.stringLength filler);
939 assert lib.assertMsg (strw <= width)
940 "fixedWidthString: requested string length (${
941 toString width}) must not be shorter than actual length (${
943 if strw == width then str else filler + fixedWidthString reqWidth filler str;
945 /* Format a number adding leading zeroes up to fixed width.
948 fixedWidthNumber 5 15
951 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
953 /* Convert a float to a string, but emit a warning when precision is lost
954 during the conversion
957 floatToString 0.000001
959 floatToString 0.0000001
960 => trace: warning: Imprecise conversion from float to string 0.000000
963 floatToString = float: let
964 result = toString float;
965 precise = float == fromJSON result;
966 in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}"
969 /* Soft-deprecated function. While the original implementation is available as
970 isConvertibleWithToString, consider using isStringLike instead, if suitable. */
971 isCoercibleToString = lib.warnIf (lib.isInOldestRelease 2305)
972 "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."
973 isConvertibleWithToString;
975 /* Check whether a list or other value can be passed to toString.
977 Many types of value are coercible to string this way, including int, float,
978 null, bool, list of similarly coercible values.
980 isConvertibleWithToString = let
981 types = [ "null" "int" "float" "bool" ];
984 elem (typeOf x) types ||
985 (isList x && lib.all isConvertibleWithToString x);
987 /* Check whether a value can be coerced to a string.
988 The value must be a string, path, or attribute set.
990 String-like values can be used without explicit conversion in
991 string interpolations and in most functions that expect a string.
999 /* Check whether a value is a store path.
1002 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
1004 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
1006 isStorePath pkgs.python
1008 isStorePath [] || isStorePath 42 || isStorePath {} || …
1012 if isStringLike x then
1013 let str = toString x; in
1014 substring 0 1 str == "/"
1015 && dirOf str == storeDir
1019 /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
1020 ambiguity between zero-padded and octal numbers. See toIntBase10.
1036 => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
1039 => error: floating point JSON numbers are not supported
1043 matchStripInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*";
1044 matchLeadingZero = match "0[[:digit:]]+";
1048 # RegEx: Match any leading whitespace, possibly a '-', one or more digits,
1049 # and finally match any trailing whitespace.
1050 strippedInput = matchStripInput str;
1052 # RegEx: Match a leading '0' then one or more digits.
1053 isLeadingZero = matchLeadingZero (head strippedInput) == [];
1055 # Attempt to parse input
1056 parsedInput = fromJSON (head strippedInput);
1058 generalError = "toInt: Could not convert ${escapeNixString str} to int.";
1061 # Error on presence of non digit characters.
1062 if strippedInput == null
1063 then throw generalError
1064 # Error on presence of leading zero/octal ambiguity.
1065 else if isLeadingZero
1066 then throw "toInt: Ambiguity in interpretation of ${escapeNixString str} between octal and zero padded integer."
1067 # Error if parse function fails.
1068 else if !isInt parsedInput
1069 then throw generalError
1074 /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
1092 => error: floating point JSON numbers are not supported
1096 matchStripInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*";
1097 matchZero = match "0+";
1101 # RegEx: Match any leading whitespace, then match any zero padding,
1102 # capture possibly a '-' followed by one or more digits,
1103 # and finally match any trailing whitespace.
1104 strippedInput = matchStripInput str;
1106 # RegEx: Match at least one '0'.
1107 isZero = matchZero (head strippedInput) == [];
1109 # Attempt to parse input
1110 parsedInput = fromJSON (head strippedInput);
1112 generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
1115 # Error on presence of non digit characters.
1116 if strippedInput == null
1117 then throw generalError
1118 # In the special case zero-padded zero (00000), return early.
1121 # Error if parse function fails.
1122 else if !isInt parsedInput
1123 then throw generalError
1127 /* Read a list of paths from `file`, relative to the `rootPath`.
1128 Lines beginning with `#` are treated as comments and ignored.
1129 Whitespace is significant.
1131 NOTE: This function is not performant and should be avoided.
1134 readPathsFromFile /prefix
1135 ./pkgs/development/libraries/qt-5/5.4/qtbase/series
1136 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
1137 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
1138 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
1139 "/prefix/nix-profiles-library-paths.patch"
1140 "/prefix/compose-search-path.patch" ]
1142 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead."
1145 lines = lib.splitString "\n" (readFile file);
1146 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
1147 relativePaths = removeComments lines;
1148 absolutePaths = map (path: rootPath + "/${path}") relativePaths;
1152 /* Read the contents of a file removing the trailing \n
1154 Type: fileContents :: path -> string
1157 $ echo "1.0" > ./version
1159 fileContents ./version
1162 fileContents = file: removeSuffix "\n" (readFile file);
1165 /* Creates a valid derivation name from a potentially invalid one.
1167 Type: sanitizeDerivationName :: String -> String
1170 sanitizeDerivationName "../hello.bar # foo"
1172 sanitizeDerivationName ""
1174 sanitizeDerivationName pkgs.hello
1175 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
1177 sanitizeDerivationName =
1178 let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
1181 # First detect the common case of already valid strings, to speed those up
1182 if stringLength string <= 207 && okRegex string != null
1183 then unsafeDiscardStringContext string
1184 else lib.pipe string [
1185 # Get rid of string context. This is safe under the assumption that the
1186 # resulting string is only used as a derivation name
1187 unsafeDiscardStringContext
1188 # Strip all leading "."
1189 (x: elemAt (match "\\.*(.*)" x) 0)
1190 # Split out all invalid characters
1191 # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
1192 # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
1193 (split "[^[:alnum:]+._?=-]+")
1194 # Replace invalid character ranges with a "-"
1195 (concatMapStrings (s: if lib.isList s then "-" else s))
1196 # Limit to 211 characters (minus 4 chars for ".drv")
1197 (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
1198 # If the result is empty, replace it with "unknown"
1199 (x: if stringLength x == 0 then "unknown" else x)
1202 /* Computes the Levenshtein distance between two strings.
1203 Complexity O(n*m) where n and m are the lengths of the strings.
1204 Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
1206 Type: levenshtein :: string -> string -> int
1209 levenshtein "foo" "foo"
1211 levenshtein "book" "hook"
1213 levenshtein "hello" "Heyo"
1216 levenshtein = a: b: let
1217 # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
1218 arr = lib.genList (i:
1221 ) (stringLength b + 1)
1222 ) (stringLength a + 1);
1223 d = x: y: lib.elemAt (lib.elemAt arr x) y;
1225 let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
1229 else if i == 0 then j
1231 ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1))
1232 ( d (i - 1) (j - 1) + c );
1233 in d (stringLength a) (stringLength b);
1235 /* Returns the length of the prefix common to both strings.
1237 commonPrefixLength = a: b:
1239 m = lib.min (stringLength a) (stringLength b);
1240 go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
1243 /* Returns the length of the suffix common to both strings.
1245 commonSuffixLength = a: b:
1247 m = lib.min (stringLength a) (stringLength b);
1248 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;
1251 /* Returns whether the levenshtein distance between two strings is at most some value
1252 Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
1254 Type: levenshteinAtMost :: int -> string -> string -> bool
1257 levenshteinAtMost 0 "foo" "foo"
1259 levenshteinAtMost 1 "foo" "boa"
1261 levenshteinAtMost 2 "foo" "boa"
1263 levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1265 levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1269 levenshteinAtMost = let
1270 infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1272 # This function takes two strings stripped by their common pre and suffix,
1273 # and returns whether they differ by at most two by Levenshtein distance.
1274 # Because of this stripping, if they do indeed differ by at most two edits,
1275 # we know that those edits were (if at all) done at the start or the end,
1276 # while the middle has to have stayed the same. This fact is used in the
1278 infixDifferAtMost2 = x: y:
1280 xlen = stringLength x;
1281 ylen = stringLength y;
1282 # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
1283 # diff is one of 0, 1 or 2
1286 # Infix of x and y, stripped by the left and right most character
1287 xinfix = substring 1 (xlen - 2) x;
1288 yinfix = substring 1 (ylen - 2) y;
1290 # x and y but a character deleted at the left or right
1291 xdelr = substring 0 (xlen - 1) x;
1292 xdell = substring 1 (xlen - 1) x;
1293 ydelr = substring 0 (ylen - 1) y;
1294 ydell = substring 1 (ylen - 1) y;
1296 # A length difference of 2 can only be gotten with 2 delete edits,
1297 # which have to have happened at the start and end of x
1298 # Example: "abcdef" -> "bcde"
1299 if diff == 2 then xinfix == y
1300 # A length difference of 1 can only be gotten with a deletion on the
1301 # right and a replacement on the left or vice versa.
1302 # Example: "abcdef" -> "bcdez" or "zbcde"
1303 else if diff == 1 then xinfix == ydelr || xinfix == ydell
1304 # No length difference can either happen through replacements on both
1305 # sides, or a deletion on the left and an insertion on the right or
1307 # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1308 else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1310 in k: if k <= 0 then a: b: a == b else
1313 alen = stringLength a;
1314 blen = stringLength b;
1315 prelen = commonPrefixLength a b;
1316 suflen = commonSuffixLength a b;
1317 presuflen = prelen + suflen;
1318 ainfix = substring prelen (alen - presuflen) a;
1319 binfix = substring prelen (blen - presuflen) b;
1321 # Make a be the bigger string
1322 if alen < blen then f b a
1323 # If a has over k more characters than b, even with k deletes on a, b can't be reached
1324 else if alen - blen > k then false
1325 else if k == 1 then infixDifferAtMost1 ainfix binfix
1326 else if k == 2 then infixDifferAtMost2 ainfix binfix
1327 else levenshtein ainfix binfix <= k;