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 or (separator: list:
99 lib.foldl' (x: y: x + y) "" (intersperse separator list));
100
101 /* Maps a function over a list of strings and then concatenates the
102 result with the specified separator interspersed between
103 elements.
104
105 Type: concatMapStringsSep :: string -> (a -> string) -> [a] -> string
106
107 Example:
108 concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"]
109 => "FOO-BAR-BAZ"
110 */
111 concatMapStringsSep =
112 # Separator to add between elements
113 sep:
114 # Function to map over the list
115 f:
116 # List of input strings
117 list: concatStringsSep sep (map f list);
118
119 /* Same as `concatMapStringsSep`, but the mapping function
120 additionally receives the position of its argument.
121
122 Type: concatIMapStringsSep :: string -> (int -> a -> string) -> [a] -> string
123
124 Example:
125 concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
126 => "6-3-2"
127 */
128 concatImapStringsSep =
129 # Separator to add between elements
130 sep:
131 # Function that receives elements and their positions
132 f:
133 # List of input strings
134 list: concatStringsSep sep (lib.imap1 f list);
135
136 /* Concatenate a list of strings, adding a newline at the end of each one.
137 Defined as `concatMapStrings (s: s + "\n")`.
138
139 Type: concatLines :: [string] -> string
140
141 Example:
142 concatLines [ "foo" "bar" ]
143 => "foo\nbar\n"
144 */
145 concatLines = concatMapStrings (s: s + "\n");
146
147 /*
148 Replicate a string n times,
149 and concatenate the parts into a new string.
150
151 Type: replicate :: int -> string -> string
152
153 Example:
154 replicate 3 "v"
155 => "vvv"
156 replicate 5 "hello"
157 => "hellohellohellohellohello"
158 */
159 replicate = n: s: concatStrings (lib.lists.replicate n s);
160
161 /* Construct a Unix-style, colon-separated search path consisting of
162 the given `subDir` appended to each of the given paths.
163
164 Type: makeSearchPath :: string -> [string] -> string
165
166 Example:
167 makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
168 => "/root/bin:/usr/bin:/usr/local/bin"
169 makeSearchPath "bin" [""]
170 => "/bin"
171 */
172 makeSearchPath =
173 # Directory name to append
174 subDir:
175 # List of base paths
176 paths:
177 concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
178
179 /* Construct a Unix-style search path by appending the given
180 `subDir` to the specified `output` of each of the packages. If no
181 output by the given name is found, fallback to `.out` and then to
182 the default.
183
184 Type: string -> string -> [package] -> string
185
186 Example:
187 makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
188 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
189 */
190 makeSearchPathOutput =
191 # Package output to use
192 output:
193 # Directory name to append
194 subDir:
195 # List of packages
196 pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
197
198 /* Construct a library search path (such as RPATH) containing the
199 libraries for a set of packages
200
201 Example:
202 makeLibraryPath [ "/usr" "/usr/local" ]
203 => "/usr/lib:/usr/local/lib"
204 pkgs = import <nixpkgs> { }
205 makeLibraryPath [ pkgs.openssl pkgs.zlib ]
206 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
207 */
208 makeLibraryPath = makeSearchPathOutput "lib" "lib";
209
210 /* Construct a binary search path (such as $PATH) containing the
211 binaries for a set of packages.
212
213 Example:
214 makeBinPath ["/root" "/usr" "/usr/local"]
215 => "/root/bin:/usr/bin:/usr/local/bin"
216 */
217 makeBinPath = makeSearchPathOutput "bin" "bin";
218
219 /* Normalize path, removing extraneous /s
220
221 Type: normalizePath :: string -> string
222
223 Example:
224 normalizePath "/a//b///c/"
225 => "/a/b/c/"
226 */
227 normalizePath = s:
228 warnIf
229 (isPath s)
230 ''
231 lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported.
232 Path values are always normalised in Nix, so there's no need to call this function on them.
233 This function also copies the path to the Nix store and returns the store path, the same as "''${path}" will, which may not be what you want.
234 This behavior is deprecated and will throw an error in the future.''
235 (
236 builtins.foldl'
237 (x: y: if y == "/" && hasSuffix "/" x then x else x+y)
238 ""
239 (stringToCharacters s)
240 );
241
242 /* Depending on the boolean `cond', return either the given string
243 or the empty string. Useful to concatenate against a bigger string.
244
245 Type: optionalString :: bool -> string -> string
246
247 Example:
248 optionalString true "some-string"
249 => "some-string"
250 optionalString false "some-string"
251 => ""
252 */
253 optionalString =
254 # Condition
255 cond:
256 # String to return if condition is true
257 string: if cond then string else "";
258
259 /* Determine whether a string has given prefix.
260
261 Type: hasPrefix :: string -> string -> bool
262
263 Example:
264 hasPrefix "foo" "foobar"
265 => true
266 hasPrefix "foo" "barfoo"
267 => false
268 */
269 hasPrefix =
270 # Prefix to check for
271 pref:
272 # Input string
273 str:
274 # Before 23.05, paths would be copied to the store before converting them
275 # to strings and comparing. This was surprising and confusing.
276 warnIf
277 (isPath pref)
278 ''
279 lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported.
280 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
281 This function also copies the path to the Nix store, which may not be what you want.
282 This behavior is deprecated and will throw an error in the future.
283 You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.''
284 (substring 0 (stringLength pref) str == pref);
285
286 /* Determine whether a string has given suffix.
287
288 Type: hasSuffix :: string -> string -> bool
289
290 Example:
291 hasSuffix "foo" "foobar"
292 => false
293 hasSuffix "foo" "barfoo"
294 => true
295 */
296 hasSuffix =
297 # Suffix to check for
298 suffix:
299 # Input string
300 content:
301 let
302 lenContent = stringLength content;
303 lenSuffix = stringLength suffix;
304 in
305 # Before 23.05, paths would be copied to the store before converting them
306 # to strings and comparing. This was surprising and confusing.
307 warnIf
308 (isPath suffix)
309 ''
310 lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
311 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
312 This function also copies the path to the Nix store, which may not be what you want.
313 This behavior is deprecated and will throw an error in the future.''
314 (
315 lenContent >= lenSuffix
316 && substring (lenContent - lenSuffix) lenContent content == suffix
317 );
318
319 /* Determine whether a string contains the given infix
320
321 Type: hasInfix :: string -> string -> bool
322
323 Example:
324 hasInfix "bc" "abcd"
325 => true
326 hasInfix "ab" "abcd"
327 => true
328 hasInfix "cd" "abcd"
329 => true
330 hasInfix "foo" "abcd"
331 => false
332 */
333 hasInfix = infix: content:
334 # Before 23.05, paths would be copied to the store before converting them
335 # to strings and comparing. This was surprising and confusing.
336 warnIf
337 (isPath infix)
338 ''
339 lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported.
340 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
341 This function also copies the path to the Nix store, which may not be what you want.
342 This behavior is deprecated and will throw an error in the future.''
343 (builtins.match ".*${escapeRegex infix}.*" "${content}" != null);
344
345 /* Convert a string to a list of characters (i.e. singleton strings).
346 This allows you to, e.g., map a function over each character. However,
347 note that this will likely be horribly inefficient; Nix is not a
348 general purpose programming language. Complex string manipulations
349 should, if appropriate, be done in a derivation.
350 Also note that Nix treats strings as a list of bytes and thus doesn't
351 handle unicode.
352
353 Type: stringToCharacters :: string -> [string]
354
355 Example:
356 stringToCharacters ""
357 => [ ]
358 stringToCharacters "abc"
359 => [ "a" "b" "c" ]
360 stringToCharacters "🦄"
361 => [ "�" "�" "�" "�" ]
362 */
363 stringToCharacters = s:
364 genList (p: substring p 1 s) (stringLength s);
365
366 /* Manipulate a string character by character and replace them by
367 strings before concatenating the results.
368
369 Type: stringAsChars :: (string -> string) -> string -> string
370
371 Example:
372 stringAsChars (x: if x == "a" then "i" else x) "nax"
373 => "nix"
374 */
375 stringAsChars =
376 # Function to map over each individual character
377 f:
378 # Input string
379 s: concatStrings (
380 map f (stringToCharacters s)
381 );
382
383 /* Convert char to ascii value, must be in printable range
384
385 Type: charToInt :: string -> int
386
387 Example:
388 charToInt "A"
389 => 65
390 charToInt "("
391 => 40
392
393 */
394 charToInt = c: builtins.getAttr c asciiTable;
395
396 /* Escape occurrence of the elements of `list` in `string` by
397 prefixing it with a backslash.
398
399 Type: escape :: [string] -> string -> string
400
401 Example:
402 escape ["(" ")"] "(foo)"
403 => "\\(foo\\)"
404 */
405 escape = list: replaceStrings list (map (c: "\\${c}") list);
406
407 /* Escape occurrence of the element of `list` in `string` by
408 converting to its ASCII value and prefixing it with \\x.
409 Only works for printable ascii characters.
410
411 Type: escapeC = [string] -> string -> string
412
413 Example:
414 escapeC [" "] "foo bar"
415 => "foo\\x20bar"
416
417 */
418 escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
419
420 /* Escape the string so it can be safely placed inside a URL
421 query.
422
423 Type: escapeURL :: string -> string
424
425 Example:
426 escapeURL "foo/bar baz"
427 => "foo%2Fbar%20baz"
428 */
429 escapeURL = let
430 unreserved = [ "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "-" "_" "." "~" ];
431 toEscape = builtins.removeAttrs asciiTable unreserved;
432 in
433 replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape);
434
435 /* Quote string to be used safely within the Bourne shell.
436
437 Type: escapeShellArg :: string -> string
438
439 Example:
440 escapeShellArg "esc'ape\nme"
441 => "'esc'\\''ape\nme'"
442 */
443 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
444
445 /* Quote all arguments to be safely passed to the Bourne shell.
446
447 Type: escapeShellArgs :: [string] -> string
448
449 Example:
450 escapeShellArgs ["one" "two three" "four'five"]
451 => "'one' 'two three' 'four'\\''five'"
452 */
453 escapeShellArgs = concatMapStringsSep " " escapeShellArg;
454
455 /* Test whether the given name is a valid POSIX shell variable name.
456
457 Type: string -> bool
458
459 Example:
460 isValidPosixName "foo_bar000"
461 => true
462 isValidPosixName "0-bad.jpg"
463 => false
464 */
465 isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
466
467 /* Translate a Nix value into a shell variable declaration, with proper escaping.
468
469 The value can be a string (mapped to a regular variable), a list of strings
470 (mapped to a Bash-style array) or an attribute set of strings (mapped to a
471 Bash-style associative array). Note that "string" includes string-coercible
472 values like paths or derivations.
473
474 Strings are translated into POSIX sh-compatible code; lists and attribute sets
475 assume a shell that understands Bash syntax (e.g. Bash or ZSH).
476
477 Type: string -> (string | listOf string | attrsOf string) -> string
478
479 Example:
480 ''
481 ${toShellVar "foo" "some string"}
482 [[ "$foo" == "some string" ]]
483 ''
484 */
485 toShellVar = name: value:
486 lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
487 if isAttrs value && ! isStringLike value then
488 "declare -A ${name}=(${
489 concatStringsSep " " (lib.mapAttrsToList (n: v:
490 "[${escapeShellArg n}]=${escapeShellArg v}"
491 ) value)
492 })"
493 else if isList value then
494 "declare -a ${name}=(${escapeShellArgs value})"
495 else
496 "${name}=${escapeShellArg value}"
497 );
498
499 /* Translate an attribute set into corresponding shell variable declarations
500 using `toShellVar`.
501
502 Type: attrsOf (string | listOf string | attrsOf string) -> string
503
504 Example:
505 let
506 foo = "value";
507 bar = foo;
508 in ''
509 ${toShellVars { inherit foo bar; }}
510 [[ "$foo" == "$bar" ]]
511 ''
512 */
513 toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
514
515 /* Turn a string into a Nix expression representing that string
516
517 Type: string -> string
518
519 Example:
520 escapeNixString "hello\${}\n"
521 => "\"hello\\\${}\\n\""
522 */
523 escapeNixString = s: escape ["$"] (toJSON s);
524
525 /* Turn a string into an exact regular expression
526
527 Type: string -> string
528
529 Example:
530 escapeRegex "[^a-z]*"
531 => "\\[\\^a-z]\\*"
532 */
533 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
534
535 /* Quotes a string if it can't be used as an identifier directly.
536
537 Type: string -> string
538
539 Example:
540 escapeNixIdentifier "hello"
541 => "hello"
542 escapeNixIdentifier "0abc"
543 => "\"0abc\""
544 */
545 escapeNixIdentifier = s:
546 # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
547 if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
548 then s else escapeNixString s;
549
550 /* Escapes a string such that it is safe to include verbatim in an XML
551 document.
552
553 Type: string -> string
554
555 Example:
556 escapeXML ''"test" 'test' < & >''
557 => ""test" 'test' < & >"
558 */
559 escapeXML = builtins.replaceStrings
560 ["\"" "'" "<" ">" "&"]
561 [""" "'" "<" ">" "&"];
562
563 # warning added 12-12-2022
564 replaceChars = lib.warn "replaceChars is a deprecated alias of replaceStrings, replace usages of it with replaceStrings." builtins.replaceStrings;
565
566 # Case conversion utilities.
567 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
568 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
569
570 /* Converts an ASCII string to lower-case.
571
572 Type: toLower :: string -> string
573
574 Example:
575 toLower "HOME"
576 => "home"
577 */
578 toLower = replaceStrings upperChars lowerChars;
579
580 /* Converts an ASCII string to upper-case.
581
582 Type: toUpper :: string -> string
583
584 Example:
585 toUpper "home"
586 => "HOME"
587 */
588 toUpper = replaceStrings lowerChars upperChars;
589
590 /* Appends string context from another string. This is an implementation
591 detail of Nix and should be used carefully.
592
593 Strings in Nix carry an invisible `context` which is a list of strings
594 representing store paths. If the string is later used in a derivation
595 attribute, the derivation will properly populate the inputDrvs and
596 inputSrcs.
597
598 Example:
599 pkgs = import <nixpkgs> { };
600 addContextFrom pkgs.coreutils "bar"
601 => "bar"
602 */
603 addContextFrom = a: b: substring 0 0 a + b;
604
605 /* Cut a string with a separator and produces a list of strings which
606 were separated by this separator.
607
608 Example:
609 splitString "." "foo.bar.baz"
610 => [ "foo" "bar" "baz" ]
611 splitString "/" "/usr/local/bin"
612 => [ "" "usr" "local" "bin" ]
613 */
614 splitString = sep: s:
615 let
616 splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s));
617 in
618 map (addContextFrom s) splits;
619
620 /* Return a string without the specified prefix, if the prefix matches.
621
622 Type: string -> string -> string
623
624 Example:
625 removePrefix "foo." "foo.bar.baz"
626 => "bar.baz"
627 removePrefix "xxx" "foo.bar.baz"
628 => "foo.bar.baz"
629 */
630 removePrefix =
631 # Prefix to remove if it matches
632 prefix:
633 # Input string
634 str:
635 # Before 23.05, paths would be copied to the store before converting them
636 # to strings and comparing. This was surprising and confusing.
637 warnIf
638 (isPath prefix)
639 ''
640 lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported.
641 There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case.
642 This function also copies the path to the Nix store, which may not be what you want.
643 This behavior is deprecated and will throw an error in the future.''
644 (let
645 preLen = stringLength prefix;
646 in
647 if substring 0 preLen str == prefix then
648 # -1 will take the string until the end
649 substring preLen (-1) str
650 else
651 str);
652
653 /* Return a string without the specified suffix, if the suffix matches.
654
655 Type: string -> string -> string
656
657 Example:
658 removeSuffix "front" "homefront"
659 => "home"
660 removeSuffix "xxx" "homefront"
661 => "homefront"
662 */
663 removeSuffix =
664 # Suffix to remove if it matches
665 suffix:
666 # Input string
667 str:
668 # Before 23.05, paths would be copied to the store before converting them
669 # to strings and comparing. This was surprising and confusing.
670 warnIf
671 (isPath suffix)
672 ''
673 lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
674 There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case.
675 This function also copies the path to the Nix store, which may not be what you want.
676 This behavior is deprecated and will throw an error in the future.''
677 (let
678 sufLen = stringLength suffix;
679 sLen = stringLength str;
680 in
681 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
682 substring 0 (sLen - sufLen) str
683 else
684 str);
685
686 /* Return true if string v1 denotes a version older than v2.
687
688 Example:
689 versionOlder "1.1" "1.2"
690 => true
691 versionOlder "1.1" "1.1"
692 => false
693 */
694 versionOlder = v1: v2: compareVersions v2 v1 == 1;
695
696 /* Return true if string v1 denotes a version equal to or newer than v2.
697
698 Example:
699 versionAtLeast "1.1" "1.0"
700 => true
701 versionAtLeast "1.1" "1.1"
702 => true
703 versionAtLeast "1.1" "1.2"
704 => false
705 */
706 versionAtLeast = v1: v2: !versionOlder v1 v2;
707
708 /* This function takes an argument that's either a derivation or a
709 derivation's "name" attribute and extracts the name part from that
710 argument.
711
712 Example:
713 getName "youtube-dl-2016.01.01"
714 => "youtube-dl"
715 getName pkgs.youtube-dl
716 => "youtube-dl"
717 */
718 getName = x:
719 let
720 parse = drv: (parseDrvName drv).name;
721 in if isString x
722 then parse x
723 else x.pname or (parse x.name);
724
725 /* This function takes an argument that's either a derivation or a
726 derivation's "name" attribute and extracts the version part from that
727 argument.
728
729 Example:
730 getVersion "youtube-dl-2016.01.01"
731 => "2016.01.01"
732 getVersion pkgs.youtube-dl
733 => "2016.01.01"
734 */
735 getVersion = x:
736 let
737 parse = drv: (parseDrvName drv).version;
738 in if isString x
739 then parse x
740 else x.version or (parse x.name);
741
742 /* Extract name with version from URL. Ask for separator which is
743 supposed to start extension.
744
745 Example:
746 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
747 => "nix"
748 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
749 => "nix-1.7-x86"
750 */
751 nameFromURL = url: sep:
752 let
753 components = splitString "/" url;
754 filename = lib.last components;
755 name = head (splitString sep filename);
756 in assert name != filename; name;
757
758 /* Create a "-D<feature>:<type>=<value>" string that can be passed to typical
759 CMake invocations.
760
761 Type: cmakeOptionType :: string -> string -> string -> string
762
763 @param feature The feature to be set
764 @param type The type of the feature to be set, as described in
765 https://cmake.org/cmake/help/latest/command/set.html
766 the possible values (case insensitive) are:
767 BOOL FILEPATH PATH STRING INTERNAL
768 @param value The desired value
769
770 Example:
771 cmakeOptionType "string" "ENGINE" "sdl2"
772 => "-DENGINE:STRING=sdl2"
773 */
774 cmakeOptionType = type: feature: value:
775 assert (lib.elem (lib.toUpper type)
776 [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" ]);
777 assert (lib.isString feature);
778 assert (lib.isString value);
779 "-D${feature}:${lib.toUpper type}=${value}";
780
781 /* Create a -D<condition>={TRUE,FALSE} string that can be passed to typical
782 CMake invocations.
783
784 Type: cmakeBool :: string -> bool -> string
785
786 @param condition The condition to be made true or false
787 @param flag The controlling flag of the condition
788
789 Example:
790 cmakeBool "ENABLE_STATIC_LIBS" false
791 => "-DENABLESTATIC_LIBS:BOOL=FALSE"
792 */
793 cmakeBool = condition: flag:
794 assert (lib.isString condition);
795 assert (lib.isBool flag);
796 cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag));
797
798 /* Create a -D<feature>:STRING=<value> string that can be passed to typical
799 CMake invocations.
800 This is the most typical usage, so it deserves a special case.
801
802 Type: cmakeFeature :: string -> string -> string
803
804 @param condition The condition to be made true or false
805 @param flag The controlling flag of the condition
806
807 Example:
808 cmakeFeature "MODULES" "badblock"
809 => "-DMODULES:STRING=badblock"
810 */
811 cmakeFeature = feature: value:
812 assert (lib.isString feature);
813 assert (lib.isString value);
814 cmakeOptionType "string" feature value;
815
816 /* Create a -D<feature>=<value> string that can be passed to typical Meson
817 invocations.
818
819 Type: mesonOption :: string -> string -> string
820
821 @param feature The feature to be set
822 @param value The desired value
823
824 Example:
825 mesonOption "engine" "opengl"
826 => "-Dengine=opengl"
827 */
828 mesonOption = feature: value:
829 assert (lib.isString feature);
830 assert (lib.isString value);
831 "-D${feature}=${value}";
832
833 /* Create a -D<condition>={true,false} string that can be passed to typical
834 Meson invocations.
835
836 Type: mesonBool :: string -> bool -> string
837
838 @param condition The condition to be made true or false
839 @param flag The controlling flag of the condition
840
841 Example:
842 mesonBool "hardened" true
843 => "-Dhardened=true"
844 mesonBool "static" false
845 => "-Dstatic=false"
846 */
847 mesonBool = condition: flag:
848 assert (lib.isString condition);
849 assert (lib.isBool flag);
850 mesonOption condition (lib.boolToString flag);
851
852 /* Create a -D<feature>={enabled,disabled} string that can be passed to
853 typical Meson invocations.
854
855 Type: mesonEnable :: string -> bool -> string
856
857 @param feature The feature to be enabled or disabled
858 @param flag The controlling flag
859
860 Example:
861 mesonEnable "docs" true
862 => "-Ddocs=enabled"
863 mesonEnable "savage" false
864 => "-Dsavage=disabled"
865 */
866 mesonEnable = feature: flag:
867 assert (lib.isString feature);
868 assert (lib.isBool flag);
869 mesonOption feature (if flag then "enabled" else "disabled");
870
871 /* Create an --{enable,disable}-<feature> string that can be passed to
872 standard GNU Autoconf scripts.
873
874 Example:
875 enableFeature true "shared"
876 => "--enable-shared"
877 enableFeature false "shared"
878 => "--disable-shared"
879 */
880 enableFeature = flag: feature:
881 assert lib.isBool flag;
882 assert lib.isString feature; # e.g. passing openssl instead of "openssl"
883 "--${if flag then "enable" else "disable"}-${feature}";
884
885 /* Create an --{enable-<feature>=<value>,disable-<feature>} string that can be passed to
886 standard GNU Autoconf scripts.
887
888 Example:
889 enableFeatureAs true "shared" "foo"
890 => "--enable-shared=foo"
891 enableFeatureAs false "shared" (throw "ignored")
892 => "--disable-shared"
893 */
894 enableFeatureAs = flag: feature: value:
895 enableFeature flag feature + optionalString flag "=${value}";
896
897 /* Create an --{with,without}-<feature> string that can be passed to
898 standard GNU Autoconf scripts.
899
900 Example:
901 withFeature true "shared"
902 => "--with-shared"
903 withFeature false "shared"
904 => "--without-shared"
905 */
906 withFeature = flag: feature:
907 assert isString feature; # e.g. passing openssl instead of "openssl"
908 "--${if flag then "with" else "without"}-${feature}";
909
910 /* Create an --{with-<feature>=<value>,without-<feature>} string that can be passed to
911 standard GNU Autoconf scripts.
912
913 Example:
914 withFeatureAs true "shared" "foo"
915 => "--with-shared=foo"
916 withFeatureAs false "shared" (throw "ignored")
917 => "--without-shared"
918 */
919 withFeatureAs = flag: feature: value:
920 withFeature flag feature + optionalString flag "=${value}";
921
922 /* Create a fixed width string with additional prefix to match
923 required width.
924
925 This function will fail if the input string is longer than the
926 requested length.
927
928 Type: fixedWidthString :: int -> string -> string -> string
929
930 Example:
931 fixedWidthString 5 "0" (toString 15)
932 => "00015"
933 */
934 fixedWidthString = width: filler: str:
935 let
936 strw = lib.stringLength str;
937 reqWidth = width - (lib.stringLength filler);
938 in
939 assert lib.assertMsg (strw <= width)
940 "fixedWidthString: requested string length (${
941 toString width}) must not be shorter than actual length (${
942 toString strw})";
943 if strw == width then str else filler + fixedWidthString reqWidth filler str;
944
945 /* Format a number adding leading zeroes up to fixed width.
946
947 Example:
948 fixedWidthNumber 5 15
949 => "00015"
950 */
951 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
952
953 /* Convert a float to a string, but emit a warning when precision is lost
954 during the conversion
955
956 Example:
957 floatToString 0.000001
958 => "0.000001"
959 floatToString 0.0000001
960 => trace: warning: Imprecise conversion from float to string 0.000000
961 "0.000000"
962 */
963 floatToString = float: let
964 result = toString float;
965 precise = float == fromJSON result;
966 in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}"
967 result;
968
969 /* Soft-deprecated function. While the original implementation is available as
970 isConvertibleWithToString, consider using isStringLike instead, if suitable. */
971 isCoercibleToString = lib.warnIf (lib.isInOldestRelease 2305)
972 "lib.strings.isCoercibleToString is deprecated in favor of either isStringLike or isConvertibleWithToString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles."
973 isConvertibleWithToString;
974
975 /* Check whether a list or other value can be passed to toString.
976
977 Many types of value are coercible to string this way, including int, float,
978 null, bool, list of similarly coercible values.
979 */
980 isConvertibleWithToString = x:
981 isStringLike x ||
982 elem (typeOf x) [ "null" "int" "float" "bool" ] ||
983 (isList x && lib.all isConvertibleWithToString x);
984
985 /* Check whether a value can be coerced to a string.
986 The value must be a string, path, or attribute set.
987
988 String-like values can be used without explicit conversion in
989 string interpolations and in most functions that expect a string.
990 */
991 isStringLike = x:
992 isString x ||
993 isPath x ||
994 x ? outPath ||
995 x ? __toString;
996
997 /* Check whether a value is a store path.
998
999 Example:
1000 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
1001 => false
1002 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
1003 => true
1004 isStorePath pkgs.python
1005 => true
1006 isStorePath [] || isStorePath 42 || isStorePath {} || …
1007 => false
1008 */
1009 isStorePath = x:
1010 if isStringLike x then
1011 let str = toString x; in
1012 substring 0 1 str == "/"
1013 && dirOf str == storeDir
1014 else
1015 false;
1016
1017 /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
1018 ambiguity between zero-padded and octal numbers. See toIntBase10.
1019
1020 Type: string -> int
1021
1022 Example:
1023
1024 toInt "1337"
1025 => 1337
1026
1027 toInt "-4"
1028 => -4
1029
1030 toInt " 123 "
1031 => 123
1032
1033 toInt "00024"
1034 => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
1035
1036 toInt "3.14"
1037 => error: floating point JSON numbers are not supported
1038 */
1039 toInt = str:
1040 let
1041 # RegEx: Match any leading whitespace, possibly a '-', one or more digits,
1042 # and finally match any trailing whitespace.
1043 strippedInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*" str;
1044
1045 # RegEx: Match a leading '0' then one or more digits.
1046 isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == [];
1047
1048 # Attempt to parse input
1049 parsedInput = fromJSON (head strippedInput);
1050
1051 generalError = "toInt: Could not convert ${escapeNixString str} to int.";
1052
1053 octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}"
1054 + " between octal and zero padded integer.";
1055
1056 in
1057 # Error on presence of non digit characters.
1058 if strippedInput == null
1059 then throw generalError
1060 # Error on presence of leading zero/octal ambiguity.
1061 else if isLeadingZero
1062 then throw octalAmbigError
1063 # Error if parse function fails.
1064 else if !isInt parsedInput
1065 then throw generalError
1066 # Return result.
1067 else parsedInput;
1068
1069
1070 /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
1071
1072 Type: string -> int
1073
1074 Example:
1075 toIntBase10 "1337"
1076 => 1337
1077
1078 toIntBase10 "-4"
1079 => -4
1080
1081 toIntBase10 " 123 "
1082 => 123
1083
1084 toIntBase10 "00024"
1085 => 24
1086
1087 toIntBase10 "3.14"
1088 => error: floating point JSON numbers are not supported
1089 */
1090 toIntBase10 = str:
1091 let
1092 # RegEx: Match any leading whitespace, then match any zero padding,
1093 # capture possibly a '-' followed by one or more digits,
1094 # and finally match any trailing whitespace.
1095 strippedInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*" str;
1096
1097 # RegEx: Match at least one '0'.
1098 isZero = match "0+" (head strippedInput) == [];
1099
1100 # Attempt to parse input
1101 parsedInput = fromJSON (head strippedInput);
1102
1103 generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
1104
1105 in
1106 # Error on presence of non digit characters.
1107 if strippedInput == null
1108 then throw generalError
1109 # In the special case zero-padded zero (00000), return early.
1110 else if isZero
1111 then 0
1112 # Error if parse function fails.
1113 else if !isInt parsedInput
1114 then throw generalError
1115 # Return result.
1116 else parsedInput;
1117
1118 /* Read a list of paths from `file`, relative to the `rootPath`.
1119 Lines beginning with `#` are treated as comments and ignored.
1120 Whitespace is significant.
1121
1122 NOTE: This function is not performant and should be avoided.
1123
1124 Example:
1125 readPathsFromFile /prefix
1126 ./pkgs/development/libraries/qt-5/5.4/qtbase/series
1127 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
1128 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
1129 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
1130 "/prefix/nix-profiles-library-paths.patch"
1131 "/prefix/compose-search-path.patch" ]
1132 */
1133 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead"
1134 (rootPath: file:
1135 let
1136 lines = lib.splitString "\n" (readFile file);
1137 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
1138 relativePaths = removeComments lines;
1139 absolutePaths = map (path: rootPath + "/${path}") relativePaths;
1140 in
1141 absolutePaths);
1142
1143 /* Read the contents of a file removing the trailing \n
1144
1145 Type: fileContents :: path -> string
1146
1147 Example:
1148 $ echo "1.0" > ./version
1149
1150 fileContents ./version
1151 => "1.0"
1152 */
1153 fileContents = file: removeSuffix "\n" (readFile file);
1154
1155
1156 /* Creates a valid derivation name from a potentially invalid one.
1157
1158 Type: sanitizeDerivationName :: String -> String
1159
1160 Example:
1161 sanitizeDerivationName "../hello.bar # foo"
1162 => "-hello.bar-foo"
1163 sanitizeDerivationName ""
1164 => "unknown"
1165 sanitizeDerivationName pkgs.hello
1166 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
1167 */
1168 sanitizeDerivationName =
1169 let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
1170 in
1171 string:
1172 # First detect the common case of already valid strings, to speed those up
1173 if stringLength string <= 207 && okRegex string != null
1174 then unsafeDiscardStringContext string
1175 else lib.pipe string [
1176 # Get rid of string context. This is safe under the assumption that the
1177 # resulting string is only used as a derivation name
1178 unsafeDiscardStringContext
1179 # Strip all leading "."
1180 (x: elemAt (match "\\.*(.*)" x) 0)
1181 # Split out all invalid characters
1182 # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
1183 # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
1184 (split "[^[:alnum:]+._?=-]+")
1185 # Replace invalid character ranges with a "-"
1186 (concatMapStrings (s: if lib.isList s then "-" else s))
1187 # Limit to 211 characters (minus 4 chars for ".drv")
1188 (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
1189 # If the result is empty, replace it with "unknown"
1190 (x: if stringLength x == 0 then "unknown" else x)
1191 ];
1192
1193 /* Computes the Levenshtein distance between two strings.
1194 Complexity O(n*m) where n and m are the lengths of the strings.
1195 Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
1196
1197 Type: levenshtein :: string -> string -> int
1198
1199 Example:
1200 levenshtein "foo" "foo"
1201 => 0
1202 levenshtein "book" "hook"
1203 => 1
1204 levenshtein "hello" "Heyo"
1205 => 3
1206 */
1207 levenshtein = a: b: let
1208 # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
1209 arr = lib.genList (i:
1210 lib.genList (j:
1211 dist i j
1212 ) (stringLength b + 1)
1213 ) (stringLength a + 1);
1214 d = x: y: lib.elemAt (lib.elemAt arr x) y;
1215 dist = i: j:
1216 let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
1217 then 0 else 1;
1218 in
1219 if j == 0 then i
1220 else if i == 0 then j
1221 else lib.min
1222 ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1))
1223 ( d (i - 1) (j - 1) + c );
1224 in d (stringLength a) (stringLength b);
1225
1226 /* Returns the length of the prefix common to both strings.
1227 */
1228 commonPrefixLength = a: b:
1229 let
1230 m = lib.min (stringLength a) (stringLength b);
1231 go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
1232 in go 0;
1233
1234 /* Returns the length of the suffix common to both strings.
1235 */
1236 commonSuffixLength = a: b:
1237 let
1238 m = lib.min (stringLength a) (stringLength b);
1239 go = i: if i >= m then m else if substring (stringLength a - i - 1) 1 a == substring (stringLength b - i - 1) 1 b then go (i + 1) else i;
1240 in go 0;
1241
1242 /* Returns whether the levenshtein distance between two strings is at most some value
1243 Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
1244
1245 Type: levenshteinAtMost :: int -> string -> string -> bool
1246
1247 Example:
1248 levenshteinAtMost 0 "foo" "foo"
1249 => true
1250 levenshteinAtMost 1 "foo" "boa"
1251 => false
1252 levenshteinAtMost 2 "foo" "boa"
1253 => true
1254 levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1255 => false
1256 levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1257 => true
1258
1259 */
1260 levenshteinAtMost = let
1261 infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1262
1263 # This function takes two strings stripped by their common pre and suffix,
1264 # and returns whether they differ by at most two by Levenshtein distance.
1265 # Because of this stripping, if they do indeed differ by at most two edits,
1266 # we know that those edits were (if at all) done at the start or the end,
1267 # while the middle has to have stayed the same. This fact is used in the
1268 # implementation.
1269 infixDifferAtMost2 = x: y:
1270 let
1271 xlen = stringLength x;
1272 ylen = stringLength y;
1273 # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
1274 # diff is one of 0, 1 or 2
1275 diff = xlen - ylen;
1276
1277 # Infix of x and y, stripped by the left and right most character
1278 xinfix = substring 1 (xlen - 2) x;
1279 yinfix = substring 1 (ylen - 2) y;
1280
1281 # x and y but a character deleted at the left or right
1282 xdelr = substring 0 (xlen - 1) x;
1283 xdell = substring 1 (xlen - 1) x;
1284 ydelr = substring 0 (ylen - 1) y;
1285 ydell = substring 1 (ylen - 1) y;
1286 in
1287 # A length difference of 2 can only be gotten with 2 delete edits,
1288 # which have to have happened at the start and end of x
1289 # Example: "abcdef" -> "bcde"
1290 if diff == 2 then xinfix == y
1291 # A length difference of 1 can only be gotten with a deletion on the
1292 # right and a replacement on the left or vice versa.
1293 # Example: "abcdef" -> "bcdez" or "zbcde"
1294 else if diff == 1 then xinfix == ydelr || xinfix == ydell
1295 # No length difference can either happen through replacements on both
1296 # sides, or a deletion on the left and an insertion on the right or
1297 # vice versa
1298 # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1299 else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1300
1301 in k: if k <= 0 then a: b: a == b else
1302 let f = a: b:
1303 let
1304 alen = stringLength a;
1305 blen = stringLength b;
1306 prelen = commonPrefixLength a b;
1307 suflen = commonSuffixLength a b;
1308 presuflen = prelen + suflen;
1309 ainfix = substring prelen (alen - presuflen) a;
1310 binfix = substring prelen (blen - presuflen) b;
1311 in
1312 # Make a be the bigger string
1313 if alen < blen then f b a
1314 # If a has over k more characters than b, even with k deletes on a, b can't be reached
1315 else if alen - blen > k then false
1316 else if k == 1 then infixDifferAtMost1 ainfix binfix
1317 else if k == 2 then infixDifferAtMost2 ainfix binfix
1318 else levenshtein ainfix binfix <= k;
1319 in f;
1320}