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