1/* String manipulation functions. */
2{ lib }:
3let
4
5inherit (builtins) length;
6
7in
8
9rec {
10
11 inherit (builtins)
12 compareVersions
13 elem
14 elemAt
15 filter
16 fromJSON
17 head
18 isInt
19 isList
20 isAttrs
21 isString
22 match
23 parseDrvName
24 readFile
25 replaceStrings
26 split
27 storeDir
28 stringLength
29 substring
30 tail
31 toJSON
32 typeOf
33 unsafeDiscardStringContext
34 ;
35
36 /* Concatenate a list of strings.
37
38 Type: concatStrings :: [string] -> string
39
40 Example:
41 concatStrings ["foo" "bar"]
42 => "foobar"
43 */
44 concatStrings = builtins.concatStringsSep "";
45
46 /* Map a function over a list and concatenate the resulting strings.
47
48 Type: concatMapStrings :: (a -> string) -> [a] -> string
49
50 Example:
51 concatMapStrings (x: "a" + x) ["foo" "bar"]
52 => "afooabar"
53 */
54 concatMapStrings = f: list: concatStrings (map f list);
55
56 /* Like `concatMapStrings` except that the f functions also gets the
57 position as a parameter.
58
59 Type: concatImapStrings :: (int -> a -> string) -> [a] -> string
60
61 Example:
62 concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"]
63 => "1-foo2-bar"
64 */
65 concatImapStrings = f: list: concatStrings (lib.imap1 f list);
66
67 /* Place an element between each element of a list
68
69 Type: intersperse :: a -> [a] -> [a]
70
71 Example:
72 intersperse "/" ["usr" "local" "bin"]
73 => ["usr" "/" "local" "/" "bin"].
74 */
75 intersperse =
76 # Separator to add between elements
77 separator:
78 # Input list
79 list:
80 if list == [] || length list == 1
81 then list
82 else tail (lib.concatMap (x: [separator x]) list);
83
84 /* Concatenate a list of strings with a separator between each element
85
86 Type: concatStringsSep :: string -> [string] -> string
87
88 Example:
89 concatStringsSep "/" ["usr" "local" "bin"]
90 => "usr/local/bin"
91 */
92 concatStringsSep = builtins.concatStringsSep or (separator: list:
93 lib.foldl' (x: y: x + y) "" (intersperse separator list));
94
95 /* Maps a function over a list of strings and then concatenates the
96 result with the specified separator interspersed between
97 elements.
98
99 Type: concatMapStringsSep :: string -> (a -> string) -> [a] -> string
100
101 Example:
102 concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"]
103 => "FOO-BAR-BAZ"
104 */
105 concatMapStringsSep =
106 # Separator to add between elements
107 sep:
108 # Function to map over the list
109 f:
110 # List of input strings
111 list: concatStringsSep sep (map f list);
112
113 /* Same as `concatMapStringsSep`, but the mapping function
114 additionally receives the position of its argument.
115
116 Type: concatIMapStringsSep :: string -> (int -> a -> string) -> [a] -> string
117
118 Example:
119 concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
120 => "6-3-2"
121 */
122 concatImapStringsSep =
123 # Separator to add between elements
124 sep:
125 # Function that receives elements and their positions
126 f:
127 # List of input strings
128 list: concatStringsSep sep (lib.imap1 f list);
129
130 /* Construct a Unix-style, colon-separated search path consisting of
131 the given `subDir` appended to each of the given paths.
132
133 Type: makeSearchPath :: string -> [string] -> string
134
135 Example:
136 makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
137 => "/root/bin:/usr/bin:/usr/local/bin"
138 makeSearchPath "bin" [""]
139 => "/bin"
140 */
141 makeSearchPath =
142 # Directory name to append
143 subDir:
144 # List of base paths
145 paths:
146 concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
147
148 /* Construct a Unix-style search path by appending the given
149 `subDir` to the specified `output` of each of the packages. If no
150 output by the given name is found, fallback to `.out` and then to
151 the default.
152
153 Type: string -> string -> [package] -> string
154
155 Example:
156 makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
157 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
158 */
159 makeSearchPathOutput =
160 # Package output to use
161 output:
162 # Directory name to append
163 subDir:
164 # List of packages
165 pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
166
167 /* Construct a library search path (such as RPATH) containing the
168 libraries for a set of packages
169
170 Example:
171 makeLibraryPath [ "/usr" "/usr/local" ]
172 => "/usr/lib:/usr/local/lib"
173 pkgs = import <nixpkgs> { }
174 makeLibraryPath [ pkgs.openssl pkgs.zlib ]
175 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
176 */
177 makeLibraryPath = makeSearchPathOutput "lib" "lib";
178
179 /* Construct a binary search path (such as $PATH) containing the
180 binaries for a set of packages.
181
182 Example:
183 makeBinPath ["/root" "/usr" "/usr/local"]
184 => "/root/bin:/usr/bin:/usr/local/bin"
185 */
186 makeBinPath = makeSearchPathOutput "bin" "bin";
187
188 /* Normalize path, removing extranous /s
189
190 Type: normalizePath :: string -> string
191
192 Example:
193 normalizePath "/a//b///c/"
194 => "/a/b/c/"
195 */
196 normalizePath = s: (builtins.foldl' (x: y: if y == "/" && hasSuffix "/" x then x else x+y) "" (stringToCharacters s));
197
198 /* Depending on the boolean `cond', return either the given string
199 or the empty string. Useful to concatenate against a bigger string.
200
201 Type: optionalString :: bool -> string -> string
202
203 Example:
204 optionalString true "some-string"
205 => "some-string"
206 optionalString false "some-string"
207 => ""
208 */
209 optionalString =
210 # Condition
211 cond:
212 # String to return if condition is true
213 string: if cond then string else "";
214
215 /* Determine whether a string has given prefix.
216
217 Type: hasPrefix :: string -> string -> bool
218
219 Example:
220 hasPrefix "foo" "foobar"
221 => true
222 hasPrefix "foo" "barfoo"
223 => false
224 */
225 hasPrefix =
226 # Prefix to check for
227 pref:
228 # Input string
229 str: substring 0 (stringLength pref) str == pref;
230
231 /* Determine whether a string has given suffix.
232
233 Type: hasSuffix :: string -> string -> bool
234
235 Example:
236 hasSuffix "foo" "foobar"
237 => false
238 hasSuffix "foo" "barfoo"
239 => true
240 */
241 hasSuffix =
242 # Suffix to check for
243 suffix:
244 # Input string
245 content:
246 let
247 lenContent = stringLength content;
248 lenSuffix = stringLength suffix;
249 in lenContent >= lenSuffix &&
250 substring (lenContent - lenSuffix) lenContent content == suffix;
251
252 /* Determine whether a string contains the given infix
253
254 Type: hasInfix :: string -> string -> bool
255
256 Example:
257 hasInfix "bc" "abcd"
258 => true
259 hasInfix "ab" "abcd"
260 => true
261 hasInfix "cd" "abcd"
262 => true
263 hasInfix "foo" "abcd"
264 => false
265 */
266 hasInfix = infix: content:
267 builtins.match ".*${escapeRegex infix}.*" "${content}" != null;
268
269 /* Convert a string to a list of characters (i.e. singleton strings).
270 This allows you to, e.g., map a function over each character. However,
271 note that this will likely be horribly inefficient; Nix is not a
272 general purpose programming language. Complex string manipulations
273 should, if appropriate, be done in a derivation.
274 Also note that Nix treats strings as a list of bytes and thus doesn't
275 handle unicode.
276
277 Type: stringToCharacters :: string -> [string]
278
279 Example:
280 stringToCharacters ""
281 => [ ]
282 stringToCharacters "abc"
283 => [ "a" "b" "c" ]
284 stringToCharacters "💩"
285 => [ "�" "�" "�" "�" ]
286 */
287 stringToCharacters = s:
288 map (p: substring p 1 s) (lib.range 0 (stringLength s - 1));
289
290 /* Manipulate a string character by character and replace them by
291 strings before concatenating the results.
292
293 Type: stringAsChars :: (string -> string) -> string -> string
294
295 Example:
296 stringAsChars (x: if x == "a" then "i" else x) "nax"
297 => "nix"
298 */
299 stringAsChars =
300 # Function to map over each individual character
301 f:
302 # Input string
303 s: concatStrings (
304 map f (stringToCharacters s)
305 );
306
307 /* Convert char to ascii value, must be in printable range
308
309 Type: charToInt :: string -> int
310
311 Example:
312 charToInt "A"
313 => 65
314 charToInt "("
315 => 40
316
317 */
318 charToInt = let
319 table = import ./ascii-table.nix;
320 in c: builtins.getAttr c table;
321
322 /* Escape occurrence of the elements of `list` in `string` by
323 prefixing it with a backslash.
324
325 Type: escape :: [string] -> string -> string
326
327 Example:
328 escape ["(" ")"] "(foo)"
329 => "\\(foo\\)"
330 */
331 escape = list: replaceChars list (map (c: "\\${c}") list);
332
333 /* Escape occurence of the element of `list` in `string` by
334 converting to its ASCII value and prefixing it with \\x.
335 Only works for printable ascii characters.
336
337 Type: escapeC = [string] -> string -> string
338
339 Example:
340 escapeC [" "] "foo bar"
341 => "foo\\x20bar"
342
343 */
344 escapeC = list: replaceChars list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
345
346 /* Quote string to be used safely within the Bourne shell.
347
348 Type: escapeShellArg :: string -> string
349
350 Example:
351 escapeShellArg "esc'ape\nme"
352 => "'esc'\\''ape\nme'"
353 */
354 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
355
356 /* Quote all arguments to be safely passed to the Bourne shell.
357
358 Type: escapeShellArgs :: [string] -> string
359
360 Example:
361 escapeShellArgs ["one" "two three" "four'five"]
362 => "'one' 'two three' 'four'\\''five'"
363 */
364 escapeShellArgs = concatMapStringsSep " " escapeShellArg;
365
366 /* Test whether the given name is a valid POSIX shell variable name.
367
368 Type: string -> bool
369
370 Example:
371 isValidPosixName "foo_bar000"
372 => true
373 isValidPosixName "0-bad.jpg"
374 => false
375 */
376 isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
377
378 /* Translate a Nix value into a shell variable declaration, with proper escaping.
379
380 The value can be a string (mapped to a regular variable), a list of strings
381 (mapped to a Bash-style array) or an attribute set of strings (mapped to a
382 Bash-style associative array). Note that "string" includes string-coercible
383 values like paths or derivations.
384
385 Strings are translated into POSIX sh-compatible code; lists and attribute sets
386 assume a shell that understands Bash syntax (e.g. Bash or ZSH).
387
388 Type: string -> (string | listOf string | attrsOf string) -> string
389
390 Example:
391 ''
392 ${toShellVar "foo" "some string"}
393 [[ "$foo" == "some string" ]]
394 ''
395 */
396 toShellVar = name: value:
397 lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
398 if isAttrs value && ! isCoercibleToString value then
399 "declare -A ${name}=(${
400 concatStringsSep " " (lib.mapAttrsToList (n: v:
401 "[${escapeShellArg n}]=${escapeShellArg v}"
402 ) value)
403 })"
404 else if isList value then
405 "declare -a ${name}=(${escapeShellArgs value})"
406 else
407 "${name}=${escapeShellArg value}"
408 );
409
410 /* Translate an attribute set into corresponding shell variable declarations
411 using `toShellVar`.
412
413 Type: attrsOf (string | listOf string | attrsOf string) -> string
414
415 Example:
416 let
417 foo = "value";
418 bar = foo;
419 in ''
420 ${toShellVars { inherit foo bar; }}
421 [[ "$foo" == "$bar" ]]
422 ''
423 */
424 toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
425
426 /* Turn a string into a Nix expression representing that string
427
428 Type: string -> string
429
430 Example:
431 escapeNixString "hello\${}\n"
432 => "\"hello\\\${}\\n\""
433 */
434 escapeNixString = s: escape ["$"] (toJSON s);
435
436 /* Turn a string into an exact regular expression
437
438 Type: string -> string
439
440 Example:
441 escapeRegex "[^a-z]*"
442 => "\\[\\^a-z]\\*"
443 */
444 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
445
446 /* Quotes a string if it can't be used as an identifier directly.
447
448 Type: string -> string
449
450 Example:
451 escapeNixIdentifier "hello"
452 => "hello"
453 escapeNixIdentifier "0abc"
454 => "\"0abc\""
455 */
456 escapeNixIdentifier = s:
457 # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
458 if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
459 then s else escapeNixString s;
460
461 /* Escapes a string such that it is safe to include verbatim in an XML
462 document.
463
464 Type: string -> string
465
466 Example:
467 escapeXML ''"test" 'test' < & >''
468 => ""test" 'test' < & >"
469 */
470 escapeXML = builtins.replaceStrings
471 ["\"" "'" "<" ">" "&"]
472 [""" "'" "<" ">" "&"];
473
474 # Obsolete - use replaceStrings instead.
475 replaceChars = builtins.replaceStrings or (
476 del: new: s:
477 let
478 substList = lib.zipLists del new;
479 subst = c:
480 let found = lib.findFirst (sub: sub.fst == c) null substList; in
481 if found == null then
482 c
483 else
484 found.snd;
485 in
486 stringAsChars subst s);
487
488 # Case conversion utilities.
489 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
490 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
491
492 /* Converts an ASCII string to lower-case.
493
494 Type: toLower :: string -> string
495
496 Example:
497 toLower "HOME"
498 => "home"
499 */
500 toLower = replaceChars upperChars lowerChars;
501
502 /* Converts an ASCII string to upper-case.
503
504 Type: toUpper :: string -> string
505
506 Example:
507 toUpper "home"
508 => "HOME"
509 */
510 toUpper = replaceChars lowerChars upperChars;
511
512 /* Appends string context from another string. This is an implementation
513 detail of Nix.
514
515 Strings in Nix carry an invisible `context` which is a list of strings
516 representing store paths. If the string is later used in a derivation
517 attribute, the derivation will properly populate the inputDrvs and
518 inputSrcs.
519
520 Example:
521 pkgs = import <nixpkgs> { };
522 addContextFrom pkgs.coreutils "bar"
523 => "bar"
524 */
525 addContextFrom = a: b: substring 0 0 a + b;
526
527 /* Cut a string with a separator and produces a list of strings which
528 were separated by this separator.
529
530 Example:
531 splitString "." "foo.bar.baz"
532 => [ "foo" "bar" "baz" ]
533 splitString "/" "/usr/local/bin"
534 => [ "" "usr" "local" "bin" ]
535 */
536 splitString = _sep: _s:
537 let
538 sep = builtins.unsafeDiscardStringContext _sep;
539 s = builtins.unsafeDiscardStringContext _s;
540 splits = builtins.filter builtins.isString (builtins.split (escapeRegex sep) s);
541 in
542 map (v: addContextFrom _sep (addContextFrom _s v)) splits;
543
544 /* Return a string without the specified prefix, if the prefix matches.
545
546 Type: string -> string -> string
547
548 Example:
549 removePrefix "foo." "foo.bar.baz"
550 => "bar.baz"
551 removePrefix "xxx" "foo.bar.baz"
552 => "foo.bar.baz"
553 */
554 removePrefix =
555 # Prefix to remove if it matches
556 prefix:
557 # Input string
558 str:
559 let
560 preLen = stringLength prefix;
561 sLen = stringLength str;
562 in
563 if hasPrefix prefix str then
564 substring preLen (sLen - preLen) str
565 else
566 str;
567
568 /* Return a string without the specified suffix, if the suffix matches.
569
570 Type: string -> string -> string
571
572 Example:
573 removeSuffix "front" "homefront"
574 => "home"
575 removeSuffix "xxx" "homefront"
576 => "homefront"
577 */
578 removeSuffix =
579 # Suffix to remove if it matches
580 suffix:
581 # Input string
582 str:
583 let
584 sufLen = stringLength suffix;
585 sLen = stringLength str;
586 in
587 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
588 substring 0 (sLen - sufLen) str
589 else
590 str;
591
592 /* Return true if string v1 denotes a version older than v2.
593
594 Example:
595 versionOlder "1.1" "1.2"
596 => true
597 versionOlder "1.1" "1.1"
598 => false
599 */
600 versionOlder = v1: v2: compareVersions v2 v1 == 1;
601
602 /* Return true if string v1 denotes a version equal to or newer than v2.
603
604 Example:
605 versionAtLeast "1.1" "1.0"
606 => true
607 versionAtLeast "1.1" "1.1"
608 => true
609 versionAtLeast "1.1" "1.2"
610 => false
611 */
612 versionAtLeast = v1: v2: !versionOlder v1 v2;
613
614 /* This function takes an argument that's either a derivation or a
615 derivation's "name" attribute and extracts the name part from that
616 argument.
617
618 Example:
619 getName "youtube-dl-2016.01.01"
620 => "youtube-dl"
621 getName pkgs.youtube-dl
622 => "youtube-dl"
623 */
624 getName = x:
625 let
626 parse = drv: (parseDrvName drv).name;
627 in if isString x
628 then parse x
629 else x.pname or (parse x.name);
630
631 /* This function takes an argument that's either a derivation or a
632 derivation's "name" attribute and extracts the version part from that
633 argument.
634
635 Example:
636 getVersion "youtube-dl-2016.01.01"
637 => "2016.01.01"
638 getVersion pkgs.youtube-dl
639 => "2016.01.01"
640 */
641 getVersion = x:
642 let
643 parse = drv: (parseDrvName drv).version;
644 in if isString x
645 then parse x
646 else x.version or (parse x.name);
647
648 /* Extract name with version from URL. Ask for separator which is
649 supposed to start extension.
650
651 Example:
652 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
653 => "nix"
654 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
655 => "nix-1.7-x86"
656 */
657 nameFromURL = url: sep:
658 let
659 components = splitString "/" url;
660 filename = lib.last components;
661 name = head (splitString sep filename);
662 in assert name != filename; name;
663
664 /* Create an --{enable,disable}-<feat> string that can be passed to
665 standard GNU Autoconf scripts.
666
667 Example:
668 enableFeature true "shared"
669 => "--enable-shared"
670 enableFeature false "shared"
671 => "--disable-shared"
672 */
673 enableFeature = enable: feat:
674 assert isString feat; # e.g. passing openssl instead of "openssl"
675 "--${if enable then "enable" else "disable"}-${feat}";
676
677 /* Create an --{enable-<feat>=<value>,disable-<feat>} string that can be passed to
678 standard GNU Autoconf scripts.
679
680 Example:
681 enableFeatureAs true "shared" "foo"
682 => "--enable-shared=foo"
683 enableFeatureAs false "shared" (throw "ignored")
684 => "--disable-shared"
685 */
686 enableFeatureAs = enable: feat: value: enableFeature enable feat + optionalString enable "=${value}";
687
688 /* Create an --{with,without}-<feat> string that can be passed to
689 standard GNU Autoconf scripts.
690
691 Example:
692 withFeature true "shared"
693 => "--with-shared"
694 withFeature false "shared"
695 => "--without-shared"
696 */
697 withFeature = with_: feat:
698 assert isString feat; # e.g. passing openssl instead of "openssl"
699 "--${if with_ then "with" else "without"}-${feat}";
700
701 /* Create an --{with-<feat>=<value>,without-<feat>} string that can be passed to
702 standard GNU Autoconf scripts.
703
704 Example:
705 withFeatureAs true "shared" "foo"
706 => "--with-shared=foo"
707 withFeatureAs false "shared" (throw "ignored")
708 => "--without-shared"
709 */
710 withFeatureAs = with_: feat: value: withFeature with_ feat + optionalString with_ "=${value}";
711
712 /* Create a fixed width string with additional prefix to match
713 required width.
714
715 This function will fail if the input string is longer than the
716 requested length.
717
718 Type: fixedWidthString :: int -> string -> string -> string
719
720 Example:
721 fixedWidthString 5 "0" (toString 15)
722 => "00015"
723 */
724 fixedWidthString = width: filler: str:
725 let
726 strw = lib.stringLength str;
727 reqWidth = width - (lib.stringLength filler);
728 in
729 assert lib.assertMsg (strw <= width)
730 "fixedWidthString: requested string length (${
731 toString width}) must not be shorter than actual length (${
732 toString strw})";
733 if strw == width then str else filler + fixedWidthString reqWidth filler str;
734
735 /* Format a number adding leading zeroes up to fixed width.
736
737 Example:
738 fixedWidthNumber 5 15
739 => "00015"
740 */
741 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
742
743 /* Convert a float to a string, but emit a warning when precision is lost
744 during the conversion
745
746 Example:
747 floatToString 0.000001
748 => "0.000001"
749 floatToString 0.0000001
750 => trace: warning: Imprecise conversion from float to string 0.000000
751 "0.000000"
752 */
753 floatToString = float: let
754 result = toString float;
755 precise = float == fromJSON result;
756 in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}"
757 result;
758
759 /* Check whether a value can be coerced to a string */
760 isCoercibleToString = x:
761 elem (typeOf x) [ "path" "string" "null" "int" "float" "bool" ] ||
762 (isList x && lib.all isCoercibleToString x) ||
763 x ? outPath ||
764 x ? __toString;
765
766 /* Check whether a value is a store path.
767
768 Example:
769 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
770 => false
771 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
772 => true
773 isStorePath pkgs.python
774 => true
775 isStorePath [] || isStorePath 42 || isStorePath {} || …
776 => false
777 */
778 isStorePath = x:
779 if !(isList x) && isCoercibleToString x then
780 let str = toString x; in
781 substring 0 1 str == "/"
782 && dirOf str == storeDir
783 else
784 false;
785
786 /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
787 ambiguity between zero-padded and octal numbers. See toIntBase10.
788
789 Type: string -> int
790
791 Example:
792
793 toInt "1337"
794 => 1337
795
796 toInt "-4"
797 => -4
798
799 toInt " 123 "
800 => 123
801
802 toInt "00024"
803 => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
804
805 toInt "3.14"
806 => error: floating point JSON numbers are not supported
807 */
808 toInt = str:
809 let
810 # RegEx: Match any leading whitespace, then any digits, and finally match any trailing
811 # whitespace.
812 strippedInput = match "[[:space:]]*([[:digit:]]+)[[:space:]]*" str;
813
814 # RegEx: Match a leading '0' then one or more digits.
815 isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == [];
816
817 # Attempt to parse input
818 parsedInput = fromJSON (head strippedInput);
819
820 generalError = "toInt: Could not convert ${escapeNixString str} to int.";
821
822 octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}"
823 + " between octal and zero padded integer.";
824
825 in
826 # Error on presence of non digit characters.
827 if strippedInput == null
828 then throw generalError
829 # Error on presence of leading zero/octal ambiguity.
830 else if isLeadingZero
831 then throw octalAmbigError
832 # Error if parse function fails.
833 else if !isInt parsedInput
834 then throw generalError
835 # Return result.
836 else parsedInput;
837
838
839 /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
840
841 Type: string -> int
842
843 Example:
844 toIntBase10 "1337"
845 => 1337
846
847 toIntBase10 "-4"
848 => -4
849
850 toIntBase10 " 123 "
851 => 123
852
853 toIntBase10 "00024"
854 => 24
855
856 toIntBase10 "3.14"
857 => error: floating point JSON numbers are not supported
858 */
859 toIntBase10 = str:
860 let
861 # RegEx: Match any leading whitespace, then match any zero padding, capture any remaining
862 # digits after that, and finally match any trailing whitespace.
863 strippedInput = match "[[:space:]]*0*([[:digit:]]+)[[:space:]]*" str;
864
865 # RegEx: Match at least one '0'.
866 isZero = match "0+" (head strippedInput) == [];
867
868 # Attempt to parse input
869 parsedInput = fromJSON (head strippedInput);
870
871 generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
872
873 in
874 # Error on presence of non digit characters.
875 if strippedInput == null
876 then throw generalError
877 # In the special case zero-padded zero (00000), return early.
878 else if isZero
879 then 0
880 # Error if parse function fails.
881 else if !isInt parsedInput
882 then throw generalError
883 # Return result.
884 else parsedInput;
885
886 /* Read a list of paths from `file`, relative to the `rootPath`.
887 Lines beginning with `#` are treated as comments and ignored.
888 Whitespace is significant.
889
890 NOTE: This function is not performant and should be avoided.
891
892 Example:
893 readPathsFromFile /prefix
894 ./pkgs/development/libraries/qt-5/5.4/qtbase/series
895 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
896 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
897 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
898 "/prefix/nix-profiles-library-paths.patch"
899 "/prefix/compose-search-path.patch" ]
900 */
901 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead"
902 (rootPath: file:
903 let
904 lines = lib.splitString "\n" (readFile file);
905 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
906 relativePaths = removeComments lines;
907 absolutePaths = map (path: rootPath + "/${path}") relativePaths;
908 in
909 absolutePaths);
910
911 /* Read the contents of a file removing the trailing \n
912
913 Type: fileContents :: path -> string
914
915 Example:
916 $ echo "1.0" > ./version
917
918 fileContents ./version
919 => "1.0"
920 */
921 fileContents = file: removeSuffix "\n" (readFile file);
922
923
924 /* Creates a valid derivation name from a potentially invalid one.
925
926 Type: sanitizeDerivationName :: String -> String
927
928 Example:
929 sanitizeDerivationName "../hello.bar # foo"
930 => "-hello.bar-foo"
931 sanitizeDerivationName ""
932 => "unknown"
933 sanitizeDerivationName pkgs.hello
934 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
935 */
936 sanitizeDerivationName =
937 let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
938 in
939 string:
940 # First detect the common case of already valid strings, to speed those up
941 if stringLength string <= 207 && okRegex string != null
942 then unsafeDiscardStringContext string
943 else lib.pipe string [
944 # Get rid of string context. This is safe under the assumption that the
945 # resulting string is only used as a derivation name
946 unsafeDiscardStringContext
947 # Strip all leading "."
948 (x: elemAt (match "\\.*(.*)" x) 0)
949 # Split out all invalid characters
950 # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
951 # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
952 (split "[^[:alnum:]+._?=-]+")
953 # Replace invalid character ranges with a "-"
954 (concatMapStrings (s: if lib.isList s then "-" else s))
955 # Limit to 211 characters (minus 4 chars for ".drv")
956 (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
957 # If the result is empty, replace it with "unknown"
958 (x: if stringLength x == 0 then "unknown" else x)
959 ];
960
961 /* Computes the Levenshtein distance between two strings.
962 Complexity O(n*m) where n and m are the lengths of the strings.
963 Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
964
965 Type: levenshtein :: string -> string -> int
966
967 Example:
968 levenshtein "foo" "foo"
969 => 0
970 levenshtein "book" "hook"
971 => 1
972 levenshtein "hello" "Heyo"
973 => 3
974 */
975 levenshtein = a: b: let
976 # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
977 arr = lib.genList (i:
978 lib.genList (j:
979 dist i j
980 ) (stringLength b + 1)
981 ) (stringLength a + 1);
982 d = x: y: lib.elemAt (lib.elemAt arr x) y;
983 dist = i: j:
984 let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
985 then 0 else 1;
986 in
987 if j == 0 then i
988 else if i == 0 then j
989 else lib.min
990 ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1))
991 ( d (i - 1) (j - 1) + c );
992 in d (stringLength a) (stringLength b);
993
994 /* Returns the length of the prefix common to both strings.
995 */
996 commonPrefixLength = a: b:
997 let
998 m = lib.min (stringLength a) (stringLength b);
999 go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
1000 in go 0;
1001
1002 /* Returns the length of the suffix common to both strings.
1003 */
1004 commonSuffixLength = a: b:
1005 let
1006 m = lib.min (stringLength a) (stringLength b);
1007 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;
1008 in go 0;
1009
1010 /* Returns whether the levenshtein distance between two strings is at most some value
1011 Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
1012
1013 Type: levenshteinAtMost :: int -> string -> string -> bool
1014
1015 Example:
1016 levenshteinAtMost 0 "foo" "foo"
1017 => true
1018 levenshteinAtMost 1 "foo" "boa"
1019 => false
1020 levenshteinAtMost 2 "foo" "boa"
1021 => true
1022 levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1023 => false
1024 levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1025 => true
1026
1027 */
1028 levenshteinAtMost = let
1029 infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1030
1031 # This function takes two strings stripped by their common pre and suffix,
1032 # and returns whether they differ by at most two by Levenshtein distance.
1033 # Because of this stripping, if they do indeed differ by at most two edits,
1034 # we know that those edits were (if at all) done at the start or the end,
1035 # while the middle has to have stayed the same. This fact is used in the
1036 # implementation.
1037 infixDifferAtMost2 = x: y:
1038 let
1039 xlen = stringLength x;
1040 ylen = stringLength y;
1041 # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
1042 # diff is one of 0, 1 or 2
1043 diff = xlen - ylen;
1044
1045 # Infix of x and y, stripped by the left and right most character
1046 xinfix = substring 1 (xlen - 2) x;
1047 yinfix = substring 1 (ylen - 2) y;
1048
1049 # x and y but a character deleted at the left or right
1050 xdelr = substring 0 (xlen - 1) x;
1051 xdell = substring 1 (xlen - 1) x;
1052 ydelr = substring 0 (ylen - 1) y;
1053 ydell = substring 1 (ylen - 1) y;
1054 in
1055 # A length difference of 2 can only be gotten with 2 delete edits,
1056 # which have to have happened at the start and end of x
1057 # Example: "abcdef" -> "bcde"
1058 if diff == 2 then xinfix == y
1059 # A length difference of 1 can only be gotten with a deletion on the
1060 # right and a replacement on the left or vice versa.
1061 # Example: "abcdef" -> "bcdez" or "zbcde"
1062 else if diff == 1 then xinfix == ydelr || xinfix == ydell
1063 # No length difference can either happen through replacements on both
1064 # sides, or a deletion on the left and an insertion on the right or
1065 # vice versa
1066 # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1067 else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1068
1069 in k: if k <= 0 then a: b: a == b else
1070 let f = a: b:
1071 let
1072 alen = stringLength a;
1073 blen = stringLength b;
1074 prelen = commonPrefixLength a b;
1075 suflen = commonSuffixLength a b;
1076 presuflen = prelen + suflen;
1077 ainfix = substring prelen (alen - presuflen) a;
1078 binfix = substring prelen (blen - presuflen) b;
1079 in
1080 # Make a be the bigger string
1081 if alen < blen then f b a
1082 # If a has over k more characters than b, even with k deletes on a, b can't be reached
1083 else if alen - blen > k then false
1084 else if k == 1 then infixDifferAtMost1 ainfix binfix
1085 else if k == 2 then infixDifferAtMost2 ainfix binfix
1086 else levenshtein ainfix binfix <= k;
1087 in f;
1088}