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