at 25.11-pre 65 kB view raw
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 => "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;" 1396 ``` 1397 1398 ::: 1399 */ 1400 escapeXML = 1401 builtins.replaceStrings 1402 [ "\"" "'" "<" ">" "&" ] 1403 [ "&quot;" "&apos;" "&lt;" "&gt;" "&amp;" ]; 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}