1/* String manipulation functions. */
2{ lib }:
3let
4
5 inherit (builtins) length;
6
7 inherit (lib.trivial) warnIf;
8
9asciiTable = import ./ascii-table.nix;
10
11in
12
13rec {
14
15 inherit (builtins)
16 compareVersions
17 elem
18 elemAt
19 filter
20 fromJSON
21 genList
22 head
23 isInt
24 isList
25 isAttrs
26 isPath
27 isString
28 match
29 parseDrvName
30 readFile
31 replaceStrings
32 split
33 storeDir
34 stringLength
35 substring
36 tail
37 toJSON
38 typeOf
39 unsafeDiscardStringContext
40 ;
41
42 /* Concatenate a list of strings.
43
44 Type: concatStrings :: [string] -> string
45
46 Example:
47 concatStrings ["foo" "bar"]
48 => "foobar"
49 */
50 concatStrings = builtins.concatStringsSep "";
51
52 /* Map a function over a list and concatenate the resulting strings.
53
54 Type: concatMapStrings :: (a -> string) -> [a] -> string
55
56 Example:
57 concatMapStrings (x: "a" + x) ["foo" "bar"]
58 => "afooabar"
59 */
60 concatMapStrings = f: list: concatStrings (map f list);
61
62 /* Like `concatMapStrings` except that the f functions also gets the
63 position as a parameter.
64
65 Type: concatImapStrings :: (int -> a -> string) -> [a] -> string
66
67 Example:
68 concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"]
69 => "1-foo2-bar"
70 */
71 concatImapStrings = f: list: concatStrings (lib.imap1 f list);
72
73 /* Place an element between each element of a list
74
75 Type: intersperse :: a -> [a] -> [a]
76
77 Example:
78 intersperse "/" ["usr" "local" "bin"]
79 => ["usr" "/" "local" "/" "bin"].
80 */
81 intersperse =
82 # Separator to add between elements
83 separator:
84 # Input list
85 list:
86 if list == [] || length list == 1
87 then list
88 else tail (lib.concatMap (x: [separator x]) list);
89
90 /* Concatenate a list of strings with a separator between each element
91
92 Type: concatStringsSep :: string -> [string] -> string
93
94 Example:
95 concatStringsSep "/" ["usr" "local" "bin"]
96 => "usr/local/bin"
97 */
98 concatStringsSep = builtins.concatStringsSep;
99
100 /* Maps a function over a list of strings and then concatenates the
101 result with the specified separator interspersed between
102 elements.
103
104 Type: concatMapStringsSep :: string -> (a -> string) -> [a] -> string
105
106 Example:
107 concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"]
108 => "FOO-BAR-BAZ"
109 */
110 concatMapStringsSep =
111 # Separator to add between elements
112 sep:
113 # Function to map over the list
114 f:
115 # List of input strings
116 list: concatStringsSep sep (map f list);
117
118 /* Same as `concatMapStringsSep`, but the mapping function
119 additionally receives the position of its argument.
120
121 Type: concatIMapStringsSep :: string -> (int -> a -> string) -> [a] -> string
122
123 Example:
124 concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
125 => "6-3-2"
126 */
127 concatImapStringsSep =
128 # Separator to add between elements
129 sep:
130 # Function that receives elements and their positions
131 f:
132 # List of input strings
133 list: concatStringsSep sep (lib.imap1 f list);
134
135 /* Concatenate a list of strings, adding a newline at the end of each one.
136 Defined as `concatMapStrings (s: s + "\n")`.
137
138 Type: concatLines :: [string] -> string
139
140 Example:
141 concatLines [ "foo" "bar" ]
142 => "foo\nbar\n"
143 */
144 concatLines = concatMapStrings (s: s + "\n");
145
146 /*
147 Replicate a string n times,
148 and concatenate the parts into a new string.
149
150 Type: replicate :: int -> string -> string
151
152 Example:
153 replicate 3 "v"
154 => "vvv"
155 replicate 5 "hello"
156 => "hellohellohellohellohello"
157 */
158 replicate = n: s: concatStrings (lib.lists.replicate n s);
159
160 /* Construct a Unix-style, colon-separated search path consisting of
161 the given `subDir` appended to each of the given paths.
162
163 Type: makeSearchPath :: string -> [string] -> string
164
165 Example:
166 makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
167 => "/root/bin:/usr/bin:/usr/local/bin"
168 makeSearchPath "bin" [""]
169 => "/bin"
170 */
171 makeSearchPath =
172 # Directory name to append
173 subDir:
174 # List of base paths
175 paths:
176 concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
177
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
181 the default.
182
183 Type: string -> string -> [package] -> string
184
185 Example:
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"
188 */
189 makeSearchPathOutput =
190 # Package output to use
191 output:
192 # Directory name to append
193 subDir:
194 # List of packages
195 pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
196
197 /* Construct a library search path (such as RPATH) containing the
198 libraries for a set of packages
199
200 Example:
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"
206 */
207 makeLibraryPath = makeSearchPathOutput "lib" "lib";
208
209 /* Construct an include search path (such as C_INCLUDE_PATH) containing the
210 header files for a set of packages or paths.
211
212 Example:
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"
218 */
219 makeIncludePath = makeSearchPathOutput "dev" "include";
220
221 /* Construct a binary search path (such as $PATH) containing the
222 binaries for a set of packages.
223
224 Example:
225 makeBinPath ["/root" "/usr" "/usr/local"]
226 => "/root/bin:/usr/bin:/usr/local/bin"
227 */
228 makeBinPath = makeSearchPathOutput "bin" "bin";
229
230 /* Normalize path, removing extraneous /s
231
232 Type: normalizePath :: string -> string
233
234 Example:
235 normalizePath "/a//b///c/"
236 => "/a/b/c/"
237 */
238 normalizePath = s:
239 warnIf
240 (isPath s)
241 ''
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.''
246 (
247 builtins.foldl'
248 (x: y: if y == "/" && hasSuffix "/" x then x else x+y)
249 ""
250 (stringToCharacters s)
251 );
252
253 /* Depending on the boolean `cond', return either the given string
254 or the empty string. Useful to concatenate against a bigger string.
255
256 Type: optionalString :: bool -> string -> string
257
258 Example:
259 optionalString true "some-string"
260 => "some-string"
261 optionalString false "some-string"
262 => ""
263 */
264 optionalString =
265 # Condition
266 cond:
267 # String to return if condition is true
268 string: if cond then string else "";
269
270 /* Determine whether a string has given prefix.
271
272 Type: hasPrefix :: string -> string -> bool
273
274 Example:
275 hasPrefix "foo" "foobar"
276 => true
277 hasPrefix "foo" "barfoo"
278 => false
279 */
280 hasPrefix =
281 # Prefix to check for
282 pref:
283 # Input string
284 str:
285 # Before 23.05, paths would be copied to the store before converting them
286 # to strings and comparing. This was surprising and confusing.
287 warnIf
288 (isPath pref)
289 ''
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);
296
297 /* Determine whether a string has given suffix.
298
299 Type: hasSuffix :: string -> string -> bool
300
301 Example:
302 hasSuffix "foo" "foobar"
303 => false
304 hasSuffix "foo" "barfoo"
305 => true
306 */
307 hasSuffix =
308 # Suffix to check for
309 suffix:
310 # Input string
311 content:
312 let
313 lenContent = stringLength content;
314 lenSuffix = stringLength suffix;
315 in
316 # Before 23.05, paths would be copied to the store before converting them
317 # to strings and comparing. This was surprising and confusing.
318 warnIf
319 (isPath suffix)
320 ''
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.''
325 (
326 lenContent >= lenSuffix
327 && substring (lenContent - lenSuffix) lenContent content == suffix
328 );
329
330 /* Determine whether a string contains the given infix
331
332 Type: hasInfix :: string -> string -> bool
333
334 Example:
335 hasInfix "bc" "abcd"
336 => true
337 hasInfix "ab" "abcd"
338 => true
339 hasInfix "cd" "abcd"
340 => true
341 hasInfix "foo" "abcd"
342 => false
343 */
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.
347 warnIf
348 (isPath infix)
349 ''
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);
355
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
362 handle unicode.
363
364 Type: stringToCharacters :: string -> [string]
365
366 Example:
367 stringToCharacters ""
368 => [ ]
369 stringToCharacters "abc"
370 => [ "a" "b" "c" ]
371 stringToCharacters "🦄"
372 => [ "�" "�" "�" "�" ]
373 */
374 stringToCharacters = s:
375 genList (p: substring p 1 s) (stringLength s);
376
377 /* Manipulate a string character by character and replace them by
378 strings before concatenating the results.
379
380 Type: stringAsChars :: (string -> string) -> string -> string
381
382 Example:
383 stringAsChars (x: if x == "a" then "i" else x) "nax"
384 => "nix"
385 */
386 stringAsChars =
387 # Function to map over each individual character
388 f:
389 # Input string
390 s: concatStrings (
391 map f (stringToCharacters s)
392 );
393
394 /* Convert char to ascii value, must be in printable range
395
396 Type: charToInt :: string -> int
397
398 Example:
399 charToInt "A"
400 => 65
401 charToInt "("
402 => 40
403
404 */
405 charToInt = c: builtins.getAttr c asciiTable;
406
407 /* Escape occurrence of the elements of `list` in `string` by
408 prefixing it with a backslash.
409
410 Type: escape :: [string] -> string -> string
411
412 Example:
413 escape ["(" ")"] "(foo)"
414 => "\\(foo\\)"
415 */
416 escape = list: replaceStrings list (map (c: "\\${c}") list);
417
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.
421
422 Type: escapeC = [string] -> string -> string
423
424 Example:
425 escapeC [" "] "foo bar"
426 => "foo\\x20bar"
427
428 */
429 escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
430
431 /* Escape the string so it can be safely placed inside a URL
432 query.
433
434 Type: escapeURL :: string -> string
435
436 Example:
437 escapeURL "foo/bar baz"
438 => "foo%2Fbar%20baz"
439 */
440 escapeURL = let
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;
443 in
444 replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape);
445
446 /* Quote string to be used safely within the Bourne shell.
447
448 Type: escapeShellArg :: string -> string
449
450 Example:
451 escapeShellArg "esc'ape\nme"
452 => "'esc'\\''ape\nme'"
453 */
454 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
455
456 /* Quote all arguments to be safely passed to the Bourne shell.
457
458 Type: escapeShellArgs :: [string] -> string
459
460 Example:
461 escapeShellArgs ["one" "two three" "four'five"]
462 => "'one' 'two three' 'four'\\''five'"
463 */
464 escapeShellArgs = concatMapStringsSep " " escapeShellArg;
465
466 /* Test whether the given name is a valid POSIX shell variable name.
467
468 Type: string -> bool
469
470 Example:
471 isValidPosixName "foo_bar000"
472 => true
473 isValidPosixName "0-bad.jpg"
474 => false
475 */
476 isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
477
478 /* Translate a Nix value into a shell variable declaration, with proper escaping.
479
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.
484
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).
487
488 Type: string -> (string | listOf string | attrsOf string) -> string
489
490 Example:
491 ''
492 ${toShellVar "foo" "some string"}
493 [[ "$foo" == "some string" ]]
494 ''
495 */
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}"
502 ) value)
503 })"
504 else if isList value then
505 "declare -a ${name}=(${escapeShellArgs value})"
506 else
507 "${name}=${escapeShellArg value}"
508 );
509
510 /* Translate an attribute set into corresponding shell variable declarations
511 using `toShellVar`.
512
513 Type: attrsOf (string | listOf string | attrsOf string) -> string
514
515 Example:
516 let
517 foo = "value";
518 bar = foo;
519 in ''
520 ${toShellVars { inherit foo bar; }}
521 [[ "$foo" == "$bar" ]]
522 ''
523 */
524 toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
525
526 /* Turn a string into a Nix expression representing that string
527
528 Type: string -> string
529
530 Example:
531 escapeNixString "hello\${}\n"
532 => "\"hello\\\${}\\n\""
533 */
534 escapeNixString = s: escape ["$"] (toJSON s);
535
536 /* Turn a string into an exact regular expression
537
538 Type: string -> string
539
540 Example:
541 escapeRegex "[^a-z]*"
542 => "\\[\\^a-z]\\*"
543 */
544 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
545
546 /* Quotes a string if it can't be used as an identifier directly.
547
548 Type: string -> string
549
550 Example:
551 escapeNixIdentifier "hello"
552 => "hello"
553 escapeNixIdentifier "0abc"
554 => "\"0abc\""
555 */
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;
560
561 /* Escapes a string such that it is safe to include verbatim in an XML
562 document.
563
564 Type: string -> string
565
566 Example:
567 escapeXML ''"test" 'test' < & >''
568 => ""test" 'test' < & >"
569 */
570 escapeXML = builtins.replaceStrings
571 ["\"" "'" "<" ">" "&"]
572 [""" "'" "<" ">" "&"];
573
574 # warning added 12-12-2022
575 replaceChars = lib.warn "lib.replaceChars is a deprecated alias of lib.replaceStrings." builtins.replaceStrings;
576
577 # Case conversion utilities.
578 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
579 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
580
581 /* Converts an ASCII string to lower-case.
582
583 Type: toLower :: string -> string
584
585 Example:
586 toLower "HOME"
587 => "home"
588 */
589 toLower = replaceStrings upperChars lowerChars;
590
591 /* Converts an ASCII string to upper-case.
592
593 Type: toUpper :: string -> string
594
595 Example:
596 toUpper "home"
597 => "HOME"
598 */
599 toUpper = replaceStrings lowerChars upperChars;
600
601 /* Appends string context from another string. This is an implementation
602 detail of Nix and should be used carefully.
603
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
607 inputSrcs.
608
609 Example:
610 pkgs = import <nixpkgs> { };
611 addContextFrom pkgs.coreutils "bar"
612 => "bar"
613 */
614 addContextFrom = a: b: substring 0 0 a + b;
615
616 /* Cut a string with a separator and produces a list of strings which
617 were separated by this separator.
618
619 Example:
620 splitString "." "foo.bar.baz"
621 => [ "foo" "bar" "baz" ]
622 splitString "/" "/usr/local/bin"
623 => [ "" "usr" "local" "bin" ]
624 */
625 splitString = sep: s:
626 let
627 splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s));
628 in
629 map (addContextFrom s) splits;
630
631 /* Return a string without the specified prefix, if the prefix matches.
632
633 Type: string -> string -> string
634
635 Example:
636 removePrefix "foo." "foo.bar.baz"
637 => "bar.baz"
638 removePrefix "xxx" "foo.bar.baz"
639 => "foo.bar.baz"
640 */
641 removePrefix =
642 # Prefix to remove if it matches
643 prefix:
644 # Input string
645 str:
646 # Before 23.05, paths would be copied to the store before converting them
647 # to strings and comparing. This was surprising and confusing.
648 warnIf
649 (isPath prefix)
650 ''
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.''
655 (let
656 preLen = stringLength prefix;
657 in
658 if substring 0 preLen str == prefix then
659 # -1 will take the string until the end
660 substring preLen (-1) str
661 else
662 str);
663
664 /* Return a string without the specified suffix, if the suffix matches.
665
666 Type: string -> string -> string
667
668 Example:
669 removeSuffix "front" "homefront"
670 => "home"
671 removeSuffix "xxx" "homefront"
672 => "homefront"
673 */
674 removeSuffix =
675 # Suffix to remove if it matches
676 suffix:
677 # Input string
678 str:
679 # Before 23.05, paths would be copied to the store before converting them
680 # to strings and comparing. This was surprising and confusing.
681 warnIf
682 (isPath suffix)
683 ''
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.''
688 (let
689 sufLen = stringLength suffix;
690 sLen = stringLength str;
691 in
692 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
693 substring 0 (sLen - sufLen) str
694 else
695 str);
696
697 /* Return true if string v1 denotes a version older than v2.
698
699 Example:
700 versionOlder "1.1" "1.2"
701 => true
702 versionOlder "1.1" "1.1"
703 => false
704 */
705 versionOlder = v1: v2: compareVersions v2 v1 == 1;
706
707 /* Return true if string v1 denotes a version equal to or newer than v2.
708
709 Example:
710 versionAtLeast "1.1" "1.0"
711 => true
712 versionAtLeast "1.1" "1.1"
713 => true
714 versionAtLeast "1.1" "1.2"
715 => false
716 */
717 versionAtLeast = v1: v2: !versionOlder v1 v2;
718
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
721 argument.
722
723 Example:
724 getName "youtube-dl-2016.01.01"
725 => "youtube-dl"
726 getName pkgs.youtube-dl
727 => "youtube-dl"
728 */
729 getName = let
730 parse = drv: (parseDrvName drv).name;
731 in x:
732 if isString x
733 then parse x
734 else x.pname or (parse x.name);
735
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
738 argument.
739
740 Example:
741 getVersion "youtube-dl-2016.01.01"
742 => "2016.01.01"
743 getVersion pkgs.youtube-dl
744 => "2016.01.01"
745 */
746 getVersion = let
747 parse = drv: (parseDrvName drv).version;
748 in x:
749 if isString x
750 then parse x
751 else x.version or (parse x.name);
752
753 /* Extract name with version from URL. Ask for separator which is
754 supposed to start extension.
755
756 Example:
757 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
758 => "nix"
759 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
760 => "nix-1.7-x86"
761 */
762 nameFromURL = url: sep:
763 let
764 components = splitString "/" url;
765 filename = lib.last components;
766 name = head (splitString sep filename);
767 in assert name != filename; name;
768
769 /* Create a "-D<feature>:<type>=<value>" string that can be passed to typical
770 CMake invocations.
771
772 Type: cmakeOptionType :: string -> string -> string -> string
773
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
780
781 Example:
782 cmakeOptionType "string" "ENGINE" "sdl2"
783 => "-DENGINE:STRING=sdl2"
784 */
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}";
792
793 /* Create a -D<condition>={TRUE,FALSE} string that can be passed to typical
794 CMake invocations.
795
796 Type: cmakeBool :: string -> bool -> string
797
798 @param condition The condition to be made true or false
799 @param flag The controlling flag of the condition
800
801 Example:
802 cmakeBool "ENABLE_STATIC_LIBS" false
803 => "-DENABLESTATIC_LIBS:BOOL=FALSE"
804 */
805 cmakeBool = condition: flag:
806 assert (lib.isString condition);
807 assert (lib.isBool flag);
808 cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag));
809
810 /* Create a -D<feature>:STRING=<value> string that can be passed to typical
811 CMake invocations.
812 This is the most typical usage, so it deserves a special case.
813
814 Type: cmakeFeature :: string -> string -> string
815
816 @param condition The condition to be made true or false
817 @param flag The controlling flag of the condition
818
819 Example:
820 cmakeFeature "MODULES" "badblock"
821 => "-DMODULES:STRING=badblock"
822 */
823 cmakeFeature = feature: value:
824 assert (lib.isString feature);
825 assert (lib.isString value);
826 cmakeOptionType "string" feature value;
827
828 /* Create a -D<feature>=<value> string that can be passed to typical Meson
829 invocations.
830
831 Type: mesonOption :: string -> string -> string
832
833 @param feature The feature to be set
834 @param value The desired value
835
836 Example:
837 mesonOption "engine" "opengl"
838 => "-Dengine=opengl"
839 */
840 mesonOption = feature: value:
841 assert (lib.isString feature);
842 assert (lib.isString value);
843 "-D${feature}=${value}";
844
845 /* Create a -D<condition>={true,false} string that can be passed to typical
846 Meson invocations.
847
848 Type: mesonBool :: string -> bool -> string
849
850 @param condition The condition to be made true or false
851 @param flag The controlling flag of the condition
852
853 Example:
854 mesonBool "hardened" true
855 => "-Dhardened=true"
856 mesonBool "static" false
857 => "-Dstatic=false"
858 */
859 mesonBool = condition: flag:
860 assert (lib.isString condition);
861 assert (lib.isBool flag);
862 mesonOption condition (lib.boolToString flag);
863
864 /* Create a -D<feature>={enabled,disabled} string that can be passed to
865 typical Meson invocations.
866
867 Type: mesonEnable :: string -> bool -> string
868
869 @param feature The feature to be enabled or disabled
870 @param flag The controlling flag
871
872 Example:
873 mesonEnable "docs" true
874 => "-Ddocs=enabled"
875 mesonEnable "savage" false
876 => "-Dsavage=disabled"
877 */
878 mesonEnable = feature: flag:
879 assert (lib.isString feature);
880 assert (lib.isBool flag);
881 mesonOption feature (if flag then "enabled" else "disabled");
882
883 /* Create an --{enable,disable}-<feature> string that can be passed to
884 standard GNU Autoconf scripts.
885
886 Example:
887 enableFeature true "shared"
888 => "--enable-shared"
889 enableFeature false "shared"
890 => "--disable-shared"
891 */
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}";
896
897 /* Create an --{enable-<feature>=<value>,disable-<feature>} string that can be passed to
898 standard GNU Autoconf scripts.
899
900 Example:
901 enableFeatureAs true "shared" "foo"
902 => "--enable-shared=foo"
903 enableFeatureAs false "shared" (throw "ignored")
904 => "--disable-shared"
905 */
906 enableFeatureAs = flag: feature: value:
907 enableFeature flag feature + optionalString flag "=${value}";
908
909 /* Create an --{with,without}-<feature> string that can be passed to
910 standard GNU Autoconf scripts.
911
912 Example:
913 withFeature true "shared"
914 => "--with-shared"
915 withFeature false "shared"
916 => "--without-shared"
917 */
918 withFeature = flag: feature:
919 assert isString feature; # e.g. passing openssl instead of "openssl"
920 "--${if flag then "with" else "without"}-${feature}";
921
922 /* Create an --{with-<feature>=<value>,without-<feature>} string that can be passed to
923 standard GNU Autoconf scripts.
924
925 Example:
926 withFeatureAs true "shared" "foo"
927 => "--with-shared=foo"
928 withFeatureAs false "shared" (throw "ignored")
929 => "--without-shared"
930 */
931 withFeatureAs = flag: feature: value:
932 withFeature flag feature + optionalString flag "=${value}";
933
934 /* Create a fixed width string with additional prefix to match
935 required width.
936
937 This function will fail if the input string is longer than the
938 requested length.
939
940 Type: fixedWidthString :: int -> string -> string -> string
941
942 Example:
943 fixedWidthString 5 "0" (toString 15)
944 => "00015"
945 */
946 fixedWidthString = width: filler: str:
947 let
948 strw = lib.stringLength str;
949 reqWidth = width - (lib.stringLength filler);
950 in
951 assert lib.assertMsg (strw <= width)
952 "fixedWidthString: requested string length (${
953 toString width}) must not be shorter than actual length (${
954 toString strw})";
955 if strw == width then str else filler + fixedWidthString reqWidth filler str;
956
957 /* Format a number adding leading zeroes up to fixed width.
958
959 Example:
960 fixedWidthNumber 5 15
961 => "00015"
962 */
963 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
964
965 /* Convert a float to a string, but emit a warning when precision is lost
966 during the conversion
967
968 Example:
969 floatToString 0.000001
970 => "0.000001"
971 floatToString 0.0000001
972 => trace: warning: Imprecise conversion from float to string 0.000000
973 "0.000000"
974 */
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}"
979 result;
980
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;
986
987 /* Check whether a list or other value can be passed to toString.
988
989 Many types of value are coercible to string this way, including int, float,
990 null, bool, list of similarly coercible values.
991 */
992 isConvertibleWithToString = let
993 types = [ "null" "int" "float" "bool" ];
994 in x:
995 isStringLike x ||
996 elem (typeOf x) types ||
997 (isList x && lib.all isConvertibleWithToString x);
998
999 /* Check whether a value can be coerced to a string.
1000 The value must be a string, path, or attribute set.
1001
1002 String-like values can be used without explicit conversion in
1003 string interpolations and in most functions that expect a string.
1004 */
1005 isStringLike = x:
1006 isString x ||
1007 isPath x ||
1008 x ? outPath ||
1009 x ? __toString;
1010
1011 /* Check whether a value is a store path.
1012
1013 Example:
1014 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
1015 => false
1016 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
1017 => true
1018 isStorePath pkgs.python
1019 => true
1020 isStorePath [] || isStorePath 42 || isStorePath {} || …
1021 => false
1022 */
1023 isStorePath = x:
1024 if isStringLike x then
1025 let str = toString x; in
1026 substring 0 1 str == "/"
1027 && dirOf str == storeDir
1028 else
1029 false;
1030
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.
1033
1034 Type: string -> int
1035
1036 Example:
1037
1038 toInt "1337"
1039 => 1337
1040
1041 toInt "-4"
1042 => -4
1043
1044 toInt " 123 "
1045 => 123
1046
1047 toInt "00024"
1048 => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
1049
1050 toInt "3.14"
1051 => error: floating point JSON numbers are not supported
1052 */
1053 toInt =
1054 let
1055 matchStripInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*";
1056 matchLeadingZero = match "0[[:digit:]]+";
1057 in
1058 str:
1059 let
1060 # RegEx: Match any leading whitespace, possibly a '-', one or more digits,
1061 # and finally match any trailing whitespace.
1062 strippedInput = matchStripInput str;
1063
1064 # RegEx: Match a leading '0' then one or more digits.
1065 isLeadingZero = matchLeadingZero (head strippedInput) == [];
1066
1067 # Attempt to parse input
1068 parsedInput = fromJSON (head strippedInput);
1069
1070 generalError = "toInt: Could not convert ${escapeNixString str} to int.";
1071
1072 in
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
1082 # Return result.
1083 else parsedInput;
1084
1085
1086 /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
1087
1088 Type: string -> int
1089
1090 Example:
1091 toIntBase10 "1337"
1092 => 1337
1093
1094 toIntBase10 "-4"
1095 => -4
1096
1097 toIntBase10 " 123 "
1098 => 123
1099
1100 toIntBase10 "00024"
1101 => 24
1102
1103 toIntBase10 "3.14"
1104 => error: floating point JSON numbers are not supported
1105 */
1106 toIntBase10 =
1107 let
1108 matchStripInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*";
1109 matchZero = match "0+";
1110 in
1111 str:
1112 let
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;
1117
1118 # RegEx: Match at least one '0'.
1119 isZero = matchZero (head strippedInput) == [];
1120
1121 # Attempt to parse input
1122 parsedInput = fromJSON (head strippedInput);
1123
1124 generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
1125
1126 in
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.
1131 else if isZero
1132 then 0
1133 # Error if parse function fails.
1134 else if !isInt parsedInput
1135 then throw generalError
1136 # Return result.
1137 else parsedInput;
1138
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.
1142
1143 NOTE: This function is not performant and should be avoided.
1144
1145 Example:
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" ]
1153 */
1154 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead."
1155 (rootPath: file:
1156 let
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;
1161 in
1162 absolutePaths);
1163
1164 /* Read the contents of a file removing the trailing \n
1165
1166 Type: fileContents :: path -> string
1167
1168 Example:
1169 $ echo "1.0" > ./version
1170
1171 fileContents ./version
1172 => "1.0"
1173 */
1174 fileContents = file: removeSuffix "\n" (readFile file);
1175
1176
1177 /* Creates a valid derivation name from a potentially invalid one.
1178
1179 Type: sanitizeDerivationName :: String -> String
1180
1181 Example:
1182 sanitizeDerivationName "../hello.bar # foo"
1183 => "-hello.bar-foo"
1184 sanitizeDerivationName ""
1185 => "unknown"
1186 sanitizeDerivationName pkgs.hello
1187 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
1188 */
1189 sanitizeDerivationName =
1190 let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
1191 in
1192 string:
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)
1212 ];
1213
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
1217
1218 Type: levenshtein :: string -> string -> int
1219
1220 Example:
1221 levenshtein "foo" "foo"
1222 => 0
1223 levenshtein "book" "hook"
1224 => 1
1225 levenshtein "hello" "Heyo"
1226 => 3
1227 */
1228 levenshtein = a: b: let
1229 # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
1230 arr = lib.genList (i:
1231 lib.genList (j:
1232 dist i j
1233 ) (stringLength b + 1)
1234 ) (stringLength a + 1);
1235 d = x: y: lib.elemAt (lib.elemAt arr x) y;
1236 dist = i: j:
1237 let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
1238 then 0 else 1;
1239 in
1240 if j == 0 then i
1241 else if i == 0 then j
1242 else lib.min
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);
1246
1247 /* Returns the length of the prefix common to both strings.
1248 */
1249 commonPrefixLength = a: b:
1250 let
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;
1253 in go 0;
1254
1255 /* Returns the length of the suffix common to both strings.
1256 */
1257 commonSuffixLength = a: b:
1258 let
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;
1261 in go 0;
1262
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
1265
1266 Type: levenshteinAtMost :: int -> string -> string -> bool
1267
1268 Example:
1269 levenshteinAtMost 0 "foo" "foo"
1270 => true
1271 levenshteinAtMost 1 "foo" "boa"
1272 => false
1273 levenshteinAtMost 2 "foo" "boa"
1274 => true
1275 levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1276 => false
1277 levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1278 => true
1279
1280 */
1281 levenshteinAtMost = let
1282 infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1283
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
1289 # implementation.
1290 infixDifferAtMost2 = x: y:
1291 let
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
1296 diff = xlen - ylen;
1297
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;
1301
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;
1307 in
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
1318 # vice versa
1319 # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1320 else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1321
1322 in k: if k <= 0 then a: b: a == b else
1323 let f = a: b:
1324 let
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;
1332 in
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;
1340 in f;
1341}