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 an include search path (such as C_INCLUDE_PATH) containing the
210 header files for a set of packages or paths.
213 makeIncludePath [ "/usr" "/usr/local" ]
214 => "/usr/include:/usr/local/include"
215 pkgs = import <nixpkgs> { }
216 makeIncludePath [ pkgs.openssl pkgs.zlib ]
217 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/include:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8-dev/include"
219 makeIncludePath = makeSearchPathOutput "dev" "include";
221 /* Construct a binary search path (such as $PATH) containing the
222 binaries for a set of packages.
225 makeBinPath ["/root" "/usr" "/usr/local"]
226 => "/root/bin:/usr/bin:/usr/local/bin"
228 makeBinPath = makeSearchPathOutput "bin" "bin";
230 /* Normalize path, removing extraneous /s
232 Type: normalizePath :: string -> string
235 normalizePath "/a//b///c/"
242 lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported.
243 Path values are always normalised in Nix, so there's no need to call this function on them.
244 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.
245 This behavior is deprecated and will throw an error in the future.''
248 (x: y: if y == "/" && hasSuffix "/" x then x else x+y)
250 (stringToCharacters s)
253 /* Depending on the boolean `cond', return either the given string
254 or the empty string. Useful to concatenate against a bigger string.
256 Type: optionalString :: bool -> string -> string
259 optionalString true "some-string"
261 optionalString false "some-string"
267 # String to return if condition is true
268 string: if cond then string else "";
270 /* Determine whether a string has given prefix.
272 Type: hasPrefix :: string -> string -> bool
275 hasPrefix "foo" "foobar"
277 hasPrefix "foo" "barfoo"
281 # Prefix to check for
285 # Before 23.05, paths would be copied to the store before converting them
286 # to strings and comparing. This was surprising and confusing.
290 lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported.
291 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
292 This function also copies the path to the Nix store, which may not be what you want.
293 This behavior is deprecated and will throw an error in the future.
294 You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.''
295 (substring 0 (stringLength pref) str == pref);
297 /* Determine whether a string has given suffix.
299 Type: hasSuffix :: string -> string -> bool
302 hasSuffix "foo" "foobar"
304 hasSuffix "foo" "barfoo"
308 # Suffix to check for
313 lenContent = stringLength content;
314 lenSuffix = stringLength suffix;
316 # Before 23.05, paths would be copied to the store before converting them
317 # to strings and comparing. This was surprising and confusing.
321 lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
322 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
323 This function also copies the path to the Nix store, which may not be what you want.
324 This behavior is deprecated and will throw an error in the future.''
326 lenContent >= lenSuffix
327 && substring (lenContent - lenSuffix) lenContent content == suffix
330 /* Determine whether a string contains the given infix
332 Type: hasInfix :: string -> string -> bool
341 hasInfix "foo" "abcd"
344 hasInfix = infix: content:
345 # Before 23.05, paths would be copied to the store before converting them
346 # to strings and comparing. This was surprising and confusing.
350 lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported.
351 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
352 This function also copies the path to the Nix store, which may not be what you want.
353 This behavior is deprecated and will throw an error in the future.''
354 (builtins.match ".*${escapeRegex infix}.*" "${content}" != null);
356 /* Convert a string to a list of characters (i.e. singleton strings).
357 This allows you to, e.g., map a function over each character. However,
358 note that this will likely be horribly inefficient; Nix is not a
359 general purpose programming language. Complex string manipulations
360 should, if appropriate, be done in a derivation.
361 Also note that Nix treats strings as a list of bytes and thus doesn't
364 Type: stringToCharacters :: string -> [string]
367 stringToCharacters ""
369 stringToCharacters "abc"
371 stringToCharacters "🦄"
372 => [ "�" "�" "�" "�" ]
374 stringToCharacters = s:
375 genList (p: substring p 1 s) (stringLength s);
377 /* Manipulate a string character by character and replace them by
378 strings before concatenating the results.
380 Type: stringAsChars :: (string -> string) -> string -> string
383 stringAsChars (x: if x == "a" then "i" else x) "nax"
387 # Function to map over each individual character
391 map f (stringToCharacters s)
394 /* Convert char to ascii value, must be in printable range
396 Type: charToInt :: string -> int
405 charToInt = c: builtins.getAttr c asciiTable;
407 /* Escape occurrence of the elements of `list` in `string` by
408 prefixing it with a backslash.
410 Type: escape :: [string] -> string -> string
413 escape ["(" ")"] "(foo)"
416 escape = list: replaceStrings list (map (c: "\\${c}") list);
418 /* Escape occurrence of the element of `list` in `string` by
419 converting to its ASCII value and prefixing it with \\x.
420 Only works for printable ascii characters.
422 Type: escapeC = [string] -> string -> string
425 escapeC [" "] "foo bar"
429 escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
431 /* Escape the string so it can be safely placed inside a URL
434 Type: escapeURL :: string -> string
437 escapeURL "foo/bar baz"
441 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" "-" "_" "." "~" ];
442 toEscape = builtins.removeAttrs asciiTable unreserved;
444 replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape);
446 /* Quote string to be used safely within the Bourne shell.
448 Type: escapeShellArg :: string -> string
451 escapeShellArg "esc'ape\nme"
452 => "'esc'\\''ape\nme'"
454 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
456 /* Quote all arguments to be safely passed to the Bourne shell.
458 Type: escapeShellArgs :: [string] -> string
461 escapeShellArgs ["one" "two three" "four'five"]
462 => "'one' 'two three' 'four'\\''five'"
464 escapeShellArgs = concatMapStringsSep " " escapeShellArg;
466 /* Test whether the given name is a valid POSIX shell variable name.
471 isValidPosixName "foo_bar000"
473 isValidPosixName "0-bad.jpg"
476 isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
478 /* Translate a Nix value into a shell variable declaration, with proper escaping.
480 The value can be a string (mapped to a regular variable), a list of strings
481 (mapped to a Bash-style array) or an attribute set of strings (mapped to a
482 Bash-style associative array). Note that "string" includes string-coercible
483 values like paths or derivations.
485 Strings are translated into POSIX sh-compatible code; lists and attribute sets
486 assume a shell that understands Bash syntax (e.g. Bash or ZSH).
488 Type: string -> (string | listOf string | attrsOf string) -> string
492 ${toShellVar "foo" "some string"}
493 [[ "$foo" == "some string" ]]
496 toShellVar = name: value:
497 lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
498 if isAttrs value && ! isStringLike value then
499 "declare -A ${name}=(${
500 concatStringsSep " " (lib.mapAttrsToList (n: v:
501 "[${escapeShellArg n}]=${escapeShellArg v}"
504 else if isList value then
505 "declare -a ${name}=(${escapeShellArgs value})"
507 "${name}=${escapeShellArg value}"
510 /* Translate an attribute set into corresponding shell variable declarations
513 Type: attrsOf (string | listOf string | attrsOf string) -> string
520 ${toShellVars { inherit foo bar; }}
521 [[ "$foo" == "$bar" ]]
524 toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
526 /* Turn a string into a Nix expression representing that string
528 Type: string -> string
531 escapeNixString "hello\${}\n"
532 => "\"hello\\\${}\\n\""
534 escapeNixString = s: escape ["$"] (toJSON s);
536 /* Turn a string into an exact regular expression
538 Type: string -> string
541 escapeRegex "[^a-z]*"
544 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
546 /* Quotes a string if it can't be used as an identifier directly.
548 Type: string -> string
551 escapeNixIdentifier "hello"
553 escapeNixIdentifier "0abc"
556 escapeNixIdentifier = s:
557 # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
558 if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
559 then s else escapeNixString s;
561 /* Escapes a string such that it is safe to include verbatim in an XML
564 Type: string -> string
567 escapeXML ''"test" 'test' < & >''
568 => ""test" 'test' < & >"
570 escapeXML = builtins.replaceStrings
571 ["\"" "'" "<" ">" "&"]
572 [""" "'" "<" ">" "&"];
574 # warning added 12-12-2022
575 replaceChars = lib.warn "lib.replaceChars is a deprecated alias of lib.replaceStrings." builtins.replaceStrings;
577 # Case conversion utilities.
578 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
579 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
581 /* Converts an ASCII string to lower-case.
583 Type: toLower :: string -> string
589 toLower = replaceStrings upperChars lowerChars;
591 /* Converts an ASCII string to upper-case.
593 Type: toUpper :: string -> string
599 toUpper = replaceStrings lowerChars upperChars;
601 /* Appends string context from another string. This is an implementation
602 detail of Nix and should be used carefully.
604 Strings in Nix carry an invisible `context` which is a list of strings
605 representing store paths. If the string is later used in a derivation
606 attribute, the derivation will properly populate the inputDrvs and
610 pkgs = import <nixpkgs> { };
611 addContextFrom pkgs.coreutils "bar"
614 addContextFrom = a: b: substring 0 0 a + b;
616 /* Cut a string with a separator and produces a list of strings which
617 were separated by this separator.
620 splitString "." "foo.bar.baz"
621 => [ "foo" "bar" "baz" ]
622 splitString "/" "/usr/local/bin"
623 => [ "" "usr" "local" "bin" ]
625 splitString = sep: s:
627 splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s));
629 map (addContextFrom s) splits;
631 /* Return a string without the specified prefix, if the prefix matches.
633 Type: string -> string -> string
636 removePrefix "foo." "foo.bar.baz"
638 removePrefix "xxx" "foo.bar.baz"
642 # Prefix to remove if it matches
646 # Before 23.05, paths would be copied to the store before converting them
647 # to strings and comparing. This was surprising and confusing.
651 lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported.
652 There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case.
653 This function also copies the path to the Nix store, which may not be what you want.
654 This behavior is deprecated and will throw an error in the future.''
656 preLen = stringLength prefix;
658 if substring 0 preLen str == prefix then
659 # -1 will take the string until the end
660 substring preLen (-1) str
664 /* Return a string without the specified suffix, if the suffix matches.
666 Type: string -> string -> string
669 removeSuffix "front" "homefront"
671 removeSuffix "xxx" "homefront"
675 # Suffix to remove if it matches
679 # Before 23.05, paths would be copied to the store before converting them
680 # to strings and comparing. This was surprising and confusing.
684 lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
685 There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case.
686 This function also copies the path to the Nix store, which may not be what you want.
687 This behavior is deprecated and will throw an error in the future.''
689 sufLen = stringLength suffix;
690 sLen = stringLength str;
692 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
693 substring 0 (sLen - sufLen) str
697 /* Return true if string v1 denotes a version older than v2.
700 versionOlder "1.1" "1.2"
702 versionOlder "1.1" "1.1"
705 versionOlder = v1: v2: compareVersions v2 v1 == 1;
707 /* Return true if string v1 denotes a version equal to or newer than v2.
710 versionAtLeast "1.1" "1.0"
712 versionAtLeast "1.1" "1.1"
714 versionAtLeast "1.1" "1.2"
717 versionAtLeast = v1: v2: !versionOlder v1 v2;
719 /* This function takes an argument that's either a derivation or a
720 derivation's "name" attribute and extracts the name part from that
724 getName "youtube-dl-2016.01.01"
726 getName pkgs.youtube-dl
730 parse = drv: (parseDrvName drv).name;
734 else x.pname or (parse x.name);
736 /* This function takes an argument that's either a derivation or a
737 derivation's "name" attribute and extracts the version part from that
741 getVersion "youtube-dl-2016.01.01"
743 getVersion pkgs.youtube-dl
747 parse = drv: (parseDrvName drv).version;
751 else x.version or (parse x.name);
753 /* Extract name with version from URL. Ask for separator which is
754 supposed to start extension.
757 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
759 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
762 nameFromURL = url: sep:
764 components = splitString "/" url;
765 filename = lib.last components;
766 name = head (splitString sep filename);
767 in assert name != filename; name;
769 /* Create a "-D<feature>:<type>=<value>" string that can be passed to typical
772 Type: cmakeOptionType :: string -> string -> string -> string
774 @param feature The feature to be set
775 @param type The type of the feature to be set, as described in
776 https://cmake.org/cmake/help/latest/command/set.html
777 the possible values (case insensitive) are:
778 BOOL FILEPATH PATH STRING INTERNAL
779 @param value The desired value
782 cmakeOptionType "string" "ENGINE" "sdl2"
783 => "-DENGINE:STRING=sdl2"
785 cmakeOptionType = let
786 types = [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" ];
787 in type: feature: value:
788 assert (elem (toUpper type) types);
789 assert (isString feature);
790 assert (isString value);
791 "-D${feature}:${toUpper type}=${value}";
793 /* Create a -D<condition>={TRUE,FALSE} string that can be passed to typical
796 Type: cmakeBool :: string -> bool -> string
798 @param condition The condition to be made true or false
799 @param flag The controlling flag of the condition
802 cmakeBool "ENABLE_STATIC_LIBS" false
803 => "-DENABLESTATIC_LIBS:BOOL=FALSE"
805 cmakeBool = condition: flag:
806 assert (lib.isString condition);
807 assert (lib.isBool flag);
808 cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag));
810 /* Create a -D<feature>:STRING=<value> string that can be passed to typical
812 This is the most typical usage, so it deserves a special case.
814 Type: cmakeFeature :: string -> string -> string
816 @param condition The condition to be made true or false
817 @param flag The controlling flag of the condition
820 cmakeFeature "MODULES" "badblock"
821 => "-DMODULES:STRING=badblock"
823 cmakeFeature = feature: value:
824 assert (lib.isString feature);
825 assert (lib.isString value);
826 cmakeOptionType "string" feature value;
828 /* Create a -D<feature>=<value> string that can be passed to typical Meson
831 Type: mesonOption :: string -> string -> string
833 @param feature The feature to be set
834 @param value The desired value
837 mesonOption "engine" "opengl"
840 mesonOption = feature: value:
841 assert (lib.isString feature);
842 assert (lib.isString value);
843 "-D${feature}=${value}";
845 /* Create a -D<condition>={true,false} string that can be passed to typical
848 Type: mesonBool :: string -> bool -> string
850 @param condition The condition to be made true or false
851 @param flag The controlling flag of the condition
854 mesonBool "hardened" true
856 mesonBool "static" false
859 mesonBool = condition: flag:
860 assert (lib.isString condition);
861 assert (lib.isBool flag);
862 mesonOption condition (lib.boolToString flag);
864 /* Create a -D<feature>={enabled,disabled} string that can be passed to
865 typical Meson invocations.
867 Type: mesonEnable :: string -> bool -> string
869 @param feature The feature to be enabled or disabled
870 @param flag The controlling flag
873 mesonEnable "docs" true
875 mesonEnable "savage" false
876 => "-Dsavage=disabled"
878 mesonEnable = feature: flag:
879 assert (lib.isString feature);
880 assert (lib.isBool flag);
881 mesonOption feature (if flag then "enabled" else "disabled");
883 /* Create an --{enable,disable}-<feature> string that can be passed to
884 standard GNU Autoconf scripts.
887 enableFeature true "shared"
889 enableFeature false "shared"
890 => "--disable-shared"
892 enableFeature = flag: feature:
893 assert lib.isBool flag;
894 assert lib.isString feature; # e.g. passing openssl instead of "openssl"
895 "--${if flag then "enable" else "disable"}-${feature}";
897 /* Create an --{enable-<feature>=<value>,disable-<feature>} string that can be passed to
898 standard GNU Autoconf scripts.
901 enableFeatureAs true "shared" "foo"
902 => "--enable-shared=foo"
903 enableFeatureAs false "shared" (throw "ignored")
904 => "--disable-shared"
906 enableFeatureAs = flag: feature: value:
907 enableFeature flag feature + optionalString flag "=${value}";
909 /* Create an --{with,without}-<feature> string that can be passed to
910 standard GNU Autoconf scripts.
913 withFeature true "shared"
915 withFeature false "shared"
916 => "--without-shared"
918 withFeature = flag: feature:
919 assert isString feature; # e.g. passing openssl instead of "openssl"
920 "--${if flag then "with" else "without"}-${feature}";
922 /* Create an --{with-<feature>=<value>,without-<feature>} string that can be passed to
923 standard GNU Autoconf scripts.
926 withFeatureAs true "shared" "foo"
927 => "--with-shared=foo"
928 withFeatureAs false "shared" (throw "ignored")
929 => "--without-shared"
931 withFeatureAs = flag: feature: value:
932 withFeature flag feature + optionalString flag "=${value}";
934 /* Create a fixed width string with additional prefix to match
937 This function will fail if the input string is longer than the
940 Type: fixedWidthString :: int -> string -> string -> string
943 fixedWidthString 5 "0" (toString 15)
946 fixedWidthString = width: filler: str:
948 strw = lib.stringLength str;
949 reqWidth = width - (lib.stringLength filler);
951 assert lib.assertMsg (strw <= width)
952 "fixedWidthString: requested string length (${
953 toString width}) must not be shorter than actual length (${
955 if strw == width then str else filler + fixedWidthString reqWidth filler str;
957 /* Format a number adding leading zeroes up to fixed width.
960 fixedWidthNumber 5 15
963 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
965 /* Convert a float to a string, but emit a warning when precision is lost
966 during the conversion
969 floatToString 0.000001
971 floatToString 0.0000001
972 => trace: warning: Imprecise conversion from float to string 0.000000
975 floatToString = float: let
976 result = toString float;
977 precise = float == fromJSON result;
978 in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}"
981 /* Soft-deprecated function. While the original implementation is available as
982 isConvertibleWithToString, consider using isStringLike instead, if suitable. */
983 isCoercibleToString = lib.warnIf (lib.isInOldestRelease 2305)
984 "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."
985 isConvertibleWithToString;
987 /* Check whether a list or other value can be passed to toString.
989 Many types of value are coercible to string this way, including int, float,
990 null, bool, list of similarly coercible values.
992 isConvertibleWithToString = let
993 types = [ "null" "int" "float" "bool" ];
996 elem (typeOf x) types ||
997 (isList x && lib.all isConvertibleWithToString x);
999 /* Check whether a value can be coerced to a string.
1000 The value must be a string, path, or attribute set.
1002 String-like values can be used without explicit conversion in
1003 string interpolations and in most functions that expect a string.
1011 /* Check whether a value is a store path.
1014 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
1016 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
1018 isStorePath pkgs.python
1020 isStorePath [] || isStorePath 42 || isStorePath {} || …
1024 if isStringLike x then
1025 let str = toString x; in
1026 substring 0 1 str == "/"
1027 && dirOf str == storeDir
1031 /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
1032 ambiguity between zero-padded and octal numbers. See toIntBase10.
1048 => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
1051 => error: floating point JSON numbers are not supported
1055 matchStripInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*";
1056 matchLeadingZero = match "0[[:digit:]]+";
1060 # RegEx: Match any leading whitespace, possibly a '-', one or more digits,
1061 # and finally match any trailing whitespace.
1062 strippedInput = matchStripInput str;
1064 # RegEx: Match a leading '0' then one or more digits.
1065 isLeadingZero = matchLeadingZero (head strippedInput) == [];
1067 # Attempt to parse input
1068 parsedInput = fromJSON (head strippedInput);
1070 generalError = "toInt: Could not convert ${escapeNixString str} to int.";
1073 # Error on presence of non digit characters.
1074 if strippedInput == null
1075 then throw generalError
1076 # Error on presence of leading zero/octal ambiguity.
1077 else if isLeadingZero
1078 then throw "toInt: Ambiguity in interpretation of ${escapeNixString str} between octal and zero padded integer."
1079 # Error if parse function fails.
1080 else if !isInt parsedInput
1081 then throw generalError
1086 /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
1104 => error: floating point JSON numbers are not supported
1108 matchStripInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*";
1109 matchZero = match "0+";
1113 # RegEx: Match any leading whitespace, then match any zero padding,
1114 # capture possibly a '-' followed by one or more digits,
1115 # and finally match any trailing whitespace.
1116 strippedInput = matchStripInput str;
1118 # RegEx: Match at least one '0'.
1119 isZero = matchZero (head strippedInput) == [];
1121 # Attempt to parse input
1122 parsedInput = fromJSON (head strippedInput);
1124 generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
1127 # Error on presence of non digit characters.
1128 if strippedInput == null
1129 then throw generalError
1130 # In the special case zero-padded zero (00000), return early.
1133 # Error if parse function fails.
1134 else if !isInt parsedInput
1135 then throw generalError
1139 /* Read a list of paths from `file`, relative to the `rootPath`.
1140 Lines beginning with `#` are treated as comments and ignored.
1141 Whitespace is significant.
1143 NOTE: This function is not performant and should be avoided.
1146 readPathsFromFile /prefix
1147 ./pkgs/development/libraries/qt-5/5.4/qtbase/series
1148 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
1149 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
1150 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
1151 "/prefix/nix-profiles-library-paths.patch"
1152 "/prefix/compose-search-path.patch" ]
1154 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead."
1157 lines = lib.splitString "\n" (readFile file);
1158 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
1159 relativePaths = removeComments lines;
1160 absolutePaths = map (path: rootPath + "/${path}") relativePaths;
1164 /* Read the contents of a file removing the trailing \n
1166 Type: fileContents :: path -> string
1169 $ echo "1.0" > ./version
1171 fileContents ./version
1174 fileContents = file: removeSuffix "\n" (readFile file);
1177 /* Creates a valid derivation name from a potentially invalid one.
1179 Type: sanitizeDerivationName :: String -> String
1182 sanitizeDerivationName "../hello.bar # foo"
1184 sanitizeDerivationName ""
1186 sanitizeDerivationName pkgs.hello
1187 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
1189 sanitizeDerivationName =
1190 let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
1193 # First detect the common case of already valid strings, to speed those up
1194 if stringLength string <= 207 && okRegex string != null
1195 then unsafeDiscardStringContext string
1196 else lib.pipe string [
1197 # Get rid of string context. This is safe under the assumption that the
1198 # resulting string is only used as a derivation name
1199 unsafeDiscardStringContext
1200 # Strip all leading "."
1201 (x: elemAt (match "\\.*(.*)" x) 0)
1202 # Split out all invalid characters
1203 # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
1204 # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
1205 (split "[^[:alnum:]+._?=-]+")
1206 # Replace invalid character ranges with a "-"
1207 (concatMapStrings (s: if lib.isList s then "-" else s))
1208 # Limit to 211 characters (minus 4 chars for ".drv")
1209 (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
1210 # If the result is empty, replace it with "unknown"
1211 (x: if stringLength x == 0 then "unknown" else x)
1214 /* Computes the Levenshtein distance between two strings.
1215 Complexity O(n*m) where n and m are the lengths of the strings.
1216 Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
1218 Type: levenshtein :: string -> string -> int
1221 levenshtein "foo" "foo"
1223 levenshtein "book" "hook"
1225 levenshtein "hello" "Heyo"
1228 levenshtein = a: b: let
1229 # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
1230 arr = lib.genList (i:
1233 ) (stringLength b + 1)
1234 ) (stringLength a + 1);
1235 d = x: y: lib.elemAt (lib.elemAt arr x) y;
1237 let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
1241 else if i == 0 then j
1243 ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1))
1244 ( d (i - 1) (j - 1) + c );
1245 in d (stringLength a) (stringLength b);
1247 /* Returns the length of the prefix common to both strings.
1249 commonPrefixLength = a: b:
1251 m = lib.min (stringLength a) (stringLength b);
1252 go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
1255 /* Returns the length of the suffix common to both strings.
1257 commonSuffixLength = a: b:
1259 m = lib.min (stringLength a) (stringLength b);
1260 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;
1263 /* Returns whether the levenshtein distance between two strings is at most some value
1264 Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
1266 Type: levenshteinAtMost :: int -> string -> string -> bool
1269 levenshteinAtMost 0 "foo" "foo"
1271 levenshteinAtMost 1 "foo" "boa"
1273 levenshteinAtMost 2 "foo" "boa"
1275 levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1277 levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1281 levenshteinAtMost = let
1282 infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1284 # This function takes two strings stripped by their common pre and suffix,
1285 # and returns whether they differ by at most two by Levenshtein distance.
1286 # Because of this stripping, if they do indeed differ by at most two edits,
1287 # we know that those edits were (if at all) done at the start or the end,
1288 # while the middle has to have stayed the same. This fact is used in the
1290 infixDifferAtMost2 = x: y:
1292 xlen = stringLength x;
1293 ylen = stringLength y;
1294 # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
1295 # diff is one of 0, 1 or 2
1298 # Infix of x and y, stripped by the left and right most character
1299 xinfix = substring 1 (xlen - 2) x;
1300 yinfix = substring 1 (ylen - 2) y;
1302 # x and y but a character deleted at the left or right
1303 xdelr = substring 0 (xlen - 1) x;
1304 xdell = substring 1 (xlen - 1) x;
1305 ydelr = substring 0 (ylen - 1) y;
1306 ydell = substring 1 (ylen - 1) y;
1308 # A length difference of 2 can only be gotten with 2 delete edits,
1309 # which have to have happened at the start and end of x
1310 # Example: "abcdef" -> "bcde"
1311 if diff == 2 then xinfix == y
1312 # A length difference of 1 can only be gotten with a deletion on the
1313 # right and a replacement on the left or vice versa.
1314 # Example: "abcdef" -> "bcdez" or "zbcde"
1315 else if diff == 1 then xinfix == ydelr || xinfix == ydell
1316 # No length difference can either happen through replacements on both
1317 # sides, or a deletion on the left and an insertion on the right or
1319 # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1320 else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1322 in k: if k <= 0 then a: b: a == b else
1325 alen = stringLength a;
1326 blen = stringLength b;
1327 prelen = commonPrefixLength a b;
1328 suflen = commonSuffixLength a b;
1329 presuflen = prelen + suflen;
1330 ainfix = substring prelen (alen - presuflen) a;
1331 binfix = substring prelen (blen - presuflen) b;
1333 # Make a be the bigger string
1334 if alen < blen then f b a
1335 # If a has over k more characters than b, even with k deletes on a, b can't be reached
1336 else if alen - blen > k then false
1337 else if k == 1 then infixDifferAtMost1 ainfix binfix
1338 else if k == 2 then infixDifferAtMost2 ainfix binfix
1339 else levenshtein ainfix binfix <= k;