1/**
2 String manipulation functions.
3*/
4{ lib }:
5let
6
7 inherit (builtins) length;
8
9 inherit (lib.trivial) warnIf;
10
11 asciiTable = import ./ascii-table.nix;
12
13in
14
15rec {
16
17 inherit (builtins)
18 compareVersions
19 elem
20 elemAt
21 filter
22 fromJSON
23 genList
24 head
25 isInt
26 isList
27 isAttrs
28 isPath
29 isString
30 match
31 parseDrvName
32 readFile
33 replaceStrings
34 split
35 storeDir
36 stringLength
37 substring
38 tail
39 toJSON
40 typeOf
41 unsafeDiscardStringContext
42 ;
43
44 /**
45 Concatenates a list of strings with a separator between each element.
46
47 # Inputs
48
49 `sep`
50 : Separator to add between elements
51
52 `list`
53 : List of strings that will be joined
54
55 # Type
56
57 ```
58 join :: string -> [ string ] -> string
59 ```
60
61 # Examples
62 :::{.example}
63 ## `lib.strings.join` usage example
64
65 ```nix
66 join ", " ["foo" "bar"]
67 => "foo, bar"
68 ```
69
70 :::
71 */
72 join = builtins.concatStringsSep;
73
74 /**
75 Concatenate a list of strings.
76
77 # Type
78
79 ```
80 concatStrings :: [string] -> string
81 ```
82
83 # Examples
84 :::{.example}
85 ## `lib.strings.concatStrings` usage example
86
87 ```nix
88 concatStrings ["foo" "bar"]
89 => "foobar"
90 ```
91
92 :::
93 */
94 concatStrings = builtins.concatStringsSep "";
95
96 /**
97 Map a function over a list and concatenate the resulting strings.
98
99 # Inputs
100
101 `f`
102 : 1\. Function argument
103
104 `list`
105 : 2\. Function argument
106
107 # Type
108
109 ```
110 concatMapStrings :: (a -> string) -> [a] -> string
111 ```
112
113 # Examples
114 :::{.example}
115 ## `lib.strings.concatMapStrings` usage example
116
117 ```nix
118 concatMapStrings (x: "a" + x) ["foo" "bar"]
119 => "afooabar"
120 ```
121
122 :::
123 */
124 concatMapStrings = f: list: concatStrings (map f list);
125
126 /**
127 Like `concatMapStrings` except that the f functions also gets the
128 position as a parameter.
129
130 # Inputs
131
132 `f`
133 : 1\. Function argument
134
135 `list`
136 : 2\. Function argument
137
138 # Type
139
140 ```
141 concatImapStrings :: (int -> a -> string) -> [a] -> string
142 ```
143
144 # Examples
145 :::{.example}
146 ## `lib.strings.concatImapStrings` usage example
147
148 ```nix
149 concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"]
150 => "1-foo2-bar"
151 ```
152
153 :::
154 */
155 concatImapStrings = f: list: concatStrings (lib.imap1 f list);
156
157 /**
158 Place an element between each element of a list
159
160 # Inputs
161
162 `separator`
163 : Separator to add between elements
164
165 `list`
166 : Input list
167
168 # Type
169
170 ```
171 intersperse :: a -> [a] -> [a]
172 ```
173
174 # Examples
175 :::{.example}
176 ## `lib.strings.intersperse` usage example
177
178 ```nix
179 intersperse "/" ["usr" "local" "bin"]
180 => ["usr" "/" "local" "/" "bin"].
181 ```
182
183 :::
184 */
185 intersperse =
186 separator: list:
187 if list == [ ] || length list == 1 then
188 list
189 else
190 tail (
191 lib.concatMap (x: [
192 separator
193 x
194 ]) list
195 );
196
197 /**
198 Concatenate a list of strings with a separator between each element
199
200 # Inputs
201
202 `sep`
203 : Separator to add between elements
204
205 `list`
206 : List of input strings
207
208 # Type
209
210 ```
211 concatStringsSep :: string -> [string] -> string
212 ```
213
214 # Examples
215 :::{.example}
216 ## `lib.strings.concatStringsSep` usage example
217
218 ```nix
219 concatStringsSep "/" ["usr" "local" "bin"]
220 => "usr/local/bin"
221 ```
222
223 :::
224 */
225 concatStringsSep = builtins.concatStringsSep;
226
227 /**
228 Maps a function over a list of strings and then concatenates the
229 result with the specified separator interspersed between
230 elements.
231
232 # Inputs
233
234 `sep`
235 : Separator to add between elements
236
237 `f`
238 : Function to map over the list
239
240 `list`
241 : List of input strings
242
243 # Type
244
245 ```
246 concatMapStringsSep :: string -> (a -> string) -> [a] -> string
247 ```
248
249 # Examples
250 :::{.example}
251 ## `lib.strings.concatMapStringsSep` usage example
252
253 ```nix
254 concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"]
255 => "FOO-BAR-BAZ"
256 ```
257
258 :::
259 */
260 concatMapStringsSep =
261 sep: f: list:
262 concatStringsSep sep (map f list);
263
264 /**
265 Same as `concatMapStringsSep`, but the mapping function
266 additionally receives the position of its argument.
267
268 # Inputs
269
270 `sep`
271 : Separator to add between elements
272
273 `f`
274 : Function that receives elements and their positions
275
276 `list`
277 : List of input strings
278
279 # Type
280
281 ```
282 concatIMapStringsSep :: string -> (int -> a -> string) -> [a] -> string
283 ```
284
285 # Examples
286 :::{.example}
287 ## `lib.strings.concatImapStringsSep` usage example
288
289 ```nix
290 concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
291 => "6-3-2"
292 ```
293
294 :::
295 */
296 concatImapStringsSep =
297 sep: f: list:
298 concatStringsSep sep (lib.imap1 f list);
299
300 /**
301 Like [`concatMapStringsSep`](#function-library-lib.strings.concatMapStringsSep)
302 but takes an attribute set instead of a list.
303
304 # Inputs
305
306 `sep`
307 : Separator to add between item strings
308
309 `f`
310 : Function that takes each key and value and return a string
311
312 `attrs`
313 : Attribute set to map from
314
315 # Type
316
317 ```
318 concatMapAttrsStringSep :: String -> (String -> Any -> String) -> AttrSet -> String
319 ```
320
321 # Examples
322
323 :::{.example}
324 ## `lib.strings.concatMapAttrsStringSep` usage example
325
326 ```nix
327 concatMapAttrsStringSep "\n" (name: value: "${name}: foo-${value}") { a = "0.1.0"; b = "0.2.0"; }
328 => "a: foo-0.1.0\nb: foo-0.2.0"
329 ```
330
331 :::
332 */
333 concatMapAttrsStringSep =
334 sep: f: attrs:
335 concatStringsSep sep (lib.attrValues (lib.mapAttrs f attrs));
336
337 /**
338 Concatenate a list of strings, adding a newline at the end of each one.
339 Defined as `concatMapStrings (s: s + "\n")`.
340
341 # Inputs
342
343 `list`
344 : List of strings. Any element that is not a string will be implicitly converted to a string.
345
346 # Type
347
348 ```
349 concatLines :: [string] -> string
350 ```
351
352 # Examples
353 :::{.example}
354 ## `lib.strings.concatLines` usage example
355
356 ```nix
357 concatLines [ "foo" "bar" ]
358 => "foo\nbar\n"
359 ```
360
361 :::
362 */
363 concatLines = concatMapStrings (s: s + "\n");
364
365 /**
366 Given string `s`, replace every occurrence of the string `from` with the string `to`.
367
368 # Inputs
369
370 `from`
371 : The string to be replaced
372
373 `to`
374 : The string to replace with
375
376 `s`
377 : The original string where replacements will be made
378
379 # Type
380
381 ```
382 replaceString :: string -> string -> string -> string
383 ```
384
385 # Examples
386 :::{.example}
387 ## `lib.strings.replaceString` usage example
388
389 ```nix
390 replaceString "world" "Nix" "Hello, world!"
391 => "Hello, Nix!"
392 replaceString "." "_" "v1.2.3"
393 => "v1_2_3"
394 ```
395
396 :::
397 */
398 replaceString = from: to: replaceStrings [ from ] [ to ];
399
400 /**
401 Repeat a string `n` times,
402 and concatenate the parts into a new string.
403
404 # Inputs
405
406 `n`
407 : 1\. Function argument
408
409 `s`
410 : 2\. Function argument
411
412 # Type
413
414 ```
415 replicate :: int -> string -> string
416 ```
417
418 # Examples
419 :::{.example}
420 ## `lib.strings.replicate` usage example
421
422 ```nix
423 replicate 3 "v"
424 => "vvv"
425 replicate 5 "hello"
426 => "hellohellohellohellohello"
427 ```
428
429 :::
430 */
431 replicate = n: s: concatStrings (lib.lists.replicate n s);
432
433 /**
434 Remove leading and trailing whitespace from a string `s`.
435
436 Whitespace is defined as any of the following characters:
437 " ", "\t" "\r" "\n"
438
439 # Inputs
440
441 `s`
442 : The string to trim
443
444 # Type
445
446 ```
447 trim :: string -> string
448 ```
449
450 # Examples
451 :::{.example}
452 ## `lib.strings.trim` usage example
453
454 ```nix
455 trim " hello, world! "
456 => "hello, world!"
457 ```
458
459 :::
460 */
461 trim = trimWith {
462 start = true;
463 end = true;
464 };
465
466 /**
467 Remove leading and/or trailing whitespace from a string `s`.
468
469 To remove both leading and trailing whitespace, you can also use [`trim`](#function-library-lib.strings.trim)
470
471 Whitespace is defined as any of the following characters:
472 " ", "\t" "\r" "\n"
473
474 # Inputs
475
476 `config` (Attribute set)
477 : `start`
478 : Whether to trim leading whitespace (`false` by default)
479
480 : `end`
481 : Whether to trim trailing whitespace (`false` by default)
482
483 `s`
484 : The string to trim
485
486 # Type
487
488 ```
489 trimWith :: { start :: Bool; end :: Bool } -> String -> String
490 ```
491
492 # Examples
493 :::{.example}
494 ## `lib.strings.trimWith` usage example
495
496 ```nix
497 trimWith { start = true; } " hello, world! "}
498 => "hello, world! "
499
500 trimWith { end = true; } " hello, world! "}
501 => " hello, world!"
502 ```
503 :::
504 */
505 trimWith =
506 {
507 start ? false,
508 end ? false,
509 }:
510 let
511 # Define our own whitespace character class instead of using
512 # `[:space:]`, which is not well-defined.
513 chars = " \t\r\n";
514
515 # To match up until trailing whitespace, we need to capture a
516 # group that ends with a non-whitespace character.
517 regex =
518 if start && end then
519 "[${chars}]*(.*[^${chars}])[${chars}]*"
520 else if start then
521 "[${chars}]*(.*)"
522 else if end then
523 "(.*[^${chars}])[${chars}]*"
524 else
525 "(.*)";
526 in
527 s:
528 let
529 # If the string was empty or entirely whitespace,
530 # then the regex may not match and `res` will be `null`.
531 res = match regex s;
532 in
533 optionalString (res != null) (head res);
534
535 /**
536 Construct a Unix-style, colon-separated search path consisting of
537 the given `subDir` appended to each of the given paths.
538
539 # Inputs
540
541 `subDir`
542 : Directory name to append
543
544 `paths`
545 : List of base paths
546
547 # Type
548
549 ```
550 makeSearchPath :: string -> [string] -> string
551 ```
552
553 # Examples
554 :::{.example}
555 ## `lib.strings.makeSearchPath` usage example
556
557 ```nix
558 makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
559 => "/root/bin:/usr/bin:/usr/local/bin"
560 makeSearchPath "bin" [""]
561 => "/bin"
562 ```
563
564 :::
565 */
566 makeSearchPath =
567 subDir: paths: concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
568
569 /**
570 Construct a Unix-style search path by appending the given
571 `subDir` to the specified `output` of each of the packages.
572
573 If no output by the given name is found, fallback to `.out` and then to
574 the default.
575
576 # Inputs
577
578 `output`
579 : Package output to use
580
581 `subDir`
582 : Directory name to append
583
584 `pkgs`
585 : List of packages
586
587 # Type
588
589 ```
590 makeSearchPathOutput :: string -> string -> [package] -> string
591 ```
592
593 # Examples
594 :::{.example}
595 ## `lib.strings.makeSearchPathOutput` usage example
596
597 ```nix
598 makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
599 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
600 ```
601
602 :::
603 */
604 makeSearchPathOutput =
605 output: subDir: pkgs:
606 makeSearchPath subDir (map (lib.getOutput output) pkgs);
607
608 /**
609 Construct a library search path (such as RPATH) containing the
610 libraries for a set of packages
611
612 # Inputs
613
614 `packages`
615 : List of packages
616
617 # Type
618
619 ```
620 makeLibraryPath :: [package] -> string
621 ```
622
623 # Examples
624 :::{.example}
625 ## `lib.strings.makeLibraryPath` usage example
626
627 ```nix
628 makeLibraryPath [ "/usr" "/usr/local" ]
629 => "/usr/lib:/usr/local/lib"
630 pkgs = import <nixpkgs> { }
631 makeLibraryPath [ pkgs.openssl pkgs.zlib ]
632 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
633 ```
634
635 :::
636 */
637 makeLibraryPath = makeSearchPathOutput "lib" "lib";
638
639 /**
640 Construct an include search path (such as C_INCLUDE_PATH) containing the
641 header files for a set of packages or paths.
642
643 # Inputs
644
645 `packages`
646 : List of packages
647
648 # Type
649
650 ```
651 makeIncludePath :: [package] -> string
652 ```
653
654 # Examples
655 :::{.example}
656 ## `lib.strings.makeIncludePath` usage example
657
658 ```nix
659 makeIncludePath [ "/usr" "/usr/local" ]
660 => "/usr/include:/usr/local/include"
661 pkgs = import <nixpkgs> { }
662 makeIncludePath [ pkgs.openssl pkgs.zlib ]
663 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/include:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8-dev/include"
664 ```
665
666 :::
667 */
668 makeIncludePath = makeSearchPathOutput "dev" "include";
669
670 /**
671 Construct a binary search path (such as $PATH) containing the
672 binaries for a set of packages.
673
674 # Inputs
675
676 `packages`
677 : List of packages
678
679 # Type
680
681 ```
682 makeBinPath :: [package] -> string
683 ```
684
685 # Examples
686 :::{.example}
687 ## `lib.strings.makeBinPath` usage example
688
689 ```nix
690 makeBinPath ["/root" "/usr" "/usr/local"]
691 => "/root/bin:/usr/bin:/usr/local/bin"
692 ```
693
694 :::
695 */
696 makeBinPath = makeSearchPathOutput "bin" "bin";
697
698 /**
699 Normalize path, removing extraneous /s
700
701 # Inputs
702
703 `s`
704 : 1\. Function argument
705
706 # Type
707
708 ```
709 normalizePath :: string -> string
710 ```
711
712 # Examples
713 :::{.example}
714 ## `lib.strings.normalizePath` usage example
715
716 ```nix
717 normalizePath "/a//b///c/"
718 => "/a/b/c/"
719 ```
720
721 :::
722 */
723 normalizePath =
724 s:
725 warnIf (isPath s)
726 ''
727 lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported.
728 Path values are always normalised in Nix, so there's no need to call this function on them.
729 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.
730 This behavior is deprecated and will throw an error in the future.''
731 (
732 builtins.foldl' (x: y: if y == "/" && hasSuffix "/" x then x else x + y) "" (stringToCharacters s)
733 );
734
735 /**
736 Depending on the boolean `cond', return either the given string
737 or the empty string. Useful to concatenate against a bigger string.
738
739 # Inputs
740
741 `cond`
742 : Condition
743
744 `string`
745 : String to return if condition is true
746
747 # Type
748
749 ```
750 optionalString :: bool -> string -> string
751 ```
752
753 # Examples
754 :::{.example}
755 ## `lib.strings.optionalString` usage example
756
757 ```nix
758 optionalString true "some-string"
759 => "some-string"
760 optionalString false "some-string"
761 => ""
762 ```
763
764 :::
765 */
766 optionalString = cond: string: if cond then string else "";
767
768 /**
769 Determine whether a string has given prefix.
770
771 # Inputs
772
773 `pref`
774 : Prefix to check for
775
776 `str`
777 : Input string
778
779 # Type
780
781 ```
782 hasPrefix :: string -> string -> bool
783 ```
784
785 # Examples
786 :::{.example}
787 ## `lib.strings.hasPrefix` usage example
788
789 ```nix
790 hasPrefix "foo" "foobar"
791 => true
792 hasPrefix "foo" "barfoo"
793 => false
794 ```
795
796 :::
797 */
798 hasPrefix =
799 pref: str:
800 # Before 23.05, paths would be copied to the store before converting them
801 # to strings and comparing. This was surprising and confusing.
802 warnIf (isPath pref)
803 ''
804 lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported.
805 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
806 This function also copies the path to the Nix store, which may not be what you want.
807 This behavior is deprecated and will throw an error in the future.
808 You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.''
809 (substring 0 (stringLength pref) str == pref);
810
811 /**
812 Determine whether a string has given suffix.
813
814 # Inputs
815
816 `suffix`
817 : Suffix to check for
818
819 `content`
820 : Input string
821
822 # Type
823
824 ```
825 hasSuffix :: string -> string -> bool
826 ```
827
828 # Examples
829 :::{.example}
830 ## `lib.strings.hasSuffix` usage example
831
832 ```nix
833 hasSuffix "foo" "foobar"
834 => false
835 hasSuffix "foo" "barfoo"
836 => true
837 ```
838
839 :::
840 */
841 hasSuffix =
842 suffix: content:
843 let
844 lenContent = stringLength content;
845 lenSuffix = stringLength suffix;
846 in
847 # Before 23.05, paths would be copied to the store before converting them
848 # to strings and comparing. This was surprising and confusing.
849 warnIf (isPath suffix)
850 ''
851 lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
852 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
853 This function also copies the path to the Nix store, which may not be what you want.
854 This behavior is deprecated and will throw an error in the future.''
855 (lenContent >= lenSuffix && substring (lenContent - lenSuffix) lenContent content == suffix);
856
857 /**
858 Determine whether a string contains the given infix
859
860 # Inputs
861
862 `infix`
863 : 1\. Function argument
864
865 `content`
866 : 2\. Function argument
867
868 # Type
869
870 ```
871 hasInfix :: string -> string -> bool
872 ```
873
874 # Examples
875 :::{.example}
876 ## `lib.strings.hasInfix` usage example
877
878 ```nix
879 hasInfix "bc" "abcd"
880 => true
881 hasInfix "ab" "abcd"
882 => true
883 hasInfix "cd" "abcd"
884 => true
885 hasInfix "foo" "abcd"
886 => false
887 ```
888
889 :::
890 */
891 hasInfix =
892 infix: content:
893 # Before 23.05, paths would be copied to the store before converting them
894 # to strings and comparing. This was surprising and confusing.
895 warnIf (isPath infix)
896 ''
897 lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported.
898 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
899 This function also copies the path to the Nix store, which may not be what you want.
900 This behavior is deprecated and will throw an error in the future.''
901 (builtins.match ".*${escapeRegex infix}.*" "${content}" != null);
902
903 /**
904 Convert a string `s` to a list of characters (i.e. singleton strings).
905 This allows you to, e.g., map a function over each character. However,
906 note that this will likely be horribly inefficient; Nix is not a
907 general purpose programming language. Complex string manipulations
908 should, if appropriate, be done in a derivation.
909 Also note that Nix treats strings as a list of bytes and thus doesn't
910 handle unicode.
911
912 # Inputs
913
914 `s`
915 : 1\. Function argument
916
917 # Type
918
919 ```
920 stringToCharacters :: string -> [string]
921 ```
922
923 # Examples
924 :::{.example}
925 ## `lib.strings.stringToCharacters` usage example
926
927 ```nix
928 stringToCharacters ""
929 => [ ]
930 stringToCharacters "abc"
931 => [ "a" "b" "c" ]
932 stringToCharacters "🦄"
933 => [ "�" "�" "�" "�" ]
934 ```
935
936 :::
937 */
938 stringToCharacters = s: genList (p: substring p 1 s) (stringLength s);
939
940 /**
941 Manipulate a string character by character and replace them by
942 strings before concatenating the results.
943
944 # Inputs
945
946 `f`
947 : Function to map over each individual character
948
949 `s`
950 : Input string
951
952 # Type
953
954 ```
955 stringAsChars :: (string -> string) -> string -> string
956 ```
957
958 # Examples
959 :::{.example}
960 ## `lib.strings.stringAsChars` usage example
961
962 ```nix
963 stringAsChars (x: if x == "a" then "i" else x) "nax"
964 => "nix"
965 ```
966
967 :::
968 */
969 stringAsChars =
970 # Function to map over each individual character
971 f:
972 # Input string
973 s:
974 concatStrings (map f (stringToCharacters s));
975
976 /**
977 Convert char to ascii value, must be in printable range
978
979 # Inputs
980
981 `c`
982 : 1\. Function argument
983
984 # Type
985
986 ```
987 charToInt :: string -> int
988 ```
989
990 # Examples
991 :::{.example}
992 ## `lib.strings.charToInt` usage example
993
994 ```nix
995 charToInt "A"
996 => 65
997 charToInt "("
998 => 40
999 ```
1000
1001 :::
1002 */
1003 charToInt = c: builtins.getAttr c asciiTable;
1004
1005 /**
1006 Escape occurrence of the elements of `list` in `string` by
1007 prefixing it with a backslash.
1008
1009 # Inputs
1010
1011 `list`
1012 : 1\. Function argument
1013
1014 `string`
1015 : 2\. Function argument
1016
1017 # Type
1018
1019 ```
1020 escape :: [string] -> string -> string
1021 ```
1022
1023 # Examples
1024 :::{.example}
1025 ## `lib.strings.escape` usage example
1026
1027 ```nix
1028 escape ["(" ")"] "(foo)"
1029 => "\\(foo\\)"
1030 ```
1031
1032 :::
1033 */
1034 escape = list: replaceStrings list (map (c: "\\${c}") list);
1035
1036 /**
1037 Escape occurrence of the element of `list` in `string` by
1038 converting to its ASCII value and prefixing it with \\x.
1039 Only works for printable ascii characters.
1040
1041 # Inputs
1042
1043 `list`
1044 : 1\. Function argument
1045
1046 `string`
1047 : 2\. Function argument
1048
1049 # Type
1050
1051 ```
1052 escapeC = [string] -> string -> string
1053 ```
1054
1055 # Examples
1056 :::{.example}
1057 ## `lib.strings.escapeC` usage example
1058
1059 ```nix
1060 escapeC [" "] "foo bar"
1061 => "foo\\x20bar"
1062 ```
1063
1064 :::
1065 */
1066 escapeC =
1067 list:
1068 replaceStrings list (
1069 map (c: "\\x${fixedWidthString 2 "0" (toLower (lib.toHexString (charToInt c)))}") list
1070 );
1071
1072 /**
1073 Escape the `string` so it can be safely placed inside a URL
1074 query.
1075
1076 # Inputs
1077
1078 `string`
1079 : 1\. Function argument
1080
1081 # Type
1082
1083 ```
1084 escapeURL :: string -> string
1085 ```
1086
1087 # Examples
1088 :::{.example}
1089 ## `lib.strings.escapeURL` usage example
1090
1091 ```nix
1092 escapeURL "foo/bar baz"
1093 => "foo%2Fbar%20baz"
1094 ```
1095
1096 :::
1097 */
1098 escapeURL =
1099 let
1100 unreserved = [
1101 "A"
1102 "B"
1103 "C"
1104 "D"
1105 "E"
1106 "F"
1107 "G"
1108 "H"
1109 "I"
1110 "J"
1111 "K"
1112 "L"
1113 "M"
1114 "N"
1115 "O"
1116 "P"
1117 "Q"
1118 "R"
1119 "S"
1120 "T"
1121 "U"
1122 "V"
1123 "W"
1124 "X"
1125 "Y"
1126 "Z"
1127 "a"
1128 "b"
1129 "c"
1130 "d"
1131 "e"
1132 "f"
1133 "g"
1134 "h"
1135 "i"
1136 "j"
1137 "k"
1138 "l"
1139 "m"
1140 "n"
1141 "o"
1142 "p"
1143 "q"
1144 "r"
1145 "s"
1146 "t"
1147 "u"
1148 "v"
1149 "w"
1150 "x"
1151 "y"
1152 "z"
1153 "0"
1154 "1"
1155 "2"
1156 "3"
1157 "4"
1158 "5"
1159 "6"
1160 "7"
1161 "8"
1162 "9"
1163 "-"
1164 "_"
1165 "."
1166 "~"
1167 ];
1168 toEscape = removeAttrs asciiTable unreserved;
1169 in
1170 replaceStrings (builtins.attrNames toEscape) (
1171 lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape
1172 );
1173
1174 /**
1175 Quote `string` to be used safely within the Bourne shell if it has any
1176 special characters.
1177
1178 # Inputs
1179
1180 `string`
1181 : 1\. Function argument
1182
1183 # Type
1184
1185 ```
1186 escapeShellArg :: string -> string
1187 ```
1188
1189 # Examples
1190 :::{.example}
1191 ## `lib.strings.escapeShellArg` usage example
1192
1193 ```nix
1194 escapeShellArg "esc'ape\nme"
1195 => "'esc'\\''ape\nme'"
1196 ```
1197
1198 :::
1199 */
1200 escapeShellArg =
1201 arg:
1202 let
1203 string = toString arg;
1204 in
1205 if match "[[:alnum:],._+:@%/-]+" string == null then
1206 "'${replaceString "'" "'\\''" string}'"
1207 else
1208 string;
1209
1210 /**
1211 Quote all arguments that have special characters to be safely passed to the
1212 Bourne shell.
1213
1214 # Inputs
1215
1216 `args`
1217 : 1\. Function argument
1218
1219 # Type
1220
1221 ```
1222 escapeShellArgs :: [string] -> string
1223 ```
1224
1225 # Examples
1226 :::{.example}
1227 ## `lib.strings.escapeShellArgs` usage example
1228
1229 ```nix
1230 escapeShellArgs ["one" "two three" "four'five"]
1231 => "one 'two three' 'four'\\''five'"
1232 ```
1233
1234 :::
1235 */
1236 escapeShellArgs = concatMapStringsSep " " escapeShellArg;
1237
1238 /**
1239 Test whether the given `name` is a valid POSIX shell variable name.
1240
1241 # Inputs
1242
1243 `name`
1244 : 1\. Function argument
1245
1246 # Type
1247
1248 ```
1249 string -> bool
1250 ```
1251
1252 # Examples
1253 :::{.example}
1254 ## `lib.strings.isValidPosixName` usage example
1255
1256 ```nix
1257 isValidPosixName "foo_bar000"
1258 => true
1259 isValidPosixName "0-bad.jpg"
1260 => false
1261 ```
1262
1263 :::
1264 */
1265 isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
1266
1267 /**
1268 Translate a Nix value into a shell variable declaration, with proper escaping.
1269
1270 The value can be a string (mapped to a regular variable), a list of strings
1271 (mapped to a Bash-style array) or an attribute set of strings (mapped to a
1272 Bash-style associative array). Note that "string" includes string-coercible
1273 values like paths or derivations.
1274
1275 Strings are translated into POSIX sh-compatible code; lists and attribute sets
1276 assume a shell that understands Bash syntax (e.g. Bash or ZSH).
1277
1278 # Inputs
1279
1280 `name`
1281 : 1\. Function argument
1282
1283 `value`
1284 : 2\. Function argument
1285
1286 # Type
1287
1288 ```
1289 string -> ( string | [string] | { ${name} :: string; } ) -> string
1290 ```
1291
1292 # Examples
1293 :::{.example}
1294 ## `lib.strings.toShellVar` usage example
1295
1296 ```nix
1297 ''
1298 ${toShellVar "foo" "some string"}
1299 [[ "$foo" == "some string" ]]
1300 ''
1301 ```
1302
1303 :::
1304 */
1305 toShellVar =
1306 name: value:
1307 lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
1308 if isAttrs value && !isStringLike value then
1309 "declare -A ${name}=(${
1310 concatStringsSep " " (lib.mapAttrsToList (n: v: "[${escapeShellArg n}]=${escapeShellArg v}") value)
1311 })"
1312 else if isList value then
1313 "declare -a ${name}=(${escapeShellArgs value})"
1314 else
1315 "${name}=${escapeShellArg value}"
1316 );
1317
1318 /**
1319 Translate an attribute set `vars` into corresponding shell variable declarations
1320 using `toShellVar`.
1321
1322 # Inputs
1323
1324 `vars`
1325 : 1\. Function argument
1326
1327 # Type
1328
1329 ```
1330 toShellVars :: {
1331 ${name} :: string | [ string ] | { ${key} :: string; };
1332 } -> string
1333 ```
1334
1335 # Examples
1336 :::{.example}
1337 ## `lib.strings.toShellVars` usage example
1338
1339 ```nix
1340 let
1341 foo = "value";
1342 bar = foo;
1343 in ''
1344 ${toShellVars { inherit foo bar; }}
1345 [[ "$foo" == "$bar" ]]
1346 ''
1347 ```
1348
1349 :::
1350 */
1351 toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
1352
1353 /**
1354 Turn a string `s` into a Nix expression representing that string
1355
1356 # Inputs
1357
1358 `s`
1359 : 1\. Function argument
1360
1361 # Type
1362
1363 ```
1364 escapeNixString :: string -> string
1365 ```
1366
1367 # Examples
1368 :::{.example}
1369 ## `lib.strings.escapeNixString` usage example
1370
1371 ```nix
1372 escapeNixString "hello\${}\n"
1373 => "\"hello\\\${}\\n\""
1374 ```
1375
1376 :::
1377 */
1378 escapeNixString = s: escape [ "$" ] (toJSON s);
1379
1380 /**
1381 Turn a string `s` into an exact regular expression
1382
1383 # Inputs
1384
1385 `s`
1386 : 1\. Function argument
1387
1388 # Type
1389
1390 ```
1391 escapeRegex :: string -> string
1392 ```
1393
1394 # Examples
1395 :::{.example}
1396 ## `lib.strings.escapeRegex` usage example
1397
1398 ```nix
1399 escapeRegex "[^a-z]*"
1400 => "\\[\\^a-z]\\*"
1401 ```
1402
1403 :::
1404 */
1405 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
1406
1407 /**
1408 Quotes a string `s` if it can't be used as an identifier directly.
1409
1410 # Inputs
1411
1412 `s`
1413 : 1\. Function argument
1414
1415 # Type
1416
1417 ```
1418 escapeNixIdentifier :: string -> string
1419 ```
1420
1421 # Examples
1422 :::{.example}
1423 ## `lib.strings.escapeNixIdentifier` usage example
1424
1425 ```nix
1426 escapeNixIdentifier "hello"
1427 => "hello"
1428 escapeNixIdentifier "0abc"
1429 => "\"0abc\""
1430 ```
1431
1432 :::
1433 */
1434 escapeNixIdentifier =
1435 s:
1436 # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
1437 if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null then s else escapeNixString s;
1438
1439 /**
1440 Escapes a string `s` such that it is safe to include verbatim in an XML
1441 document.
1442
1443 # Inputs
1444
1445 `s`
1446 : 1\. Function argument
1447
1448 # Type
1449
1450 ```
1451 escapeXML :: string -> string
1452 ```
1453
1454 # Examples
1455 :::{.example}
1456 ## `lib.strings.escapeXML` usage example
1457
1458 ```nix
1459 escapeXML ''"test" 'test' < & >''
1460 => ""test" 'test' < & >"
1461 ```
1462
1463 :::
1464 */
1465 escapeXML =
1466 builtins.replaceStrings
1467 [ "\"" "'" "<" ">" "&" ]
1468 [ """ "'" "<" ">" "&" ];
1469
1470 # warning added 12-12-2022
1471 replaceChars = lib.warn "lib.replaceChars is a deprecated alias of lib.replaceStrings." builtins.replaceStrings;
1472
1473 # Case conversion utilities.
1474 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
1475 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1476
1477 /**
1478 Converts an ASCII string `s` to lower-case.
1479
1480 # Inputs
1481
1482 `s`
1483 : The string to convert to lower-case.
1484
1485 # Type
1486
1487 ```
1488 toLower :: string -> string
1489 ```
1490
1491 # Examples
1492 :::{.example}
1493 ## `lib.strings.toLower` usage example
1494
1495 ```nix
1496 toLower "HOME"
1497 => "home"
1498 ```
1499
1500 :::
1501 */
1502 toLower = replaceStrings upperChars lowerChars;
1503
1504 /**
1505 Converts an ASCII string `s` to upper-case.
1506
1507 # Inputs
1508
1509 `s`
1510 : The string to convert to upper-case.
1511
1512 # Type
1513
1514 ```
1515 toUpper :: string -> string
1516 ```
1517
1518 # Examples
1519 :::{.example}
1520 ## `lib.strings.toUpper` usage example
1521
1522 ```nix
1523 toUpper "home"
1524 => "HOME"
1525 ```
1526
1527 :::
1528 */
1529 toUpper = replaceStrings lowerChars upperChars;
1530
1531 /**
1532 Converts the first character of a string `s` to upper-case.
1533
1534 # Inputs
1535
1536 `str`
1537 : The string to convert to sentence case.
1538
1539 # Type
1540
1541 ```
1542 toSentenceCase :: string -> string
1543 ```
1544
1545 # Examples
1546 :::{.example}
1547 ## `lib.strings.toSentenceCase` usage example
1548
1549 ```nix
1550 toSentenceCase "home"
1551 => "Home"
1552 ```
1553
1554 :::
1555 */
1556 toSentenceCase =
1557 str:
1558 lib.throwIfNot (isString str)
1559 "toSentenceCase does only accepts string values, but got ${typeOf str}"
1560 (
1561 let
1562 firstChar = substring 0 1 str;
1563 rest = substring 1 (stringLength str) str;
1564 in
1565 addContextFrom str (toUpper firstChar + toLower rest)
1566 );
1567
1568 /**
1569 Converts a string to camelCase. Handles snake_case, PascalCase,
1570 kebab-case strings as well as strings delimited by spaces.
1571
1572 # Inputs
1573
1574 `string`
1575 : The string to convert to camelCase
1576
1577 # Type
1578
1579 ```
1580 toCamelCase :: string -> string
1581 ```
1582
1583 # Examples
1584 :::{.example}
1585 ## `lib.strings.toCamelCase` usage example
1586
1587 ```nix
1588 toCamelCase "hello-world"
1589 => "helloWorld"
1590 toCamelCase "hello_world"
1591 => "helloWorld"
1592 toCamelCase "hello world"
1593 => "helloWorld"
1594 toCamelCase "HelloWorld"
1595 => "helloWorld"
1596 ```
1597
1598 :::
1599 */
1600 toCamelCase =
1601 str:
1602 lib.throwIfNot (isString str) "toCamelCase does only accepts string values, but got ${typeOf str}" (
1603 let
1604 separators = splitStringBy (
1605 prev: curr:
1606 elem curr [
1607 "-"
1608 "_"
1609 " "
1610 ]
1611 ) false str;
1612
1613 parts = lib.flatten (
1614 map (splitStringBy (
1615 prev: curr: match "[a-z]" prev != null && match "[A-Z]" curr != null
1616 ) true) separators
1617 );
1618
1619 first = if length parts > 0 then toLower (head parts) else "";
1620 rest = if length parts > 1 then map toSentenceCase (tail parts) else [ ];
1621 in
1622 concatStrings (map (addContextFrom str) ([ first ] ++ rest))
1623 );
1624
1625 /**
1626 Appends string context from string like object `src` to `target`.
1627
1628 :::{.warning}
1629 This is an implementation
1630 detail of Nix and should be used carefully.
1631 :::
1632
1633 Strings in Nix carry an invisible `context` which is a list of strings
1634 representing store paths. If the string is later used in a derivation
1635 attribute, the derivation will properly populate the inputDrvs and
1636 inputSrcs.
1637
1638 # Inputs
1639
1640 `src`
1641 : The string to take the context from. If the argument is not a string,
1642 it will be implicitly converted to a string.
1643
1644 `target`
1645 : The string to append the context to. If the argument is not a string,
1646 it will be implicitly converted to a string.
1647
1648 # Type
1649
1650 ```
1651 addContextFrom :: string -> string -> string
1652 ```
1653
1654 # Examples
1655 :::{.example}
1656 ## `lib.strings.addContextFrom` usage example
1657
1658 ```nix
1659 pkgs = import <nixpkgs> { };
1660 addContextFrom pkgs.coreutils "bar"
1661 => "bar"
1662 ```
1663
1664 The context can be displayed using the `toString` function:
1665
1666 ```nix
1667 nix-repl> builtins.getContext (lib.strings.addContextFrom pkgs.coreutils "bar")
1668 {
1669 "/nix/store/m1s1d2dk2dqqlw3j90jl3cjy2cykbdxz-coreutils-9.5.drv" = { ... };
1670 }
1671 ```
1672
1673 :::
1674 */
1675 addContextFrom = src: target: substring 0 0 src + target;
1676
1677 /**
1678 Cut a string with a separator and produces a list of strings which
1679 were separated by this separator.
1680
1681 # Inputs
1682
1683 `sep`
1684 : 1\. Function argument
1685
1686 `s`
1687 : 2\. Function argument
1688
1689 # Type
1690
1691 ```
1692 splitString :: string -> string -> [string]
1693 ```
1694
1695 # Examples
1696 :::{.example}
1697 ## `lib.strings.splitString` usage example
1698
1699 ```nix
1700 splitString "." "foo.bar.baz"
1701 => [ "foo" "bar" "baz" ]
1702 splitString "/" "/usr/local/bin"
1703 => [ "" "usr" "local" "bin" ]
1704 ```
1705
1706 :::
1707 */
1708 splitString =
1709 sep: s:
1710 let
1711 splits = builtins.filter builtins.isString (
1712 builtins.split (escapeRegex (toString sep)) (toString s)
1713 );
1714 in
1715 map (addContextFrom s) splits;
1716
1717 /**
1718 Splits a string into substrings based on a predicate that examines adjacent characters.
1719
1720 This function provides a flexible way to split strings by checking pairs of characters
1721 against a custom predicate function. Unlike simpler splitting functions, this allows
1722 for context-aware splitting based on character transitions and patterns.
1723
1724 # Inputs
1725
1726 `predicate`
1727 : Function that takes two arguments (previous character and current character)
1728 and returns true when the string should be split at the current position.
1729 For the first character, previous will be "" (empty string).
1730
1731 `keepSplit`
1732 : Boolean that determines whether the splitting character should be kept as
1733 part of the result. If true, the character will be included at the beginning
1734 of the next substring; if false, it will be discarded.
1735
1736 `str`
1737 : The input string to split.
1738
1739 # Return
1740
1741 A list of substrings from the original string, split according to the predicate.
1742
1743 # Type
1744
1745 ```
1746 splitStringBy :: (string -> string -> bool) -> bool -> string -> [string]
1747 ```
1748
1749 # Examples
1750 :::{.example}
1751 ## `lib.strings.splitStringBy` usage example
1752
1753 Split on periods and hyphens, discarding the separators:
1754 ```nix
1755 splitStringBy (prev: curr: builtins.elem curr [ "." "-" ]) false "foo.bar-baz"
1756 => [ "foo" "bar" "baz" ]
1757 ```
1758
1759 Split on transitions from lowercase to uppercase, keeping the uppercase characters:
1760 ```nix
1761 splitStringBy (prev: curr: builtins.match "[a-z]" prev != null && builtins.match "[A-Z]" curr != null) true "fooBarBaz"
1762 => [ "foo" "Bar" "Baz" ]
1763 ```
1764
1765 Handle leading separators correctly:
1766 ```nix
1767 splitStringBy (prev: curr: builtins.elem curr [ "." ]) false ".foo.bar.baz"
1768 => [ "" "foo" "bar" "baz" ]
1769 ```
1770
1771 Handle trailing separators correctly:
1772 ```nix
1773 splitStringBy (prev: curr: builtins.elem curr [ "." ]) false "foo.bar.baz."
1774 => [ "foo" "bar" "baz" "" ]
1775 ```
1776 :::
1777 */
1778 splitStringBy =
1779 predicate: keepSplit: str:
1780 let
1781 len = stringLength str;
1782
1783 # Helper function that processes the string character by character
1784 go =
1785 pos: currentPart: result:
1786 # Base case: reached end of string
1787 if pos == len then
1788 result ++ [ currentPart ]
1789 else
1790 let
1791 currChar = substring pos 1 str;
1792 prevChar = if pos > 0 then substring (pos - 1) 1 str else "";
1793 isSplit = predicate prevChar currChar;
1794 in
1795 if isSplit then
1796 # Split here - add current part to results and start a new one
1797 let
1798 newResult = result ++ [ currentPart ];
1799 newCurrentPart = if keepSplit then currChar else "";
1800 in
1801 go (pos + 1) newCurrentPart newResult
1802 else
1803 # Keep building current part
1804 go (pos + 1) (currentPart + currChar) result;
1805 in
1806 if len == 0 then [ (addContextFrom str "") ] else map (addContextFrom str) (go 0 "" [ ]);
1807
1808 /**
1809 Return a string without the specified prefix, if the prefix matches.
1810
1811 # Inputs
1812
1813 `prefix`
1814 : Prefix to remove if it matches
1815
1816 `str`
1817 : Input string
1818
1819 # Type
1820
1821 ```
1822 removePrefix :: string -> string -> string
1823 ```
1824
1825 # Examples
1826 :::{.example}
1827 ## `lib.strings.removePrefix` usage example
1828
1829 ```nix
1830 removePrefix "foo." "foo.bar.baz"
1831 => "bar.baz"
1832 removePrefix "xxx" "foo.bar.baz"
1833 => "foo.bar.baz"
1834 ```
1835
1836 :::
1837 */
1838 removePrefix =
1839 prefix: str:
1840 # Before 23.05, paths would be copied to the store before converting them
1841 # to strings and comparing. This was surprising and confusing.
1842 warnIf (isPath prefix)
1843 ''
1844 lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported.
1845 There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case.
1846 This function also copies the path to the Nix store, which may not be what you want.
1847 This behavior is deprecated and will throw an error in the future.''
1848 (
1849 let
1850 preLen = stringLength prefix;
1851 in
1852 if substring 0 preLen str == prefix then
1853 # -1 will take the string until the end
1854 substring preLen (-1) str
1855 else
1856 str
1857 );
1858
1859 /**
1860 Return a string without the specified suffix, if the suffix matches.
1861
1862 # Inputs
1863
1864 `suffix`
1865 : Suffix to remove if it matches
1866
1867 `str`
1868 : Input string
1869
1870 # Type
1871
1872 ```
1873 removeSuffix :: string -> string -> string
1874 ```
1875
1876 # Examples
1877 :::{.example}
1878 ## `lib.strings.removeSuffix` usage example
1879
1880 ```nix
1881 removeSuffix "front" "homefront"
1882 => "home"
1883 removeSuffix "xxx" "homefront"
1884 => "homefront"
1885 ```
1886
1887 :::
1888 */
1889 removeSuffix =
1890 suffix: str:
1891 # Before 23.05, paths would be copied to the store before converting them
1892 # to strings and comparing. This was surprising and confusing.
1893 warnIf (isPath suffix)
1894 ''
1895 lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
1896 There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case.
1897 This function also copies the path to the Nix store, which may not be what you want.
1898 This behavior is deprecated and will throw an error in the future.''
1899 (
1900 let
1901 sufLen = stringLength suffix;
1902 sLen = stringLength str;
1903 in
1904 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
1905 substring 0 (sLen - sufLen) str
1906 else
1907 str
1908 );
1909
1910 /**
1911 Return true if string `v1` denotes a version older than `v2`.
1912
1913 # Inputs
1914
1915 `v1`
1916 : 1\. Function argument
1917
1918 `v2`
1919 : 2\. Function argument
1920
1921 # Type
1922
1923 ```
1924 versionOlder :: String -> String -> Bool
1925 ```
1926
1927 # Examples
1928 :::{.example}
1929 ## `lib.strings.versionOlder` usage example
1930
1931 ```nix
1932 versionOlder "1.1" "1.2"
1933 => true
1934 versionOlder "1.1" "1.1"
1935 => false
1936 ```
1937
1938 :::
1939 */
1940 versionOlder = v1: v2: compareVersions v2 v1 == 1;
1941
1942 /**
1943 Return true if string v1 denotes a version equal to or newer than v2.
1944
1945 # Inputs
1946
1947 `v1`
1948 : 1\. Function argument
1949
1950 `v2`
1951 : 2\. Function argument
1952
1953 # Type
1954
1955 ```
1956 versionAtLeast :: String -> String -> Bool
1957 ```
1958
1959 # Examples
1960 :::{.example}
1961 ## `lib.strings.versionAtLeast` usage example
1962
1963 ```nix
1964 versionAtLeast "1.1" "1.0"
1965 => true
1966 versionAtLeast "1.1" "1.1"
1967 => true
1968 versionAtLeast "1.1" "1.2"
1969 => false
1970 ```
1971
1972 :::
1973 */
1974 versionAtLeast = v1: v2: !versionOlder v1 v2;
1975
1976 /**
1977 This function takes an argument `x` that's either a derivation or a
1978 derivation's "name" attribute and extracts the name part from that
1979 argument.
1980
1981 # Inputs
1982
1983 `x`
1984 : 1\. Function argument
1985
1986 # Type
1987
1988 ```
1989 getName :: String | Derivation -> String
1990 ```
1991
1992 # Examples
1993 :::{.example}
1994 ## `lib.strings.getName` usage example
1995
1996 ```nix
1997 getName "youtube-dl-2016.01.01"
1998 => "youtube-dl"
1999 getName pkgs.youtube-dl
2000 => "youtube-dl"
2001 ```
2002
2003 :::
2004 */
2005 getName =
2006 let
2007 parse = drv: (parseDrvName drv).name;
2008 in
2009 x: if isString x then parse x else x.pname or (parse x.name);
2010
2011 /**
2012 This function takes an argument `x` that's either a derivation or a
2013 derivation's "name" attribute and extracts the version part from that
2014 argument.
2015
2016 # Inputs
2017
2018 `x`
2019 : 1\. Function argument
2020
2021 # Type
2022
2023 ```
2024 getVersion :: String | Derivation -> String
2025 ```
2026
2027 # Examples
2028 :::{.example}
2029 ## `lib.strings.getVersion` usage example
2030
2031 ```nix
2032 getVersion "youtube-dl-2016.01.01"
2033 => "2016.01.01"
2034 getVersion pkgs.youtube-dl
2035 => "2016.01.01"
2036 ```
2037
2038 :::
2039 */
2040 getVersion =
2041 let
2042 parse = drv: (parseDrvName drv).version;
2043 in
2044 x: if isString x then parse x else x.version or (parse x.name);
2045
2046 /**
2047 Extract name and version from a URL as shown in the examples.
2048
2049 Separator `sep` is used to determine the end of the extension.
2050
2051 # Inputs
2052
2053 `url`
2054 : 1\. Function argument
2055
2056 `sep`
2057 : 2\. Function argument
2058
2059 # Type
2060
2061 ```
2062 nameFromURL :: String -> String
2063 ```
2064
2065 # Examples
2066 :::{.example}
2067 ## `lib.strings.nameFromURL` usage example
2068
2069 ```nix
2070 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
2071 => "nix"
2072 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
2073 => "nix-1.7-x86"
2074 ```
2075
2076 :::
2077 */
2078 nameFromURL =
2079 url: sep:
2080 let
2081 components = splitString "/" url;
2082 filename = lib.last components;
2083 name = head (splitString sep filename);
2084 in
2085 assert name != filename;
2086 name;
2087
2088 /**
2089 Create a `"-D<feature>:<type>=<value>"` string that can be passed to typical
2090 CMake invocations.
2091
2092 # Inputs
2093
2094 `feature`
2095 : The feature to be set
2096
2097 `type`
2098 : The type of the feature to be set, as described in
2099 https://cmake.org/cmake/help/latest/command/set.html
2100 the possible values (case insensitive) are:
2101 BOOL FILEPATH PATH STRING INTERNAL LIST
2102
2103 `value`
2104 : The desired value
2105
2106 # Type
2107
2108 ```
2109 cmakeOptionType :: string -> string -> string -> string
2110 ```
2111
2112 # Examples
2113 :::{.example}
2114 ## `lib.strings.cmakeOptionType` usage example
2115
2116 ```nix
2117 cmakeOptionType "string" "ENGINE" "sdl2"
2118 => "-DENGINE:STRING=sdl2"
2119 ```
2120
2121 :::
2122 */
2123 cmakeOptionType =
2124 let
2125 types = [
2126 "BOOL"
2127 "FILEPATH"
2128 "PATH"
2129 "STRING"
2130 "INTERNAL"
2131 "LIST"
2132 ];
2133 in
2134 type: feature: value:
2135 assert (elem (toUpper type) types);
2136 assert (isString feature);
2137 assert (isString value);
2138 "-D${feature}:${toUpper type}=${value}";
2139
2140 /**
2141 Create a -D<condition>={TRUE,FALSE} string that can be passed to typical
2142 CMake invocations.
2143
2144 # Inputs
2145
2146 `condition`
2147 : The condition to be made true or false
2148
2149 `flag`
2150 : The controlling flag of the condition
2151
2152 # Type
2153
2154 ```
2155 cmakeBool :: string -> bool -> string
2156 ```
2157
2158 # Examples
2159 :::{.example}
2160 ## `lib.strings.cmakeBool` usage example
2161
2162 ```nix
2163 cmakeBool "ENABLE_STATIC_LIBS" false
2164 => "-DENABLESTATIC_LIBS:BOOL=FALSE"
2165 ```
2166
2167 :::
2168 */
2169 cmakeBool =
2170 condition: flag:
2171 assert (lib.isString condition);
2172 assert (lib.isBool flag);
2173 cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag));
2174
2175 /**
2176 Create a -D<feature>:STRING=<value> string that can be passed to typical
2177 CMake invocations.
2178 This is the most typical usage, so it deserves a special case.
2179
2180 # Inputs
2181
2182 `feature`
2183 : The feature to be set
2184
2185 `value`
2186 : The desired value
2187
2188 # Type
2189
2190 ```
2191 cmakeFeature :: string -> string -> string
2192 ```
2193
2194 # Examples
2195 :::{.example}
2196 ## `lib.strings.cmakeFeature` usage example
2197
2198 ```nix
2199 cmakeFeature "MODULES" "badblock"
2200 => "-DMODULES:STRING=badblock"
2201 ```
2202
2203 :::
2204 */
2205 cmakeFeature =
2206 feature: value:
2207 assert (lib.isString feature);
2208 assert (lib.isString value);
2209 cmakeOptionType "string" feature value;
2210
2211 /**
2212 Create a -D<feature>=<value> string that can be passed to typical Meson
2213 invocations.
2214
2215 # Inputs
2216
2217 `feature`
2218 : The feature to be set
2219
2220 `value`
2221 : The desired value
2222
2223 # Type
2224
2225 ```
2226 mesonOption :: string -> string -> string
2227 ```
2228
2229 # Examples
2230 :::{.example}
2231 ## `lib.strings.mesonOption` usage example
2232
2233 ```nix
2234 mesonOption "engine" "opengl"
2235 => "-Dengine=opengl"
2236 ```
2237
2238 :::
2239 */
2240 mesonOption =
2241 feature: value:
2242 assert (lib.isString feature);
2243 assert (lib.isString value);
2244 "-D${feature}=${value}";
2245
2246 /**
2247 Create a -D<condition>={true,false} string that can be passed to typical
2248 Meson invocations.
2249
2250 # Inputs
2251
2252 `condition`
2253 : The condition to be made true or false
2254
2255 `flag`
2256 : The controlling flag of the condition
2257
2258 # Type
2259
2260 ```
2261 mesonBool :: string -> bool -> string
2262 ```
2263
2264 # Examples
2265 :::{.example}
2266 ## `lib.strings.mesonBool` usage example
2267
2268 ```nix
2269 mesonBool "hardened" true
2270 => "-Dhardened=true"
2271 mesonBool "static" false
2272 => "-Dstatic=false"
2273 ```
2274
2275 :::
2276 */
2277 mesonBool =
2278 condition: flag:
2279 assert (lib.isString condition);
2280 assert (lib.isBool flag);
2281 mesonOption condition (lib.boolToString flag);
2282
2283 /**
2284 Create a -D<feature>={enabled,disabled} string that can be passed to
2285 typical Meson invocations.
2286
2287 # Inputs
2288
2289 `feature`
2290 : The feature to be enabled or disabled
2291
2292 `flag`
2293 : The controlling flag
2294
2295 # Type
2296
2297 ```
2298 mesonEnable :: string -> bool -> string
2299 ```
2300
2301 # Examples
2302 :::{.example}
2303 ## `lib.strings.mesonEnable` usage example
2304
2305 ```nix
2306 mesonEnable "docs" true
2307 => "-Ddocs=enabled"
2308 mesonEnable "savage" false
2309 => "-Dsavage=disabled"
2310 ```
2311
2312 :::
2313 */
2314 mesonEnable =
2315 feature: flag:
2316 assert (lib.isString feature);
2317 assert (lib.isBool flag);
2318 mesonOption feature (if flag then "enabled" else "disabled");
2319
2320 /**
2321 Create an --{enable,disable}-<feature> string that can be passed to
2322 standard GNU Autoconf scripts.
2323
2324 # Inputs
2325
2326 `flag`
2327 : 1\. Function argument
2328
2329 `feature`
2330 : 2\. Function argument
2331
2332 # Type
2333
2334 ```
2335 enableFeature :: bool -> string -> string
2336 ```
2337
2338 # Examples
2339 :::{.example}
2340 ## `lib.strings.enableFeature` usage example
2341
2342 ```nix
2343 enableFeature true "shared"
2344 => "--enable-shared"
2345 enableFeature false "shared"
2346 => "--disable-shared"
2347 ```
2348
2349 :::
2350 */
2351 enableFeature =
2352 flag: feature:
2353 assert lib.isBool flag;
2354 assert lib.isString feature; # e.g. passing openssl instead of "openssl"
2355 "--${if flag then "enable" else "disable"}-${feature}";
2356
2357 /**
2358 Create an --{enable-<feature>=<value>,disable-<feature>} string that can be passed to
2359 standard GNU Autoconf scripts.
2360
2361 # Inputs
2362
2363 `flag`
2364 : 1\. Function argument
2365
2366 `feature`
2367 : 2\. Function argument
2368
2369 `value`
2370 : 3\. Function argument
2371
2372 # Type
2373
2374 ```
2375 enableFeatureAs :: bool -> string -> string -> string
2376 ```
2377
2378 # Examples
2379 :::{.example}
2380 ## `lib.strings.enableFeatureAs` usage example
2381
2382 ```nix
2383 enableFeatureAs true "shared" "foo"
2384 => "--enable-shared=foo"
2385 enableFeatureAs false "shared" (throw "ignored")
2386 => "--disable-shared"
2387 ```
2388
2389 :::
2390 */
2391 enableFeatureAs =
2392 flag: feature: value:
2393 enableFeature flag feature + optionalString flag "=${value}";
2394
2395 /**
2396 Create an --{with,without}-<feature> string that can be passed to
2397 standard GNU Autoconf scripts.
2398
2399 # Inputs
2400
2401 `flag`
2402 : 1\. Function argument
2403
2404 `feature`
2405 : 2\. Function argument
2406
2407 # Type
2408
2409 ```
2410 withFeature :: bool -> string -> string
2411 ```
2412
2413 # Examples
2414 :::{.example}
2415 ## `lib.strings.withFeature` usage example
2416
2417 ```nix
2418 withFeature true "shared"
2419 => "--with-shared"
2420 withFeature false "shared"
2421 => "--without-shared"
2422 ```
2423
2424 :::
2425 */
2426 withFeature =
2427 flag: feature:
2428 assert isString feature; # e.g. passing openssl instead of "openssl"
2429 "--${if flag then "with" else "without"}-${feature}";
2430
2431 /**
2432 Create an --{with-<feature>=<value>,without-<feature>} string that can be passed to
2433 standard GNU Autoconf scripts.
2434
2435 # Inputs
2436
2437 `flag`
2438 : 1\. Function argument
2439
2440 `feature`
2441 : 2\. Function argument
2442
2443 `value`
2444 : 3\. Function argument
2445
2446 # Type
2447
2448 ```
2449 withFeatureAs :: bool -> string -> string -> string
2450 ```
2451
2452 # Examples
2453 :::{.example}
2454 ## `lib.strings.withFeatureAs` usage example
2455
2456 ```nix
2457 withFeatureAs true "shared" "foo"
2458 => "--with-shared=foo"
2459 withFeatureAs false "shared" (throw "ignored")
2460 => "--without-shared"
2461 ```
2462
2463 :::
2464 */
2465 withFeatureAs =
2466 flag: feature: value:
2467 withFeature flag feature + optionalString flag "=${value}";
2468
2469 /**
2470 Create a fixed width string with additional prefix to match
2471 required width.
2472
2473 This function will fail if the input string is longer than the
2474 requested length.
2475
2476 # Inputs
2477
2478 `width`
2479 : 1\. Function argument
2480
2481 `filler`
2482 : 2\. Function argument
2483
2484 `str`
2485 : 3\. Function argument
2486
2487 # Type
2488
2489 ```
2490 fixedWidthString :: int -> string -> string -> string
2491 ```
2492
2493 # Examples
2494 :::{.example}
2495 ## `lib.strings.fixedWidthString` usage example
2496
2497 ```nix
2498 fixedWidthString 5 "0" (toString 15)
2499 => "00015"
2500 ```
2501
2502 :::
2503 */
2504 fixedWidthString =
2505 width: filler: str:
2506 let
2507 strw = lib.stringLength str;
2508 reqWidth = width - (lib.stringLength filler);
2509 in
2510 assert lib.assertMsg (strw <= width)
2511 "fixedWidthString: requested string length (${toString width}) must not be shorter than actual length (${toString strw})";
2512 if strw == width then str else filler + fixedWidthString reqWidth filler str;
2513
2514 /**
2515 Format a number adding leading zeroes up to fixed width.
2516
2517 # Inputs
2518
2519 `width`
2520 : 1\. Function argument
2521
2522 `n`
2523 : 2\. Function argument
2524
2525 # Type
2526
2527 ```
2528 fixedWidthNumber :: int -> int -> string
2529 ```
2530
2531 # Examples
2532 :::{.example}
2533 ## `lib.strings.fixedWidthNumber` usage example
2534
2535 ```nix
2536 fixedWidthNumber 5 15
2537 => "00015"
2538 ```
2539
2540 :::
2541 */
2542 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
2543
2544 /**
2545 Convert a float to a string, but emit a warning when precision is lost
2546 during the conversion
2547
2548 # Inputs
2549
2550 `float`
2551 : 1\. Function argument
2552
2553 # Type
2554
2555 ```
2556 floatToString :: float -> string
2557 ```
2558
2559 # Examples
2560 :::{.example}
2561 ## `lib.strings.floatToString` usage example
2562
2563 ```nix
2564 floatToString 0.000001
2565 => "0.000001"
2566 floatToString 0.0000001
2567 => trace: warning: Imprecise conversion from float to string 0.000000
2568 "0.000000"
2569 ```
2570
2571 :::
2572 */
2573 floatToString =
2574 float:
2575 let
2576 result = toString float;
2577 precise = float == fromJSON result;
2578 in
2579 lib.warnIf (!precise) "Imprecise conversion from float to string ${result}" result;
2580
2581 /**
2582 Check whether a value `val` can be coerced to a string.
2583
2584 :::{.warning}
2585 Soft-deprecated function. While the original implementation is available as
2586 `isConvertibleWithToString`, consider using `isStringLike` instead, if suitable.
2587 :::
2588
2589 # Inputs
2590
2591 `val`
2592 : 1\. Function argument
2593
2594 # Type
2595
2596 ```
2597 isCoercibleToString :: a -> bool
2598 ```
2599 */
2600 isCoercibleToString =
2601 lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2305)
2602 "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."
2603 isConvertibleWithToString;
2604
2605 /**
2606 Check whether a list or other value `x` can be passed to toString.
2607
2608 Many types of value are coercible to string this way, including `int`, `float`,
2609 `null`, `bool`, `list` of similarly coercible values.
2610
2611 # Inputs
2612
2613 `val`
2614 : 1\. Function argument
2615
2616 # Type
2617
2618 ```
2619 isConvertibleWithToString :: a -> bool
2620 ```
2621 */
2622 isConvertibleWithToString =
2623 let
2624 types = [
2625 "null"
2626 "int"
2627 "float"
2628 "bool"
2629 ];
2630 in
2631 x: isStringLike x || elem (typeOf x) types || (isList x && lib.all isConvertibleWithToString x);
2632
2633 /**
2634 Check whether a value can be coerced to a string.
2635 The value must be a string, path, or attribute set.
2636
2637 String-like values can be used without explicit conversion in
2638 string interpolations and in most functions that expect a string.
2639
2640 # Inputs
2641
2642 `x`
2643 : 1\. Function argument
2644
2645 # Type
2646
2647 ```
2648 isStringLike :: a -> bool
2649 ```
2650 */
2651 isStringLike = x: isString x || isPath x || x ? outPath || x ? __toString;
2652
2653 /**
2654 Check whether a value `x` is a store path.
2655
2656 # Inputs
2657
2658 `x`
2659 : 1\. Function argument
2660
2661 # Type
2662
2663 ```
2664 isStorePath :: a -> bool
2665 ```
2666
2667 # Examples
2668 :::{.example}
2669 ## `lib.strings.isStorePath` usage example
2670
2671 ```nix
2672 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
2673 => false
2674 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
2675 => true
2676 isStorePath pkgs.python
2677 => true
2678 isStorePath [] || isStorePath 42 || isStorePath {} || …
2679 => false
2680 ```
2681
2682 :::
2683 */
2684 isStorePath =
2685 x:
2686 if isStringLike x then
2687 let
2688 str = toString x;
2689 in
2690 substring 0 1 str == "/"
2691 && (
2692 dirOf str == storeDir
2693 # Match content‐addressed derivations, which _currently_ do not have a
2694 # store directory prefix.
2695 # This is a workaround for https://github.com/NixOS/nix/issues/12361
2696 # which was needed during the experimental phase of ca-derivations and
2697 # should be removed once the issue has been resolved.
2698 || builtins.match "/[0-9a-z]{52}" str != null
2699 )
2700 else
2701 false;
2702
2703 /**
2704 Parse a string as an int. Does not support parsing of integers with preceding zero due to
2705 ambiguity between zero-padded and octal numbers. See toIntBase10.
2706
2707 # Inputs
2708
2709 `str`
2710 : A string to be interpreted as an int.
2711
2712 # Type
2713
2714 ```
2715 toInt :: string -> int
2716 ```
2717
2718 # Examples
2719 :::{.example}
2720 ## `lib.strings.toInt` usage example
2721
2722 ```nix
2723 toInt "1337"
2724 => 1337
2725
2726 toInt "-4"
2727 => -4
2728
2729 toInt " 123 "
2730 => 123
2731
2732 toInt "00024"
2733 => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
2734
2735 toInt "3.14"
2736 => error: floating point JSON numbers are not supported
2737 ```
2738
2739 :::
2740 */
2741 toInt =
2742 let
2743 matchStripInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*";
2744 matchLeadingZero = match "0[[:digit:]]+";
2745 in
2746 str:
2747 let
2748 # RegEx: Match any leading whitespace, possibly a '-', one or more digits,
2749 # and finally match any trailing whitespace.
2750 strippedInput = matchStripInput str;
2751
2752 # RegEx: Match a leading '0' then one or more digits.
2753 isLeadingZero = matchLeadingZero (head strippedInput) == [ ];
2754
2755 # Attempt to parse input
2756 parsedInput = fromJSON (head strippedInput);
2757
2758 generalError = "toInt: Could not convert ${escapeNixString str} to int.";
2759
2760 in
2761 # Error on presence of non digit characters.
2762 if strippedInput == null then
2763 throw generalError
2764 # Error on presence of leading zero/octal ambiguity.
2765 else if isLeadingZero then
2766 throw "toInt: Ambiguity in interpretation of ${escapeNixString str} between octal and zero padded integer."
2767 # Error if parse function fails.
2768 else if !isInt parsedInput then
2769 throw generalError
2770 # Return result.
2771 else
2772 parsedInput;
2773
2774 /**
2775 Parse a string as a base 10 int. This supports parsing of zero-padded integers.
2776
2777 # Inputs
2778
2779 `str`
2780 : A string to be interpreted as an int.
2781
2782 # Type
2783
2784 ```
2785 toIntBase10 :: string -> int
2786 ```
2787
2788 # Examples
2789 :::{.example}
2790 ## `lib.strings.toIntBase10` usage example
2791
2792 ```nix
2793 toIntBase10 "1337"
2794 => 1337
2795
2796 toIntBase10 "-4"
2797 => -4
2798
2799 toIntBase10 " 123 "
2800 => 123
2801
2802 toIntBase10 "00024"
2803 => 24
2804
2805 toIntBase10 "3.14"
2806 => error: floating point JSON numbers are not supported
2807 ```
2808
2809 :::
2810 */
2811 toIntBase10 =
2812 let
2813 matchStripInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*";
2814 matchZero = match "0+";
2815 in
2816 str:
2817 let
2818 # RegEx: Match any leading whitespace, then match any zero padding,
2819 # capture possibly a '-' followed by one or more digits,
2820 # and finally match any trailing whitespace.
2821 strippedInput = matchStripInput str;
2822
2823 # RegEx: Match at least one '0'.
2824 isZero = matchZero (head strippedInput) == [ ];
2825
2826 # Attempt to parse input
2827 parsedInput = fromJSON (head strippedInput);
2828
2829 generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
2830
2831 in
2832 # Error on presence of non digit characters.
2833 if strippedInput == null then
2834 throw generalError
2835 # In the special case zero-padded zero (00000), return early.
2836 else if isZero then
2837 0
2838 # Error if parse function fails.
2839 else if !isInt parsedInput then
2840 throw generalError
2841 # Return result.
2842 else
2843 parsedInput;
2844
2845 /**
2846 Read a list of paths from `file`, relative to the `rootPath`.
2847 Lines beginning with `#` are treated as comments and ignored.
2848 Whitespace is significant.
2849
2850 :::{.warning}
2851 This function is deprecated and should be avoided.
2852 :::
2853
2854 :::{.note}
2855 This function is not performant and should be avoided.
2856 :::
2857
2858 # Inputs
2859
2860 `rootPath`
2861 : 1\. Function argument
2862
2863 `file`
2864 : 2\. Function argument
2865
2866 # Type
2867
2868 ```
2869 readPathsFromFile :: string -> string -> [string]
2870 ```
2871
2872 # Examples
2873 :::{.example}
2874 ## `lib.strings.readPathsFromFile` usage example
2875
2876 ```nix
2877 readPathsFromFile /prefix
2878 ./pkgs/development/libraries/qt-5/5.4/qtbase/series
2879 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
2880 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
2881 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
2882 "/prefix/nix-profiles-library-paths.patch"
2883 "/prefix/compose-search-path.patch" ]
2884 ```
2885
2886 :::
2887 */
2888 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead." (
2889 rootPath: file:
2890 let
2891 lines = lib.splitString "\n" (readFile file);
2892 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
2893 relativePaths = removeComments lines;
2894 absolutePaths = map (path: rootPath + "/${path}") relativePaths;
2895 in
2896 absolutePaths
2897 );
2898
2899 /**
2900 Read the contents of a file removing the trailing \n
2901
2902 # Inputs
2903
2904 `file`
2905 : 1\. Function argument
2906
2907 # Type
2908
2909 ```
2910 fileContents :: path -> string
2911 ```
2912
2913 # Examples
2914 :::{.example}
2915 ## `lib.strings.fileContents` usage example
2916
2917 ```nix
2918 $ echo "1.0" > ./version
2919
2920 fileContents ./version
2921 => "1.0"
2922 ```
2923
2924 :::
2925 */
2926 fileContents = file: removeSuffix "\n" (readFile file);
2927
2928 /**
2929 Creates a valid derivation name from a potentially invalid one.
2930
2931 # Inputs
2932
2933 `string`
2934 : 1\. Function argument
2935
2936 # Type
2937
2938 ```
2939 sanitizeDerivationName :: String -> String
2940 ```
2941
2942 # Examples
2943 :::{.example}
2944 ## `lib.strings.sanitizeDerivationName` usage example
2945
2946 ```nix
2947 sanitizeDerivationName "../hello.bar # foo"
2948 => "-hello.bar-foo"
2949 sanitizeDerivationName ""
2950 => "unknown"
2951 sanitizeDerivationName pkgs.hello
2952 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
2953 ```
2954
2955 :::
2956 */
2957 sanitizeDerivationName =
2958 let
2959 okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
2960 in
2961 string:
2962 # First detect the common case of already valid strings, to speed those up
2963 if stringLength string <= 207 && okRegex string != null then
2964 unsafeDiscardStringContext string
2965 else
2966 lib.pipe string [
2967 # Get rid of string context. This is safe under the assumption that the
2968 # resulting string is only used as a derivation name
2969 unsafeDiscardStringContext
2970 # Strip all leading "."
2971 (x: elemAt (match "\\.*(.*)" x) 0)
2972 # Split out all invalid characters
2973 # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
2974 # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
2975 (split "[^[:alnum:]+._?=-]+")
2976 # Replace invalid character ranges with a "-"
2977 (concatMapStrings (s: if lib.isList s then "-" else s))
2978 # Limit to 211 characters (minus 4 chars for ".drv")
2979 (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
2980 # If the result is empty, replace it with "unknown"
2981 (x: if stringLength x == 0 then "unknown" else x)
2982 ];
2983
2984 /**
2985 Computes the Levenshtein distance between two strings `a` and `b`.
2986
2987 Complexity O(n*m) where n and m are the lengths of the strings.
2988 Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
2989
2990 # Inputs
2991
2992 `a`
2993 : 1\. Function argument
2994
2995 `b`
2996 : 2\. Function argument
2997
2998 # Type
2999
3000 ```
3001 levenshtein :: string -> string -> int
3002 ```
3003
3004 # Examples
3005 :::{.example}
3006 ## `lib.strings.levenshtein` usage example
3007
3008 ```nix
3009 levenshtein "foo" "foo"
3010 => 0
3011 levenshtein "book" "hook"
3012 => 1
3013 levenshtein "hello" "Heyo"
3014 => 3
3015 ```
3016
3017 :::
3018 */
3019 levenshtein =
3020 a: b:
3021 let
3022 # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
3023 arr = lib.genList (i: lib.genList (j: dist i j) (stringLength b + 1)) (stringLength a + 1);
3024 d = x: y: lib.elemAt (lib.elemAt arr x) y;
3025 dist =
3026 i: j:
3027 let
3028 c = if substring (i - 1) 1 a == substring (j - 1) 1 b then 0 else 1;
3029 in
3030 if j == 0 then
3031 i
3032 else if i == 0 then
3033 j
3034 else
3035 lib.min (lib.min (d (i - 1) j + 1) (d i (j - 1) + 1)) (d (i - 1) (j - 1) + c);
3036 in
3037 d (stringLength a) (stringLength b);
3038
3039 /**
3040 Returns the length of the prefix that appears in both strings `a` and `b`.
3041
3042 # Inputs
3043
3044 `a`
3045 : 1\. Function argument
3046
3047 `b`
3048 : 2\. Function argument
3049
3050 # Type
3051
3052 ```
3053 commonPrefixLength :: string -> string -> int
3054 ```
3055 */
3056 commonPrefixLength =
3057 a: b:
3058 let
3059 m = lib.min (stringLength a) (stringLength b);
3060 go =
3061 i:
3062 if i >= m then
3063 m
3064 else if substring i 1 a == substring i 1 b then
3065 go (i + 1)
3066 else
3067 i;
3068 in
3069 go 0;
3070
3071 /**
3072 Returns the length of the suffix common to both strings `a` and `b`.
3073
3074 # Inputs
3075
3076 `a`
3077 : 1\. Function argument
3078
3079 `b`
3080 : 2\. Function argument
3081
3082 # Type
3083
3084 ```
3085 commonSuffixLength :: string -> string -> int
3086 ```
3087 */
3088 commonSuffixLength =
3089 a: b:
3090 let
3091 m = lib.min (stringLength a) (stringLength b);
3092 go =
3093 i:
3094 if i >= m then
3095 m
3096 else if substring (stringLength a - i - 1) 1 a == substring (stringLength b - i - 1) 1 b then
3097 go (i + 1)
3098 else
3099 i;
3100 in
3101 go 0;
3102
3103 /**
3104 Returns whether the levenshtein distance between two strings `a` and `b` is at most some value `k`.
3105
3106 Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
3107
3108 # Inputs
3109
3110 `k`
3111 : Distance threshold
3112
3113 `a`
3114 : String `a`
3115
3116 `b`
3117 : String `b`
3118
3119 # Type
3120
3121 ```
3122 levenshteinAtMost :: int -> string -> string -> bool
3123 ```
3124
3125 # Examples
3126 :::{.example}
3127 ## `lib.strings.levenshteinAtMost` usage example
3128
3129 ```nix
3130 levenshteinAtMost 0 "foo" "foo"
3131 => true
3132 levenshteinAtMost 1 "foo" "boa"
3133 => false
3134 levenshteinAtMost 2 "foo" "boa"
3135 => true
3136 levenshteinAtMost 2 "This is a sentence" "this is a sentense."
3137 => false
3138 levenshteinAtMost 3 "This is a sentence" "this is a sentense."
3139 => true
3140 ```
3141
3142 :::
3143 */
3144 levenshteinAtMost =
3145 let
3146 infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
3147
3148 # This function takes two strings stripped by their common pre and suffix,
3149 # and returns whether they differ by at most two by Levenshtein distance.
3150 # Because of this stripping, if they do indeed differ by at most two edits,
3151 # we know that those edits were (if at all) done at the start or the end,
3152 # while the middle has to have stayed the same. This fact is used in the
3153 # implementation.
3154 infixDifferAtMost2 =
3155 x: y:
3156 let
3157 xlen = stringLength x;
3158 ylen = stringLength y;
3159 # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
3160 # diff is one of 0, 1 or 2
3161 diff = xlen - ylen;
3162
3163 # Infix of x and y, stripped by the left and right most character
3164 xinfix = substring 1 (xlen - 2) x;
3165 yinfix = substring 1 (ylen - 2) y;
3166
3167 # x and y but a character deleted at the left or right
3168 xdelr = substring 0 (xlen - 1) x;
3169 xdell = substring 1 (xlen - 1) x;
3170 ydelr = substring 0 (ylen - 1) y;
3171 ydell = substring 1 (ylen - 1) y;
3172 in
3173 # A length difference of 2 can only be gotten with 2 delete edits,
3174 # which have to have happened at the start and end of x
3175 # Example: "abcdef" -> "bcde"
3176 if diff == 2 then
3177 xinfix == y
3178 # A length difference of 1 can only be gotten with a deletion on the
3179 # right and a replacement on the left or vice versa.
3180 # Example: "abcdef" -> "bcdez" or "zbcde"
3181 else if diff == 1 then
3182 xinfix == ydelr || xinfix == ydell
3183 # No length difference can either happen through replacements on both
3184 # sides, or a deletion on the left and an insertion on the right or
3185 # vice versa
3186 # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
3187 else
3188 xinfix == yinfix || xdelr == ydell || xdell == ydelr;
3189
3190 in
3191 k:
3192 if k <= 0 then
3193 a: b: a == b
3194 else
3195 let
3196 f =
3197 a: b:
3198 let
3199 alen = stringLength a;
3200 blen = stringLength b;
3201 prelen = commonPrefixLength a b;
3202 suflen = commonSuffixLength a b;
3203 presuflen = prelen + suflen;
3204 ainfix = substring prelen (alen - presuflen) a;
3205 binfix = substring prelen (blen - presuflen) b;
3206 in
3207 # Make a be the bigger string
3208 if alen < blen then
3209 f b a
3210 # If a has over k more characters than b, even with k deletes on a, b can't be reached
3211 else if alen - blen > k then
3212 false
3213 else if k == 1 then
3214 infixDifferAtMost1 ainfix binfix
3215 else if k == 2 then
3216 infixDifferAtMost2 ainfix binfix
3217 else
3218 levenshtein ainfix binfix <= k;
3219 in
3220 f;
3221}