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