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