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");
147 /* Construct a Unix-style, colon-separated search path consisting of
148 the given `subDir` appended to each of the given paths.
150 Type: makeSearchPath :: string -> [string] -> string
153 makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
154 => "/root/bin:/usr/bin:/usr/local/bin"
155 makeSearchPath "bin" [""]
159 # Directory name to append
163 concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
165 /* Construct a Unix-style search path by appending the given
166 `subDir` to the specified `output` of each of the packages. If no
167 output by the given name is found, fallback to `.out` and then to
170 Type: string -> string -> [package] -> string
173 makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
174 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
176 makeSearchPathOutput =
177 # Package output to use
179 # Directory name to append
182 pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
184 /* Construct a library search path (such as RPATH) containing the
185 libraries for a set of packages
188 makeLibraryPath [ "/usr" "/usr/local" ]
189 => "/usr/lib:/usr/local/lib"
190 pkgs = import <nixpkgs> { }
191 makeLibraryPath [ pkgs.openssl pkgs.zlib ]
192 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
194 makeLibraryPath = makeSearchPathOutput "lib" "lib";
196 /* Construct a binary search path (such as $PATH) containing the
197 binaries for a set of packages.
200 makeBinPath ["/root" "/usr" "/usr/local"]
201 => "/root/bin:/usr/bin:/usr/local/bin"
203 makeBinPath = makeSearchPathOutput "bin" "bin";
205 /* Normalize path, removing extraneous /s
207 Type: normalizePath :: string -> string
210 normalizePath "/a//b///c/"
217 lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported.
218 Path values are always normalised in Nix, so there's no need to call this function on them.
219 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.
220 This behavior is deprecated and will throw an error in the future.''
223 (x: y: if y == "/" && hasSuffix "/" x then x else x+y)
225 (stringToCharacters s)
228 /* Depending on the boolean `cond', return either the given string
229 or the empty string. Useful to concatenate against a bigger string.
231 Type: optionalString :: bool -> string -> string
234 optionalString true "some-string"
236 optionalString false "some-string"
242 # String to return if condition is true
243 string: if cond then string else "";
245 /* Determine whether a string has given prefix.
247 Type: hasPrefix :: string -> string -> bool
250 hasPrefix "foo" "foobar"
252 hasPrefix "foo" "barfoo"
256 # Prefix to check for
260 # Before 23.05, paths would be copied to the store before converting them
261 # to strings and comparing. This was surprising and confusing.
265 lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported.
266 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
267 This function also copies the path to the Nix store, which may not be what you want.
268 This behavior is deprecated and will throw an error in the future.
269 You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.''
270 (substring 0 (stringLength pref) str == pref);
272 /* Determine whether a string has given suffix.
274 Type: hasSuffix :: string -> string -> bool
277 hasSuffix "foo" "foobar"
279 hasSuffix "foo" "barfoo"
283 # Suffix to check for
288 lenContent = stringLength content;
289 lenSuffix = stringLength suffix;
291 # Before 23.05, paths would be copied to the store before converting them
292 # to strings and comparing. This was surprising and confusing.
296 lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
297 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
298 This function also copies the path to the Nix store, which may not be what you want.
299 This behavior is deprecated and will throw an error in the future.''
301 lenContent >= lenSuffix
302 && substring (lenContent - lenSuffix) lenContent content == suffix
305 /* Determine whether a string contains the given infix
307 Type: hasInfix :: string -> string -> bool
316 hasInfix "foo" "abcd"
319 hasInfix = infix: content:
320 # Before 23.05, paths would be copied to the store before converting them
321 # to strings and comparing. This was surprising and confusing.
325 lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported.
326 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
327 This function also copies the path to the Nix store, which may not be what you want.
328 This behavior is deprecated and will throw an error in the future.''
329 (builtins.match ".*${escapeRegex infix}.*" "${content}" != null);
331 /* Convert a string to a list of characters (i.e. singleton strings).
332 This allows you to, e.g., map a function over each character. However,
333 note that this will likely be horribly inefficient; Nix is not a
334 general purpose programming language. Complex string manipulations
335 should, if appropriate, be done in a derivation.
336 Also note that Nix treats strings as a list of bytes and thus doesn't
339 Type: stringToCharacters :: string -> [string]
342 stringToCharacters ""
344 stringToCharacters "abc"
346 stringToCharacters "🦄"
347 => [ "�" "�" "�" "�" ]
349 stringToCharacters = s:
350 genList (p: substring p 1 s) (stringLength s);
352 /* Manipulate a string character by character and replace them by
353 strings before concatenating the results.
355 Type: stringAsChars :: (string -> string) -> string -> string
358 stringAsChars (x: if x == "a" then "i" else x) "nax"
362 # Function to map over each individual character
366 map f (stringToCharacters s)
369 /* Convert char to ascii value, must be in printable range
371 Type: charToInt :: string -> int
380 charToInt = c: builtins.getAttr c asciiTable;
382 /* Escape occurrence of the elements of `list` in `string` by
383 prefixing it with a backslash.
385 Type: escape :: [string] -> string -> string
388 escape ["(" ")"] "(foo)"
391 escape = list: replaceStrings list (map (c: "\\${c}") list);
393 /* Escape occurrence of the element of `list` in `string` by
394 converting to its ASCII value and prefixing it with \\x.
395 Only works for printable ascii characters.
397 Type: escapeC = [string] -> string -> string
400 escapeC [" "] "foo bar"
404 escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
406 /* Escape the string so it can be safely placed inside a URL
409 Type: escapeURL :: string -> string
412 escapeURL "foo/bar baz"
416 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" "-" "_" "." "~" ];
417 toEscape = builtins.removeAttrs asciiTable unreserved;
419 replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape);
421 /* Quote string to be used safely within the Bourne shell.
423 Type: escapeShellArg :: string -> string
426 escapeShellArg "esc'ape\nme"
427 => "'esc'\\''ape\nme'"
429 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
431 /* Quote all arguments to be safely passed to the Bourne shell.
433 Type: escapeShellArgs :: [string] -> string
436 escapeShellArgs ["one" "two three" "four'five"]
437 => "'one' 'two three' 'four'\\''five'"
439 escapeShellArgs = concatMapStringsSep " " escapeShellArg;
441 /* Test whether the given name is a valid POSIX shell variable name.
446 isValidPosixName "foo_bar000"
448 isValidPosixName "0-bad.jpg"
451 isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
453 /* Translate a Nix value into a shell variable declaration, with proper escaping.
455 The value can be a string (mapped to a regular variable), a list of strings
456 (mapped to a Bash-style array) or an attribute set of strings (mapped to a
457 Bash-style associative array). Note that "string" includes string-coercible
458 values like paths or derivations.
460 Strings are translated into POSIX sh-compatible code; lists and attribute sets
461 assume a shell that understands Bash syntax (e.g. Bash or ZSH).
463 Type: string -> (string | listOf string | attrsOf string) -> string
467 ${toShellVar "foo" "some string"}
468 [[ "$foo" == "some string" ]]
471 toShellVar = name: value:
472 lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
473 if isAttrs value && ! isStringLike value then
474 "declare -A ${name}=(${
475 concatStringsSep " " (lib.mapAttrsToList (n: v:
476 "[${escapeShellArg n}]=${escapeShellArg v}"
479 else if isList value then
480 "declare -a ${name}=(${escapeShellArgs value})"
482 "${name}=${escapeShellArg value}"
485 /* Translate an attribute set into corresponding shell variable declarations
488 Type: attrsOf (string | listOf string | attrsOf string) -> string
495 ${toShellVars { inherit foo bar; }}
496 [[ "$foo" == "$bar" ]]
499 toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
501 /* Turn a string into a Nix expression representing that string
503 Type: string -> string
506 escapeNixString "hello\${}\n"
507 => "\"hello\\\${}\\n\""
509 escapeNixString = s: escape ["$"] (toJSON s);
511 /* Turn a string into an exact regular expression
513 Type: string -> string
516 escapeRegex "[^a-z]*"
519 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
521 /* Quotes a string if it can't be used as an identifier directly.
523 Type: string -> string
526 escapeNixIdentifier "hello"
528 escapeNixIdentifier "0abc"
531 escapeNixIdentifier = s:
532 # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
533 if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
534 then s else escapeNixString s;
536 /* Escapes a string such that it is safe to include verbatim in an XML
539 Type: string -> string
542 escapeXML ''"test" 'test' < & >''
543 => ""test" 'test' < & >"
545 escapeXML = builtins.replaceStrings
546 ["\"" "'" "<" ">" "&"]
547 [""" "'" "<" ">" "&"];
549 # warning added 12-12-2022
550 replaceChars = lib.warn "replaceChars is a deprecated alias of replaceStrings, replace usages of it with replaceStrings." builtins.replaceStrings;
552 # Case conversion utilities.
553 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
554 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
556 /* Converts an ASCII string to lower-case.
558 Type: toLower :: string -> string
564 toLower = replaceStrings upperChars lowerChars;
566 /* Converts an ASCII string to upper-case.
568 Type: toUpper :: string -> string
574 toUpper = replaceStrings lowerChars upperChars;
576 /* Appends string context from another string. This is an implementation
577 detail of Nix and should be used carefully.
579 Strings in Nix carry an invisible `context` which is a list of strings
580 representing store paths. If the string is later used in a derivation
581 attribute, the derivation will properly populate the inputDrvs and
585 pkgs = import <nixpkgs> { };
586 addContextFrom pkgs.coreutils "bar"
589 addContextFrom = a: b: substring 0 0 a + b;
591 /* Cut a string with a separator and produces a list of strings which
592 were separated by this separator.
595 splitString "." "foo.bar.baz"
596 => [ "foo" "bar" "baz" ]
597 splitString "/" "/usr/local/bin"
598 => [ "" "usr" "local" "bin" ]
600 splitString = sep: s:
602 splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s));
604 map (addContextFrom s) splits;
606 /* Return a string without the specified prefix, if the prefix matches.
608 Type: string -> string -> string
611 removePrefix "foo." "foo.bar.baz"
613 removePrefix "xxx" "foo.bar.baz"
617 # Prefix to remove if it matches
621 # Before 23.05, paths would be copied to the store before converting them
622 # to strings and comparing. This was surprising and confusing.
626 lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported.
627 There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case.
628 This function also copies the path to the Nix store, which may not be what you want.
629 This behavior is deprecated and will throw an error in the future.''
631 preLen = stringLength prefix;
633 if substring 0 preLen str == prefix then
634 # -1 will take the string until the end
635 substring preLen (-1) str
639 /* Return a string without the specified suffix, if the suffix matches.
641 Type: string -> string -> string
644 removeSuffix "front" "homefront"
646 removeSuffix "xxx" "homefront"
650 # Suffix to remove if it matches
654 # Before 23.05, paths would be copied to the store before converting them
655 # to strings and comparing. This was surprising and confusing.
659 lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
660 There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case.
661 This function also copies the path to the Nix store, which may not be what you want.
662 This behavior is deprecated and will throw an error in the future.''
664 sufLen = stringLength suffix;
665 sLen = stringLength str;
667 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
668 substring 0 (sLen - sufLen) str
672 /* Return true if string v1 denotes a version older than v2.
675 versionOlder "1.1" "1.2"
677 versionOlder "1.1" "1.1"
680 versionOlder = v1: v2: compareVersions v2 v1 == 1;
682 /* Return true if string v1 denotes a version equal to or newer than v2.
685 versionAtLeast "1.1" "1.0"
687 versionAtLeast "1.1" "1.1"
689 versionAtLeast "1.1" "1.2"
692 versionAtLeast = v1: v2: !versionOlder v1 v2;
694 /* This function takes an argument that's either a derivation or a
695 derivation's "name" attribute and extracts the name part from that
699 getName "youtube-dl-2016.01.01"
701 getName pkgs.youtube-dl
706 parse = drv: (parseDrvName drv).name;
709 else x.pname or (parse x.name);
711 /* This function takes an argument that's either a derivation or a
712 derivation's "name" attribute and extracts the version part from that
716 getVersion "youtube-dl-2016.01.01"
718 getVersion pkgs.youtube-dl
723 parse = drv: (parseDrvName drv).version;
726 else x.version or (parse x.name);
728 /* Extract name with version from URL. Ask for separator which is
729 supposed to start extension.
732 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
734 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
737 nameFromURL = url: sep:
739 components = splitString "/" url;
740 filename = lib.last components;
741 name = head (splitString sep filename);
742 in assert name != filename; name;
744 /* Create a "-D<feature>:<type>=<value>" string that can be passed to typical
747 Type: cmakeOptionType :: string -> string -> string -> string
749 @param feature The feature to be set
750 @param type The type of the feature to be set, as described in
751 https://cmake.org/cmake/help/latest/command/set.html
752 the possible values (case insensitive) are:
753 BOOL FILEPATH PATH STRING INTERNAL
754 @param value The desired value
757 cmakeOptionType "string" "ENGINE" "sdl2"
758 => "-DENGINE:STRING=sdl2"
760 cmakeOptionType = type: feature: value:
761 assert (lib.elem (lib.toUpper type)
762 [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" ]);
763 assert (lib.isString feature);
764 assert (lib.isString value);
765 "-D${feature}:${lib.toUpper type}=${value}";
767 /* Create a -D<condition>={TRUE,FALSE} string that can be passed to typical
770 Type: cmakeBool :: string -> bool -> string
772 @param condition The condition to be made true or false
773 @param flag The controlling flag of the condition
776 cmakeBool "ENABLE_STATIC_LIBS" false
777 => "-DENABLESTATIC_LIBS:BOOL=FALSE"
779 cmakeBool = condition: flag:
780 assert (lib.isString condition);
781 assert (lib.isBool flag);
782 cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag));
784 /* Create a -D<feature>:STRING=<value> string that can be passed to typical
786 This is the most typical usage, so it deserves a special case.
788 Type: cmakeFeature :: string -> string -> string
790 @param condition The condition to be made true or false
791 @param flag The controlling flag of the condition
794 cmakeFeature "MODULES" "badblock"
795 => "-DMODULES:STRING=badblock"
797 cmakeFeature = feature: value:
798 assert (lib.isString feature);
799 assert (lib.isString value);
800 cmakeOptionType "string" feature value;
802 /* Create a -D<feature>=<value> string that can be passed to typical Meson
805 Type: mesonOption :: string -> string -> string
807 @param feature The feature to be set
808 @param value The desired value
811 mesonOption "engine" "opengl"
814 mesonOption = feature: value:
815 assert (lib.isString feature);
816 assert (lib.isString value);
817 "-D${feature}=${value}";
819 /* Create a -D<condition>={true,false} string that can be passed to typical
822 Type: mesonBool :: string -> bool -> string
824 @param condition The condition to be made true or false
825 @param flag The controlling flag of the condition
828 mesonBool "hardened" true
830 mesonBool "static" false
833 mesonBool = condition: flag:
834 assert (lib.isString condition);
835 assert (lib.isBool flag);
836 mesonOption condition (lib.boolToString flag);
838 /* Create a -D<feature>={enabled,disabled} string that can be passed to
839 typical Meson invocations.
841 Type: mesonEnable :: string -> bool -> string
843 @param feature The feature to be enabled or disabled
844 @param flag The controlling flag
847 mesonEnable "docs" true
849 mesonEnable "savage" false
850 => "-Dsavage=disabled"
852 mesonEnable = feature: flag:
853 assert (lib.isString feature);
854 assert (lib.isBool flag);
855 mesonOption feature (if flag then "enabled" else "disabled");
857 /* Create an --{enable,disable}-<feature> string that can be passed to
858 standard GNU Autoconf scripts.
861 enableFeature true "shared"
863 enableFeature false "shared"
864 => "--disable-shared"
866 enableFeature = flag: feature:
867 assert lib.isBool flag;
868 assert lib.isString feature; # e.g. passing openssl instead of "openssl"
869 "--${if flag then "enable" else "disable"}-${feature}";
871 /* Create an --{enable-<feature>=<value>,disable-<feature>} string that can be passed to
872 standard GNU Autoconf scripts.
875 enableFeatureAs true "shared" "foo"
876 => "--enable-shared=foo"
877 enableFeatureAs false "shared" (throw "ignored")
878 => "--disable-shared"
880 enableFeatureAs = flag: feature: value:
881 enableFeature flag feature + optionalString flag "=${value}";
883 /* Create an --{with,without}-<feature> string that can be passed to
884 standard GNU Autoconf scripts.
887 withFeature true "shared"
889 withFeature false "shared"
890 => "--without-shared"
892 withFeature = flag: feature:
893 assert isString feature; # e.g. passing openssl instead of "openssl"
894 "--${if flag then "with" else "without"}-${feature}";
896 /* Create an --{with-<feature>=<value>,without-<feature>} string that can be passed to
897 standard GNU Autoconf scripts.
900 withFeatureAs true "shared" "foo"
901 => "--with-shared=foo"
902 withFeatureAs false "shared" (throw "ignored")
903 => "--without-shared"
905 withFeatureAs = flag: feature: value:
906 withFeature flag feature + optionalString flag "=${value}";
908 /* Create a fixed width string with additional prefix to match
911 This function will fail if the input string is longer than the
914 Type: fixedWidthString :: int -> string -> string -> string
917 fixedWidthString 5 "0" (toString 15)
920 fixedWidthString = width: filler: str:
922 strw = lib.stringLength str;
923 reqWidth = width - (lib.stringLength filler);
925 assert lib.assertMsg (strw <= width)
926 "fixedWidthString: requested string length (${
927 toString width}) must not be shorter than actual length (${
929 if strw == width then str else filler + fixedWidthString reqWidth filler str;
931 /* Format a number adding leading zeroes up to fixed width.
934 fixedWidthNumber 5 15
937 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
939 /* Convert a float to a string, but emit a warning when precision is lost
940 during the conversion
943 floatToString 0.000001
945 floatToString 0.0000001
946 => trace: warning: Imprecise conversion from float to string 0.000000
949 floatToString = float: let
950 result = toString float;
951 precise = float == fromJSON result;
952 in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}"
955 /* Soft-deprecated function. While the original implementation is available as
956 isConvertibleWithToString, consider using isStringLike instead, if suitable. */
957 isCoercibleToString = lib.warnIf (lib.isInOldestRelease 2305)
958 "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."
959 isConvertibleWithToString;
961 /* Check whether a list or other value can be passed to toString.
963 Many types of value are coercible to string this way, including int, float,
964 null, bool, list of similarly coercible values.
966 isConvertibleWithToString = x:
968 elem (typeOf x) [ "null" "int" "float" "bool" ] ||
969 (isList x && lib.all isConvertibleWithToString x);
971 /* Check whether a value can be coerced to a string.
972 The value must be a string, path, or attribute set.
974 String-like values can be used without explicit conversion in
975 string interpolations and in most functions that expect a string.
983 /* Check whether a value is a store path.
986 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
988 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
990 isStorePath pkgs.python
992 isStorePath [] || isStorePath 42 || isStorePath {} || …
996 if isStringLike x then
997 let str = toString x; in
998 substring 0 1 str == "/"
999 && dirOf str == storeDir
1003 /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
1004 ambiguity between zero-padded and octal numbers. See toIntBase10.
1020 => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
1023 => error: floating point JSON numbers are not supported
1027 # RegEx: Match any leading whitespace, possibly a '-', one or more digits,
1028 # and finally match any trailing whitespace.
1029 strippedInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*" str;
1031 # RegEx: Match a leading '0' then one or more digits.
1032 isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == [];
1034 # Attempt to parse input
1035 parsedInput = fromJSON (head strippedInput);
1037 generalError = "toInt: Could not convert ${escapeNixString str} to int.";
1039 octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}"
1040 + " between octal and zero padded integer.";
1043 # Error on presence of non digit characters.
1044 if strippedInput == null
1045 then throw generalError
1046 # Error on presence of leading zero/octal ambiguity.
1047 else if isLeadingZero
1048 then throw octalAmbigError
1049 # Error if parse function fails.
1050 else if !isInt parsedInput
1051 then throw generalError
1056 /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
1074 => error: floating point JSON numbers are not supported
1078 # RegEx: Match any leading whitespace, then match any zero padding,
1079 # capture possibly a '-' followed by one or more digits,
1080 # and finally match any trailing whitespace.
1081 strippedInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*" str;
1083 # RegEx: Match at least one '0'.
1084 isZero = match "0+" (head strippedInput) == [];
1086 # Attempt to parse input
1087 parsedInput = fromJSON (head strippedInput);
1089 generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
1092 # Error on presence of non digit characters.
1093 if strippedInput == null
1094 then throw generalError
1095 # In the special case zero-padded zero (00000), return early.
1098 # Error if parse function fails.
1099 else if !isInt parsedInput
1100 then throw generalError
1104 /* Read a list of paths from `file`, relative to the `rootPath`.
1105 Lines beginning with `#` are treated as comments and ignored.
1106 Whitespace is significant.
1108 NOTE: This function is not performant and should be avoided.
1111 readPathsFromFile /prefix
1112 ./pkgs/development/libraries/qt-5/5.4/qtbase/series
1113 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
1114 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
1115 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
1116 "/prefix/nix-profiles-library-paths.patch"
1117 "/prefix/compose-search-path.patch" ]
1119 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead"
1122 lines = lib.splitString "\n" (readFile file);
1123 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
1124 relativePaths = removeComments lines;
1125 absolutePaths = map (path: rootPath + "/${path}") relativePaths;
1129 /* Read the contents of a file removing the trailing \n
1131 Type: fileContents :: path -> string
1134 $ echo "1.0" > ./version
1136 fileContents ./version
1139 fileContents = file: removeSuffix "\n" (readFile file);
1142 /* Creates a valid derivation name from a potentially invalid one.
1144 Type: sanitizeDerivationName :: String -> String
1147 sanitizeDerivationName "../hello.bar # foo"
1149 sanitizeDerivationName ""
1151 sanitizeDerivationName pkgs.hello
1152 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
1154 sanitizeDerivationName =
1155 let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
1158 # First detect the common case of already valid strings, to speed those up
1159 if stringLength string <= 207 && okRegex string != null
1160 then unsafeDiscardStringContext string
1161 else lib.pipe string [
1162 # Get rid of string context. This is safe under the assumption that the
1163 # resulting string is only used as a derivation name
1164 unsafeDiscardStringContext
1165 # Strip all leading "."
1166 (x: elemAt (match "\\.*(.*)" x) 0)
1167 # Split out all invalid characters
1168 # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
1169 # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
1170 (split "[^[:alnum:]+._?=-]+")
1171 # Replace invalid character ranges with a "-"
1172 (concatMapStrings (s: if lib.isList s then "-" else s))
1173 # Limit to 211 characters (minus 4 chars for ".drv")
1174 (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
1175 # If the result is empty, replace it with "unknown"
1176 (x: if stringLength x == 0 then "unknown" else x)
1179 /* Computes the Levenshtein distance between two strings.
1180 Complexity O(n*m) where n and m are the lengths of the strings.
1181 Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
1183 Type: levenshtein :: string -> string -> int
1186 levenshtein "foo" "foo"
1188 levenshtein "book" "hook"
1190 levenshtein "hello" "Heyo"
1193 levenshtein = a: b: let
1194 # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
1195 arr = lib.genList (i:
1198 ) (stringLength b + 1)
1199 ) (stringLength a + 1);
1200 d = x: y: lib.elemAt (lib.elemAt arr x) y;
1202 let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
1206 else if i == 0 then j
1208 ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1))
1209 ( d (i - 1) (j - 1) + c );
1210 in d (stringLength a) (stringLength b);
1212 /* Returns the length of the prefix common to both strings.
1214 commonPrefixLength = a: b:
1216 m = lib.min (stringLength a) (stringLength b);
1217 go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
1220 /* Returns the length of the suffix common to both strings.
1222 commonSuffixLength = a: b:
1224 m = lib.min (stringLength a) (stringLength b);
1225 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;
1228 /* Returns whether the levenshtein distance between two strings is at most some value
1229 Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
1231 Type: levenshteinAtMost :: int -> string -> string -> bool
1234 levenshteinAtMost 0 "foo" "foo"
1236 levenshteinAtMost 1 "foo" "boa"
1238 levenshteinAtMost 2 "foo" "boa"
1240 levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1242 levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1246 levenshteinAtMost = let
1247 infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1249 # This function takes two strings stripped by their common pre and suffix,
1250 # and returns whether they differ by at most two by Levenshtein distance.
1251 # Because of this stripping, if they do indeed differ by at most two edits,
1252 # we know that those edits were (if at all) done at the start or the end,
1253 # while the middle has to have stayed the same. This fact is used in the
1255 infixDifferAtMost2 = x: y:
1257 xlen = stringLength x;
1258 ylen = stringLength y;
1259 # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
1260 # diff is one of 0, 1 or 2
1263 # Infix of x and y, stripped by the left and right most character
1264 xinfix = substring 1 (xlen - 2) x;
1265 yinfix = substring 1 (ylen - 2) y;
1267 # x and y but a character deleted at the left or right
1268 xdelr = substring 0 (xlen - 1) x;
1269 xdell = substring 1 (xlen - 1) x;
1270 ydelr = substring 0 (ylen - 1) y;
1271 ydell = substring 1 (ylen - 1) y;
1273 # A length difference of 2 can only be gotten with 2 delete edits,
1274 # which have to have happened at the start and end of x
1275 # Example: "abcdef" -> "bcde"
1276 if diff == 2 then xinfix == y
1277 # A length difference of 1 can only be gotten with a deletion on the
1278 # right and a replacement on the left or vice versa.
1279 # Example: "abcdef" -> "bcdez" or "zbcde"
1280 else if diff == 1 then xinfix == ydelr || xinfix == ydell
1281 # No length difference can either happen through replacements on both
1282 # sides, or a deletion on the left and an insertion on the right or
1284 # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1285 else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1287 in k: if k <= 0 then a: b: a == b else
1290 alen = stringLength a;
1291 blen = stringLength b;
1292 prelen = commonPrefixLength a b;
1293 suflen = commonSuffixLength a b;
1294 presuflen = prelen + suflen;
1295 ainfix = substring prelen (alen - presuflen) a;
1296 binfix = substring prelen (blen - presuflen) b;
1298 # Make a be the bigger string
1299 if alen < blen then f b a
1300 # If a has over k more characters than b, even with k deletes on a, b can't be reached
1301 else if alen - blen > k then false
1302 else if k == 1 then infixDifferAtMost1 ainfix binfix
1303 else if k == 2 then infixDifferAtMost2 ainfix binfix
1304 else levenshtein ainfix binfix <= k;