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 or (separator: list:
99 lib.foldl' (x: y: x + y) "" (intersperse separator list));
101 /* Maps a function over a list of strings and then concatenates the
102 result with the specified separator interspersed between
105 Type: concatMapStringsSep :: string -> (a -> string) -> [a] -> string
108 concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"]
111 concatMapStringsSep =
112 # Separator to add between elements
114 # Function to map over the list
116 # List of input strings
117 list: concatStringsSep sep (map f list);
119 /* Same as `concatMapStringsSep`, but the mapping function
120 additionally receives the position of its argument.
122 Type: concatIMapStringsSep :: string -> (int -> a -> string) -> [a] -> string
125 concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
128 concatImapStringsSep =
129 # Separator to add between elements
131 # Function that receives elements and their positions
133 # List of input strings
134 list: concatStringsSep sep (lib.imap1 f list);
136 /* Concatenate a list of strings, adding a newline at the end of each one.
137 Defined as `concatMapStrings (s: s + "\n")`.
139 Type: concatLines :: [string] -> string
142 concatLines [ "foo" "bar" ]
145 concatLines = concatMapStrings (s: s + "\n");
148 Replicate a string n times,
149 and concatenate the parts into a new string.
151 Type: replicate :: int -> string -> string
157 => "hellohellohellohellohello"
159 replicate = n: s: concatStrings (lib.lists.replicate n s);
161 /* Construct a Unix-style, colon-separated search path consisting of
162 the given `subDir` appended to each of the given paths.
164 Type: makeSearchPath :: string -> [string] -> string
167 makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
168 => "/root/bin:/usr/bin:/usr/local/bin"
169 makeSearchPath "bin" [""]
173 # Directory name to append
177 concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
179 /* Construct a Unix-style search path by appending the given
180 `subDir` to the specified `output` of each of the packages. If no
181 output by the given name is found, fallback to `.out` and then to
184 Type: string -> string -> [package] -> string
187 makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
188 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
190 makeSearchPathOutput =
191 # Package output to use
193 # Directory name to append
196 pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
198 /* Construct a library search path (such as RPATH) containing the
199 libraries for a set of packages
202 makeLibraryPath [ "/usr" "/usr/local" ]
203 => "/usr/lib:/usr/local/lib"
204 pkgs = import <nixpkgs> { }
205 makeLibraryPath [ pkgs.openssl pkgs.zlib ]
206 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
208 makeLibraryPath = makeSearchPathOutput "lib" "lib";
210 /* Construct a binary search path (such as $PATH) containing the
211 binaries for a set of packages.
214 makeBinPath ["/root" "/usr" "/usr/local"]
215 => "/root/bin:/usr/bin:/usr/local/bin"
217 makeBinPath = makeSearchPathOutput "bin" "bin";
219 /* Normalize path, removing extraneous /s
221 Type: normalizePath :: string -> string
224 normalizePath "/a//b///c/"
231 lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported.
232 Path values are always normalised in Nix, so there's no need to call this function on them.
233 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.
234 This behavior is deprecated and will throw an error in the future.''
237 (x: y: if y == "/" && hasSuffix "/" x then x else x+y)
239 (stringToCharacters s)
242 /* Depending on the boolean `cond', return either the given string
243 or the empty string. Useful to concatenate against a bigger string.
245 Type: optionalString :: bool -> string -> string
248 optionalString true "some-string"
250 optionalString false "some-string"
256 # String to return if condition is true
257 string: if cond then string else "";
259 /* Determine whether a string has given prefix.
261 Type: hasPrefix :: string -> string -> bool
264 hasPrefix "foo" "foobar"
266 hasPrefix "foo" "barfoo"
270 # Prefix to check for
274 # Before 23.05, paths would be copied to the store before converting them
275 # to strings and comparing. This was surprising and confusing.
279 lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported.
280 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
281 This function also copies the path to the Nix store, which may not be what you want.
282 This behavior is deprecated and will throw an error in the future.
283 You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.''
284 (substring 0 (stringLength pref) str == pref);
286 /* Determine whether a string has given suffix.
288 Type: hasSuffix :: string -> string -> bool
291 hasSuffix "foo" "foobar"
293 hasSuffix "foo" "barfoo"
297 # Suffix to check for
302 lenContent = stringLength content;
303 lenSuffix = stringLength suffix;
305 # Before 23.05, paths would be copied to the store before converting them
306 # to strings and comparing. This was surprising and confusing.
310 lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
311 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
312 This function also copies the path to the Nix store, which may not be what you want.
313 This behavior is deprecated and will throw an error in the future.''
315 lenContent >= lenSuffix
316 && substring (lenContent - lenSuffix) lenContent content == suffix
319 /* Determine whether a string contains the given infix
321 Type: hasInfix :: string -> string -> bool
330 hasInfix "foo" "abcd"
333 hasInfix = infix: content:
334 # Before 23.05, paths would be copied to the store before converting them
335 # to strings and comparing. This was surprising and confusing.
339 lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported.
340 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
341 This function also copies the path to the Nix store, which may not be what you want.
342 This behavior is deprecated and will throw an error in the future.''
343 (builtins.match ".*${escapeRegex infix}.*" "${content}" != null);
345 /* Convert a string to a list of characters (i.e. singleton strings).
346 This allows you to, e.g., map a function over each character. However,
347 note that this will likely be horribly inefficient; Nix is not a
348 general purpose programming language. Complex string manipulations
349 should, if appropriate, be done in a derivation.
350 Also note that Nix treats strings as a list of bytes and thus doesn't
353 Type: stringToCharacters :: string -> [string]
356 stringToCharacters ""
358 stringToCharacters "abc"
360 stringToCharacters "🦄"
361 => [ "�" "�" "�" "�" ]
363 stringToCharacters = s:
364 genList (p: substring p 1 s) (stringLength s);
366 /* Manipulate a string character by character and replace them by
367 strings before concatenating the results.
369 Type: stringAsChars :: (string -> string) -> string -> string
372 stringAsChars (x: if x == "a" then "i" else x) "nax"
376 # Function to map over each individual character
380 map f (stringToCharacters s)
383 /* Convert char to ascii value, must be in printable range
385 Type: charToInt :: string -> int
394 charToInt = c: builtins.getAttr c asciiTable;
396 /* Escape occurrence of the elements of `list` in `string` by
397 prefixing it with a backslash.
399 Type: escape :: [string] -> string -> string
402 escape ["(" ")"] "(foo)"
405 escape = list: replaceStrings list (map (c: "\\${c}") list);
407 /* Escape occurrence of the element of `list` in `string` by
408 converting to its ASCII value and prefixing it with \\x.
409 Only works for printable ascii characters.
411 Type: escapeC = [string] -> string -> string
414 escapeC [" "] "foo bar"
418 escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
420 /* Escape the string so it can be safely placed inside a URL
423 Type: escapeURL :: string -> string
426 escapeURL "foo/bar baz"
430 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" "-" "_" "." "~" ];
431 toEscape = builtins.removeAttrs asciiTable unreserved;
433 replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape);
435 /* Quote string to be used safely within the Bourne shell.
437 Type: escapeShellArg :: string -> string
440 escapeShellArg "esc'ape\nme"
441 => "'esc'\\''ape\nme'"
443 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
445 /* Quote all arguments to be safely passed to the Bourne shell.
447 Type: escapeShellArgs :: [string] -> string
450 escapeShellArgs ["one" "two three" "four'five"]
451 => "'one' 'two three' 'four'\\''five'"
453 escapeShellArgs = concatMapStringsSep " " escapeShellArg;
455 /* Test whether the given name is a valid POSIX shell variable name.
460 isValidPosixName "foo_bar000"
462 isValidPosixName "0-bad.jpg"
465 isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
467 /* Translate a Nix value into a shell variable declaration, with proper escaping.
469 The value can be a string (mapped to a regular variable), a list of strings
470 (mapped to a Bash-style array) or an attribute set of strings (mapped to a
471 Bash-style associative array). Note that "string" includes string-coercible
472 values like paths or derivations.
474 Strings are translated into POSIX sh-compatible code; lists and attribute sets
475 assume a shell that understands Bash syntax (e.g. Bash or ZSH).
477 Type: string -> (string | listOf string | attrsOf string) -> string
481 ${toShellVar "foo" "some string"}
482 [[ "$foo" == "some string" ]]
485 toShellVar = name: value:
486 lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
487 if isAttrs value && ! isStringLike value then
488 "declare -A ${name}=(${
489 concatStringsSep " " (lib.mapAttrsToList (n: v:
490 "[${escapeShellArg n}]=${escapeShellArg v}"
493 else if isList value then
494 "declare -a ${name}=(${escapeShellArgs value})"
496 "${name}=${escapeShellArg value}"
499 /* Translate an attribute set into corresponding shell variable declarations
502 Type: attrsOf (string | listOf string | attrsOf string) -> string
509 ${toShellVars { inherit foo bar; }}
510 [[ "$foo" == "$bar" ]]
513 toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
515 /* Turn a string into a Nix expression representing that string
517 Type: string -> string
520 escapeNixString "hello\${}\n"
521 => "\"hello\\\${}\\n\""
523 escapeNixString = s: escape ["$"] (toJSON s);
525 /* Turn a string into an exact regular expression
527 Type: string -> string
530 escapeRegex "[^a-z]*"
533 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
535 /* Quotes a string if it can't be used as an identifier directly.
537 Type: string -> string
540 escapeNixIdentifier "hello"
542 escapeNixIdentifier "0abc"
545 escapeNixIdentifier = s:
546 # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
547 if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
548 then s else escapeNixString s;
550 /* Escapes a string such that it is safe to include verbatim in an XML
553 Type: string -> string
556 escapeXML ''"test" 'test' < & >''
557 => ""test" 'test' < & >"
559 escapeXML = builtins.replaceStrings
560 ["\"" "'" "<" ">" "&"]
561 [""" "'" "<" ">" "&"];
563 # warning added 12-12-2022
564 replaceChars = lib.warn "replaceChars is a deprecated alias of replaceStrings, replace usages of it with replaceStrings." builtins.replaceStrings;
566 # Case conversion utilities.
567 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
568 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
570 /* Converts an ASCII string to lower-case.
572 Type: toLower :: string -> string
578 toLower = replaceStrings upperChars lowerChars;
580 /* Converts an ASCII string to upper-case.
582 Type: toUpper :: string -> string
588 toUpper = replaceStrings lowerChars upperChars;
590 /* Appends string context from another string. This is an implementation
591 detail of Nix and should be used carefully.
593 Strings in Nix carry an invisible `context` which is a list of strings
594 representing store paths. If the string is later used in a derivation
595 attribute, the derivation will properly populate the inputDrvs and
599 pkgs = import <nixpkgs> { };
600 addContextFrom pkgs.coreutils "bar"
603 addContextFrom = a: b: substring 0 0 a + b;
605 /* Cut a string with a separator and produces a list of strings which
606 were separated by this separator.
609 splitString "." "foo.bar.baz"
610 => [ "foo" "bar" "baz" ]
611 splitString "/" "/usr/local/bin"
612 => [ "" "usr" "local" "bin" ]
614 splitString = sep: s:
616 splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s));
618 map (addContextFrom s) splits;
620 /* Return a string without the specified prefix, if the prefix matches.
622 Type: string -> string -> string
625 removePrefix "foo." "foo.bar.baz"
627 removePrefix "xxx" "foo.bar.baz"
631 # Prefix to remove if it matches
635 # Before 23.05, paths would be copied to the store before converting them
636 # to strings and comparing. This was surprising and confusing.
640 lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported.
641 There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case.
642 This function also copies the path to the Nix store, which may not be what you want.
643 This behavior is deprecated and will throw an error in the future.''
645 preLen = stringLength prefix;
647 if substring 0 preLen str == prefix then
648 # -1 will take the string until the end
649 substring preLen (-1) str
653 /* Return a string without the specified suffix, if the suffix matches.
655 Type: string -> string -> string
658 removeSuffix "front" "homefront"
660 removeSuffix "xxx" "homefront"
664 # Suffix to remove if it matches
668 # Before 23.05, paths would be copied to the store before converting them
669 # to strings and comparing. This was surprising and confusing.
673 lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
674 There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case.
675 This function also copies the path to the Nix store, which may not be what you want.
676 This behavior is deprecated and will throw an error in the future.''
678 sufLen = stringLength suffix;
679 sLen = stringLength str;
681 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
682 substring 0 (sLen - sufLen) str
686 /* Return true if string v1 denotes a version older than v2.
689 versionOlder "1.1" "1.2"
691 versionOlder "1.1" "1.1"
694 versionOlder = v1: v2: compareVersions v2 v1 == 1;
696 /* Return true if string v1 denotes a version equal to or newer than v2.
699 versionAtLeast "1.1" "1.0"
701 versionAtLeast "1.1" "1.1"
703 versionAtLeast "1.1" "1.2"
706 versionAtLeast = v1: v2: !versionOlder v1 v2;
708 /* This function takes an argument that's either a derivation or a
709 derivation's "name" attribute and extracts the name part from that
713 getName "youtube-dl-2016.01.01"
715 getName pkgs.youtube-dl
720 parse = drv: (parseDrvName drv).name;
723 else x.pname or (parse x.name);
725 /* This function takes an argument that's either a derivation or a
726 derivation's "name" attribute and extracts the version part from that
730 getVersion "youtube-dl-2016.01.01"
732 getVersion pkgs.youtube-dl
737 parse = drv: (parseDrvName drv).version;
740 else x.version or (parse x.name);
742 /* Extract name with version from URL. Ask for separator which is
743 supposed to start extension.
746 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
748 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
751 nameFromURL = url: sep:
753 components = splitString "/" url;
754 filename = lib.last components;
755 name = head (splitString sep filename);
756 in assert name != filename; name;
758 /* Create a "-D<feature>:<type>=<value>" string that can be passed to typical
761 Type: cmakeOptionType :: string -> string -> string -> string
763 @param feature The feature to be set
764 @param type The type of the feature to be set, as described in
765 https://cmake.org/cmake/help/latest/command/set.html
766 the possible values (case insensitive) are:
767 BOOL FILEPATH PATH STRING INTERNAL
768 @param value The desired value
771 cmakeOptionType "string" "ENGINE" "sdl2"
772 => "-DENGINE:STRING=sdl2"
774 cmakeOptionType = type: feature: value:
775 assert (lib.elem (lib.toUpper type)
776 [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" ]);
777 assert (lib.isString feature);
778 assert (lib.isString value);
779 "-D${feature}:${lib.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 = x:
982 elem (typeOf x) [ "null" "int" "float" "bool" ] ||
983 (isList x && lib.all isConvertibleWithToString x);
985 /* Check whether a value can be coerced to a string.
986 The value must be a string, path, or attribute set.
988 String-like values can be used without explicit conversion in
989 string interpolations and in most functions that expect a string.
997 /* Check whether a value is a store path.
1000 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
1002 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
1004 isStorePath pkgs.python
1006 isStorePath [] || isStorePath 42 || isStorePath {} || …
1010 if isStringLike x then
1011 let str = toString x; in
1012 substring 0 1 str == "/"
1013 && dirOf str == storeDir
1017 /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
1018 ambiguity between zero-padded and octal numbers. See toIntBase10.
1034 => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
1037 => error: floating point JSON numbers are not supported
1041 # RegEx: Match any leading whitespace, possibly a '-', one or more digits,
1042 # and finally match any trailing whitespace.
1043 strippedInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*" str;
1045 # RegEx: Match a leading '0' then one or more digits.
1046 isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == [];
1048 # Attempt to parse input
1049 parsedInput = fromJSON (head strippedInput);
1051 generalError = "toInt: Could not convert ${escapeNixString str} to int.";
1053 octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}"
1054 + " between octal and zero padded integer.";
1057 # Error on presence of non digit characters.
1058 if strippedInput == null
1059 then throw generalError
1060 # Error on presence of leading zero/octal ambiguity.
1061 else if isLeadingZero
1062 then throw octalAmbigError
1063 # Error if parse function fails.
1064 else if !isInt parsedInput
1065 then throw generalError
1070 /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
1088 => error: floating point JSON numbers are not supported
1092 # RegEx: Match any leading whitespace, then match any zero padding,
1093 # capture possibly a '-' followed by one or more digits,
1094 # and finally match any trailing whitespace.
1095 strippedInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*" str;
1097 # RegEx: Match at least one '0'.
1098 isZero = match "0+" (head strippedInput) == [];
1100 # Attempt to parse input
1101 parsedInput = fromJSON (head strippedInput);
1103 generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
1106 # Error on presence of non digit characters.
1107 if strippedInput == null
1108 then throw generalError
1109 # In the special case zero-padded zero (00000), return early.
1112 # Error if parse function fails.
1113 else if !isInt parsedInput
1114 then throw generalError
1118 /* Read a list of paths from `file`, relative to the `rootPath`.
1119 Lines beginning with `#` are treated as comments and ignored.
1120 Whitespace is significant.
1122 NOTE: This function is not performant and should be avoided.
1125 readPathsFromFile /prefix
1126 ./pkgs/development/libraries/qt-5/5.4/qtbase/series
1127 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
1128 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
1129 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
1130 "/prefix/nix-profiles-library-paths.patch"
1131 "/prefix/compose-search-path.patch" ]
1133 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead"
1136 lines = lib.splitString "\n" (readFile file);
1137 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
1138 relativePaths = removeComments lines;
1139 absolutePaths = map (path: rootPath + "/${path}") relativePaths;
1143 /* Read the contents of a file removing the trailing \n
1145 Type: fileContents :: path -> string
1148 $ echo "1.0" > ./version
1150 fileContents ./version
1153 fileContents = file: removeSuffix "\n" (readFile file);
1156 /* Creates a valid derivation name from a potentially invalid one.
1158 Type: sanitizeDerivationName :: String -> String
1161 sanitizeDerivationName "../hello.bar # foo"
1163 sanitizeDerivationName ""
1165 sanitizeDerivationName pkgs.hello
1166 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
1168 sanitizeDerivationName =
1169 let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
1172 # First detect the common case of already valid strings, to speed those up
1173 if stringLength string <= 207 && okRegex string != null
1174 then unsafeDiscardStringContext string
1175 else lib.pipe string [
1176 # Get rid of string context. This is safe under the assumption that the
1177 # resulting string is only used as a derivation name
1178 unsafeDiscardStringContext
1179 # Strip all leading "."
1180 (x: elemAt (match "\\.*(.*)" x) 0)
1181 # Split out all invalid characters
1182 # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
1183 # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
1184 (split "[^[:alnum:]+._?=-]+")
1185 # Replace invalid character ranges with a "-"
1186 (concatMapStrings (s: if lib.isList s then "-" else s))
1187 # Limit to 211 characters (minus 4 chars for ".drv")
1188 (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
1189 # If the result is empty, replace it with "unknown"
1190 (x: if stringLength x == 0 then "unknown" else x)
1193 /* Computes the Levenshtein distance between two strings.
1194 Complexity O(n*m) where n and m are the lengths of the strings.
1195 Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
1197 Type: levenshtein :: string -> string -> int
1200 levenshtein "foo" "foo"
1202 levenshtein "book" "hook"
1204 levenshtein "hello" "Heyo"
1207 levenshtein = a: b: let
1208 # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
1209 arr = lib.genList (i:
1212 ) (stringLength b + 1)
1213 ) (stringLength a + 1);
1214 d = x: y: lib.elemAt (lib.elemAt arr x) y;
1216 let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
1220 else if i == 0 then j
1222 ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1))
1223 ( d (i - 1) (j - 1) + c );
1224 in d (stringLength a) (stringLength b);
1226 /* Returns the length of the prefix common to both strings.
1228 commonPrefixLength = a: b:
1230 m = lib.min (stringLength a) (stringLength b);
1231 go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
1234 /* Returns the length of the suffix common to both strings.
1236 commonSuffixLength = a: b:
1238 m = lib.min (stringLength a) (stringLength b);
1239 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;
1242 /* Returns whether the levenshtein distance between two strings is at most some value
1243 Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
1245 Type: levenshteinAtMost :: int -> string -> string -> bool
1248 levenshteinAtMost 0 "foo" "foo"
1250 levenshteinAtMost 1 "foo" "boa"
1252 levenshteinAtMost 2 "foo" "boa"
1254 levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1256 levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1260 levenshteinAtMost = let
1261 infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1263 # This function takes two strings stripped by their common pre and suffix,
1264 # and returns whether they differ by at most two by Levenshtein distance.
1265 # Because of this stripping, if they do indeed differ by at most two edits,
1266 # we know that those edits were (if at all) done at the start or the end,
1267 # while the middle has to have stayed the same. This fact is used in the
1269 infixDifferAtMost2 = x: y:
1271 xlen = stringLength x;
1272 ylen = stringLength y;
1273 # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
1274 # diff is one of 0, 1 or 2
1277 # Infix of x and y, stripped by the left and right most character
1278 xinfix = substring 1 (xlen - 2) x;
1279 yinfix = substring 1 (ylen - 2) y;
1281 # x and y but a character deleted at the left or right
1282 xdelr = substring 0 (xlen - 1) x;
1283 xdell = substring 1 (xlen - 1) x;
1284 ydelr = substring 0 (ylen - 1) y;
1285 ydell = substring 1 (ylen - 1) y;
1287 # A length difference of 2 can only be gotten with 2 delete edits,
1288 # which have to have happened at the start and end of x
1289 # Example: "abcdef" -> "bcde"
1290 if diff == 2 then xinfix == y
1291 # A length difference of 1 can only be gotten with a deletion on the
1292 # right and a replacement on the left or vice versa.
1293 # Example: "abcdef" -> "bcdez" or "zbcde"
1294 else if diff == 1 then xinfix == ydelr || xinfix == ydell
1295 # No length difference can either happen through replacements on both
1296 # sides, or a deletion on the left and an insertion on the right or
1298 # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1299 else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1301 in k: if k <= 0 then a: b: a == b else
1304 alen = stringLength a;
1305 blen = stringLength b;
1306 prelen = commonPrefixLength a b;
1307 suflen = commonSuffixLength a b;
1308 presuflen = prelen + suflen;
1309 ainfix = substring prelen (alen - presuflen) a;
1310 binfix = substring prelen (blen - presuflen) b;
1312 # Make a be the bigger string
1313 if alen < blen then f b a
1314 # If a has over k more characters than b, even with k deletes on a, b can't be reached
1315 else if alen - blen > k then false
1316 else if k == 1 then infixDifferAtMost1 ainfix binfix
1317 else if k == 2 then infixDifferAtMost2 ainfix binfix
1318 else levenshtein ainfix binfix <= k;