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