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