at 24.05-pre 42 kB view raw
1/* String manipulation functions. */ 2{ lib }: 3let 4 5 inherit (builtins) length; 6 7 inherit (lib.trivial) warnIf; 8 9asciiTable = import ./ascii-table.nix; 10 11in 12 13rec { 14 15 inherit (builtins) 16 compareVersions 17 elem 18 elemAt 19 filter 20 fromJSON 21 genList 22 head 23 isInt 24 isList 25 isAttrs 26 isPath 27 isString 28 match 29 parseDrvName 30 readFile 31 replaceStrings 32 split 33 storeDir 34 stringLength 35 substring 36 tail 37 toJSON 38 typeOf 39 unsafeDiscardStringContext 40 ; 41 42 /* Concatenate a list of strings. 43 44 Type: concatStrings :: [string] -> string 45 46 Example: 47 concatStrings ["foo" "bar"] 48 => "foobar" 49 */ 50 concatStrings = builtins.concatStringsSep ""; 51 52 /* Map a function over a list and concatenate the resulting strings. 53 54 Type: concatMapStrings :: (a -> string) -> [a] -> string 55 56 Example: 57 concatMapStrings (x: "a" + x) ["foo" "bar"] 58 => "afooabar" 59 */ 60 concatMapStrings = f: list: concatStrings (map f list); 61 62 /* Like `concatMapStrings` except that the f functions also gets the 63 position as a parameter. 64 65 Type: concatImapStrings :: (int -> a -> string) -> [a] -> string 66 67 Example: 68 concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"] 69 => "1-foo2-bar" 70 */ 71 concatImapStrings = f: list: concatStrings (lib.imap1 f list); 72 73 /* Place an element between each element of a list 74 75 Type: intersperse :: a -> [a] -> [a] 76 77 Example: 78 intersperse "/" ["usr" "local" "bin"] 79 => ["usr" "/" "local" "/" "bin"]. 80 */ 81 intersperse = 82 # Separator to add between elements 83 separator: 84 # Input list 85 list: 86 if list == [] || length list == 1 87 then list 88 else tail (lib.concatMap (x: [separator x]) list); 89 90 /* Concatenate a list of strings with a separator between each element 91 92 Type: concatStringsSep :: string -> [string] -> string 93 94 Example: 95 concatStringsSep "/" ["usr" "local" "bin"] 96 => "usr/local/bin" 97 */ 98 concatStringsSep = builtins.concatStringsSep or (separator: list: 99 lib.foldl' (x: y: x + y) "" (intersperse separator list)); 100 101 /* Maps a function over a list of strings and then concatenates the 102 result with the specified separator interspersed between 103 elements. 104 105 Type: concatMapStringsSep :: string -> (a -> string) -> [a] -> string 106 107 Example: 108 concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"] 109 => "FOO-BAR-BAZ" 110 */ 111 concatMapStringsSep = 112 # Separator to add between elements 113 sep: 114 # Function to map over the list 115 f: 116 # List of input strings 117 list: concatStringsSep sep (map f list); 118 119 /* Same as `concatMapStringsSep`, but the mapping function 120 additionally receives the position of its argument. 121 122 Type: concatIMapStringsSep :: string -> (int -> a -> string) -> [a] -> string 123 124 Example: 125 concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ] 126 => "6-3-2" 127 */ 128 concatImapStringsSep = 129 # Separator to add between elements 130 sep: 131 # Function that receives elements and their positions 132 f: 133 # List of input strings 134 list: concatStringsSep sep (lib.imap1 f list); 135 136 /* Concatenate a list of strings, adding a newline at the end of each one. 137 Defined as `concatMapStrings (s: s + "\n")`. 138 139 Type: concatLines :: [string] -> string 140 141 Example: 142 concatLines [ "foo" "bar" ] 143 => "foo\nbar\n" 144 */ 145 concatLines = concatMapStrings (s: s + "\n"); 146 147 /* 148 Replicate a string n times, 149 and concatenate the parts into a new string. 150 151 Type: replicate :: int -> string -> string 152 153 Example: 154 replicate 3 "v" 155 => "vvv" 156 replicate 5 "hello" 157 => "hellohellohellohellohello" 158 */ 159 replicate = n: s: concatStrings (lib.lists.replicate n s); 160 161 /* Construct a Unix-style, colon-separated search path consisting of 162 the given `subDir` appended to each of the given paths. 163 164 Type: makeSearchPath :: string -> [string] -> string 165 166 Example: 167 makeSearchPath "bin" ["/root" "/usr" "/usr/local"] 168 => "/root/bin:/usr/bin:/usr/local/bin" 169 makeSearchPath "bin" [""] 170 => "/bin" 171 */ 172 makeSearchPath = 173 # Directory name to append 174 subDir: 175 # List of base paths 176 paths: 177 concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths)); 178 179 /* Construct a Unix-style search path by appending the given 180 `subDir` to the specified `output` of each of the packages. If no 181 output by the given name is found, fallback to `.out` and then to 182 the default. 183 184 Type: string -> string -> [package] -> string 185 186 Example: 187 makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ] 188 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin" 189 */ 190 makeSearchPathOutput = 191 # Package output to use 192 output: 193 # Directory name to append 194 subDir: 195 # List of packages 196 pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs); 197 198 /* Construct a library search path (such as RPATH) containing the 199 libraries for a set of packages 200 201 Example: 202 makeLibraryPath [ "/usr" "/usr/local" ] 203 => "/usr/lib:/usr/local/lib" 204 pkgs = import <nixpkgs> { } 205 makeLibraryPath [ pkgs.openssl pkgs.zlib ] 206 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib" 207 */ 208 makeLibraryPath = makeSearchPathOutput "lib" "lib"; 209 210 /* Construct a binary search path (such as $PATH) containing the 211 binaries for a set of packages. 212 213 Example: 214 makeBinPath ["/root" "/usr" "/usr/local"] 215 => "/root/bin:/usr/bin:/usr/local/bin" 216 */ 217 makeBinPath = makeSearchPathOutput "bin" "bin"; 218 219 /* Normalize path, removing extraneous /s 220 221 Type: normalizePath :: string -> string 222 223 Example: 224 normalizePath "/a//b///c/" 225 => "/a/b/c/" 226 */ 227 normalizePath = s: 228 warnIf 229 (isPath s) 230 '' 231 lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported. 232 Path values are always normalised in Nix, so there's no need to call this function on them. 233 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. 234 This behavior is deprecated and will throw an error in the future.'' 235 ( 236 builtins.foldl' 237 (x: y: if y == "/" && hasSuffix "/" x then x else x+y) 238 "" 239 (stringToCharacters s) 240 ); 241 242 /* Depending on the boolean `cond', return either the given string 243 or the empty string. Useful to concatenate against a bigger string. 244 245 Type: optionalString :: bool -> string -> string 246 247 Example: 248 optionalString true "some-string" 249 => "some-string" 250 optionalString false "some-string" 251 => "" 252 */ 253 optionalString = 254 # Condition 255 cond: 256 # String to return if condition is true 257 string: if cond then string else ""; 258 259 /* Determine whether a string has given prefix. 260 261 Type: hasPrefix :: string -> string -> bool 262 263 Example: 264 hasPrefix "foo" "foobar" 265 => true 266 hasPrefix "foo" "barfoo" 267 => false 268 */ 269 hasPrefix = 270 # Prefix to check for 271 pref: 272 # Input string 273 str: 274 # Before 23.05, paths would be copied to the store before converting them 275 # to strings and comparing. This was surprising and confusing. 276 warnIf 277 (isPath pref) 278 '' 279 lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported. 280 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case. 281 This function also copies the path to the Nix store, which may not be what you want. 282 This behavior is deprecated and will throw an error in the future. 283 You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.'' 284 (substring 0 (stringLength pref) str == pref); 285 286 /* Determine whether a string has given suffix. 287 288 Type: hasSuffix :: string -> string -> bool 289 290 Example: 291 hasSuffix "foo" "foobar" 292 => false 293 hasSuffix "foo" "barfoo" 294 => true 295 */ 296 hasSuffix = 297 # Suffix to check for 298 suffix: 299 # Input string 300 content: 301 let 302 lenContent = stringLength content; 303 lenSuffix = stringLength suffix; 304 in 305 # Before 23.05, paths would be copied to the store before converting them 306 # to strings and comparing. This was surprising and confusing. 307 warnIf 308 (isPath suffix) 309 '' 310 lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported. 311 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case. 312 This function also copies the path to the Nix store, which may not be what you want. 313 This behavior is deprecated and will throw an error in the future.'' 314 ( 315 lenContent >= lenSuffix 316 && substring (lenContent - lenSuffix) lenContent content == suffix 317 ); 318 319 /* Determine whether a string contains the given infix 320 321 Type: hasInfix :: string -> string -> bool 322 323 Example: 324 hasInfix "bc" "abcd" 325 => true 326 hasInfix "ab" "abcd" 327 => true 328 hasInfix "cd" "abcd" 329 => true 330 hasInfix "foo" "abcd" 331 => false 332 */ 333 hasInfix = infix: content: 334 # Before 23.05, paths would be copied to the store before converting them 335 # to strings and comparing. This was surprising and confusing. 336 warnIf 337 (isPath infix) 338 '' 339 lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported. 340 There is almost certainly a bug in the calling code, since this function always returns `false` in such a case. 341 This function also copies the path to the Nix store, which may not be what you want. 342 This behavior is deprecated and will throw an error in the future.'' 343 (builtins.match ".*${escapeRegex infix}.*" "${content}" != null); 344 345 /* Convert a string to a list of characters (i.e. singleton strings). 346 This allows you to, e.g., map a function over each character. However, 347 note that this will likely be horribly inefficient; Nix is not a 348 general purpose programming language. Complex string manipulations 349 should, if appropriate, be done in a derivation. 350 Also note that Nix treats strings as a list of bytes and thus doesn't 351 handle unicode. 352 353 Type: stringToCharacters :: string -> [string] 354 355 Example: 356 stringToCharacters "" 357 => [ ] 358 stringToCharacters "abc" 359 => [ "a" "b" "c" ] 360 stringToCharacters "🦄" 361 => [ "" "" "" "" ] 362 */ 363 stringToCharacters = s: 364 genList (p: substring p 1 s) (stringLength s); 365 366 /* Manipulate a string character by character and replace them by 367 strings before concatenating the results. 368 369 Type: stringAsChars :: (string -> string) -> string -> string 370 371 Example: 372 stringAsChars (x: if x == "a" then "i" else x) "nax" 373 => "nix" 374 */ 375 stringAsChars = 376 # Function to map over each individual character 377 f: 378 # Input string 379 s: concatStrings ( 380 map f (stringToCharacters s) 381 ); 382 383 /* Convert char to ascii value, must be in printable range 384 385 Type: charToInt :: string -> int 386 387 Example: 388 charToInt "A" 389 => 65 390 charToInt "(" 391 => 40 392 393 */ 394 charToInt = c: builtins.getAttr c asciiTable; 395 396 /* Escape occurrence of the elements of `list` in `string` by 397 prefixing it with a backslash. 398 399 Type: escape :: [string] -> string -> string 400 401 Example: 402 escape ["(" ")"] "(foo)" 403 => "\\(foo\\)" 404 */ 405 escape = list: replaceStrings list (map (c: "\\${c}") list); 406 407 /* Escape occurrence of the element of `list` in `string` by 408 converting to its ASCII value and prefixing it with \\x. 409 Only works for printable ascii characters. 410 411 Type: escapeC = [string] -> string -> string 412 413 Example: 414 escapeC [" "] "foo bar" 415 => "foo\\x20bar" 416 417 */ 418 escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list); 419 420 /* Escape the string so it can be safely placed inside a URL 421 query. 422 423 Type: escapeURL :: string -> string 424 425 Example: 426 escapeURL "foo/bar baz" 427 => "foo%2Fbar%20baz" 428 */ 429 escapeURL = let 430 unreserved = [ "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "-" "_" "." "~" ]; 431 toEscape = builtins.removeAttrs asciiTable unreserved; 432 in 433 replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape); 434 435 /* Quote string to be used safely within the Bourne shell. 436 437 Type: escapeShellArg :: string -> string 438 439 Example: 440 escapeShellArg "esc'ape\nme" 441 => "'esc'\\''ape\nme'" 442 */ 443 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'"; 444 445 /* Quote all arguments to be safely passed to the Bourne shell. 446 447 Type: escapeShellArgs :: [string] -> string 448 449 Example: 450 escapeShellArgs ["one" "two three" "four'five"] 451 => "'one' 'two three' 'four'\\''five'" 452 */ 453 escapeShellArgs = concatMapStringsSep " " escapeShellArg; 454 455 /* Test whether the given name is a valid POSIX shell variable name. 456 457 Type: string -> bool 458 459 Example: 460 isValidPosixName "foo_bar000" 461 => true 462 isValidPosixName "0-bad.jpg" 463 => false 464 */ 465 isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null; 466 467 /* Translate a Nix value into a shell variable declaration, with proper escaping. 468 469 The value can be a string (mapped to a regular variable), a list of strings 470 (mapped to a Bash-style array) or an attribute set of strings (mapped to a 471 Bash-style associative array). Note that "string" includes string-coercible 472 values like paths or derivations. 473 474 Strings are translated into POSIX sh-compatible code; lists and attribute sets 475 assume a shell that understands Bash syntax (e.g. Bash or ZSH). 476 477 Type: string -> (string | listOf string | attrsOf string) -> string 478 479 Example: 480 '' 481 ${toShellVar "foo" "some string"} 482 [[ "$foo" == "some string" ]] 483 '' 484 */ 485 toShellVar = name: value: 486 lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" ( 487 if isAttrs value && ! isStringLike value then 488 "declare -A ${name}=(${ 489 concatStringsSep " " (lib.mapAttrsToList (n: v: 490 "[${escapeShellArg n}]=${escapeShellArg v}" 491 ) value) 492 })" 493 else if isList value then 494 "declare -a ${name}=(${escapeShellArgs value})" 495 else 496 "${name}=${escapeShellArg value}" 497 ); 498 499 /* Translate an attribute set into corresponding shell variable declarations 500 using `toShellVar`. 501 502 Type: attrsOf (string | listOf string | attrsOf string) -> string 503 504 Example: 505 let 506 foo = "value"; 507 bar = foo; 508 in '' 509 ${toShellVars { inherit foo bar; }} 510 [[ "$foo" == "$bar" ]] 511 '' 512 */ 513 toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars); 514 515 /* Turn a string into a Nix expression representing that string 516 517 Type: string -> string 518 519 Example: 520 escapeNixString "hello\${}\n" 521 => "\"hello\\\${}\\n\"" 522 */ 523 escapeNixString = s: escape ["$"] (toJSON s); 524 525 /* Turn a string into an exact regular expression 526 527 Type: string -> string 528 529 Example: 530 escapeRegex "[^a-z]*" 531 => "\\[\\^a-z]\\*" 532 */ 533 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|."); 534 535 /* Quotes a string if it can't be used as an identifier directly. 536 537 Type: string -> string 538 539 Example: 540 escapeNixIdentifier "hello" 541 => "hello" 542 escapeNixIdentifier "0abc" 543 => "\"0abc\"" 544 */ 545 escapeNixIdentifier = s: 546 # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91 547 if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null 548 then s else escapeNixString s; 549 550 /* Escapes a string such that it is safe to include verbatim in an XML 551 document. 552 553 Type: string -> string 554 555 Example: 556 escapeXML ''"test" 'test' < & >'' 557 => "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;" 558 */ 559 escapeXML = builtins.replaceStrings 560 ["\"" "'" "<" ">" "&"] 561 ["&quot;" "&apos;" "&lt;" "&gt;" "&amp;"]; 562 563 # warning added 12-12-2022 564 replaceChars = lib.warn "replaceChars is a deprecated alias of replaceStrings, replace usages of it with replaceStrings." builtins.replaceStrings; 565 566 # Case conversion utilities. 567 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz"; 568 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 569 570 /* Converts an ASCII string to lower-case. 571 572 Type: toLower :: string -> string 573 574 Example: 575 toLower "HOME" 576 => "home" 577 */ 578 toLower = replaceStrings upperChars lowerChars; 579 580 /* Converts an ASCII string to upper-case. 581 582 Type: toUpper :: string -> string 583 584 Example: 585 toUpper "home" 586 => "HOME" 587 */ 588 toUpper = replaceStrings lowerChars upperChars; 589 590 /* Appends string context from another string. This is an implementation 591 detail of Nix and should be used carefully. 592 593 Strings in Nix carry an invisible `context` which is a list of strings 594 representing store paths. If the string is later used in a derivation 595 attribute, the derivation will properly populate the inputDrvs and 596 inputSrcs. 597 598 Example: 599 pkgs = import <nixpkgs> { }; 600 addContextFrom pkgs.coreutils "bar" 601 => "bar" 602 */ 603 addContextFrom = a: b: substring 0 0 a + b; 604 605 /* Cut a string with a separator and produces a list of strings which 606 were separated by this separator. 607 608 Example: 609 splitString "." "foo.bar.baz" 610 => [ "foo" "bar" "baz" ] 611 splitString "/" "/usr/local/bin" 612 => [ "" "usr" "local" "bin" ] 613 */ 614 splitString = sep: s: 615 let 616 splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s)); 617 in 618 map (addContextFrom s) splits; 619 620 /* Return a string without the specified prefix, if the prefix matches. 621 622 Type: string -> string -> string 623 624 Example: 625 removePrefix "foo." "foo.bar.baz" 626 => "bar.baz" 627 removePrefix "xxx" "foo.bar.baz" 628 => "foo.bar.baz" 629 */ 630 removePrefix = 631 # Prefix to remove if it matches 632 prefix: 633 # Input string 634 str: 635 # Before 23.05, paths would be copied to the store before converting them 636 # to strings and comparing. This was surprising and confusing. 637 warnIf 638 (isPath prefix) 639 '' 640 lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported. 641 There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case. 642 This function also copies the path to the Nix store, which may not be what you want. 643 This behavior is deprecated and will throw an error in the future.'' 644 (let 645 preLen = stringLength prefix; 646 in 647 if substring 0 preLen str == prefix then 648 # -1 will take the string until the end 649 substring preLen (-1) str 650 else 651 str); 652 653 /* Return a string without the specified suffix, if the suffix matches. 654 655 Type: string -> string -> string 656 657 Example: 658 removeSuffix "front" "homefront" 659 => "home" 660 removeSuffix "xxx" "homefront" 661 => "homefront" 662 */ 663 removeSuffix = 664 # Suffix to remove if it matches 665 suffix: 666 # Input string 667 str: 668 # Before 23.05, paths would be copied to the store before converting them 669 # to strings and comparing. This was surprising and confusing. 670 warnIf 671 (isPath suffix) 672 '' 673 lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported. 674 There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case. 675 This function also copies the path to the Nix store, which may not be what you want. 676 This behavior is deprecated and will throw an error in the future.'' 677 (let 678 sufLen = stringLength suffix; 679 sLen = stringLength str; 680 in 681 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then 682 substring 0 (sLen - sufLen) str 683 else 684 str); 685 686 /* Return true if string v1 denotes a version older than v2. 687 688 Example: 689 versionOlder "1.1" "1.2" 690 => true 691 versionOlder "1.1" "1.1" 692 => false 693 */ 694 versionOlder = v1: v2: compareVersions v2 v1 == 1; 695 696 /* Return true if string v1 denotes a version equal to or newer than v2. 697 698 Example: 699 versionAtLeast "1.1" "1.0" 700 => true 701 versionAtLeast "1.1" "1.1" 702 => true 703 versionAtLeast "1.1" "1.2" 704 => false 705 */ 706 versionAtLeast = v1: v2: !versionOlder v1 v2; 707 708 /* This function takes an argument that's either a derivation or a 709 derivation's "name" attribute and extracts the name part from that 710 argument. 711 712 Example: 713 getName "youtube-dl-2016.01.01" 714 => "youtube-dl" 715 getName pkgs.youtube-dl 716 => "youtube-dl" 717 */ 718 getName = x: 719 let 720 parse = drv: (parseDrvName drv).name; 721 in if isString x 722 then parse x 723 else x.pname or (parse x.name); 724 725 /* This function takes an argument that's either a derivation or a 726 derivation's "name" attribute and extracts the version part from that 727 argument. 728 729 Example: 730 getVersion "youtube-dl-2016.01.01" 731 => "2016.01.01" 732 getVersion pkgs.youtube-dl 733 => "2016.01.01" 734 */ 735 getVersion = x: 736 let 737 parse = drv: (parseDrvName drv).version; 738 in if isString x 739 then parse x 740 else x.version or (parse x.name); 741 742 /* Extract name with version from URL. Ask for separator which is 743 supposed to start extension. 744 745 Example: 746 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-" 747 => "nix" 748 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_" 749 => "nix-1.7-x86" 750 */ 751 nameFromURL = url: sep: 752 let 753 components = splitString "/" url; 754 filename = lib.last components; 755 name = head (splitString sep filename); 756 in assert name != filename; name; 757 758 /* Create a "-D<feature>:<type>=<value>" string that can be passed to typical 759 CMake invocations. 760 761 Type: cmakeOptionType :: string -> string -> string -> string 762 763 @param feature The feature to be set 764 @param type The type of the feature to be set, as described in 765 https://cmake.org/cmake/help/latest/command/set.html 766 the possible values (case insensitive) are: 767 BOOL FILEPATH PATH STRING INTERNAL 768 @param value The desired value 769 770 Example: 771 cmakeOptionType "string" "ENGINE" "sdl2" 772 => "-DENGINE:STRING=sdl2" 773 */ 774 cmakeOptionType = type: feature: value: 775 assert (lib.elem (lib.toUpper type) 776 [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" ]); 777 assert (lib.isString feature); 778 assert (lib.isString value); 779 "-D${feature}:${lib.toUpper type}=${value}"; 780 781 /* Create a -D<condition>={TRUE,FALSE} string that can be passed to typical 782 CMake invocations. 783 784 Type: cmakeBool :: string -> bool -> string 785 786 @param condition The condition to be made true or false 787 @param flag The controlling flag of the condition 788 789 Example: 790 cmakeBool "ENABLE_STATIC_LIBS" false 791 => "-DENABLESTATIC_LIBS:BOOL=FALSE" 792 */ 793 cmakeBool = condition: flag: 794 assert (lib.isString condition); 795 assert (lib.isBool flag); 796 cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag)); 797 798 /* Create a -D<feature>:STRING=<value> string that can be passed to typical 799 CMake invocations. 800 This is the most typical usage, so it deserves a special case. 801 802 Type: cmakeFeature :: string -> string -> string 803 804 @param condition The condition to be made true or false 805 @param flag The controlling flag of the condition 806 807 Example: 808 cmakeFeature "MODULES" "badblock" 809 => "-DMODULES:STRING=badblock" 810 */ 811 cmakeFeature = feature: value: 812 assert (lib.isString feature); 813 assert (lib.isString value); 814 cmakeOptionType "string" feature value; 815 816 /* Create a -D<feature>=<value> string that can be passed to typical Meson 817 invocations. 818 819 Type: mesonOption :: string -> string -> string 820 821 @param feature The feature to be set 822 @param value The desired value 823 824 Example: 825 mesonOption "engine" "opengl" 826 => "-Dengine=opengl" 827 */ 828 mesonOption = feature: value: 829 assert (lib.isString feature); 830 assert (lib.isString value); 831 "-D${feature}=${value}"; 832 833 /* Create a -D<condition>={true,false} string that can be passed to typical 834 Meson invocations. 835 836 Type: mesonBool :: string -> bool -> string 837 838 @param condition The condition to be made true or false 839 @param flag The controlling flag of the condition 840 841 Example: 842 mesonBool "hardened" true 843 => "-Dhardened=true" 844 mesonBool "static" false 845 => "-Dstatic=false" 846 */ 847 mesonBool = condition: flag: 848 assert (lib.isString condition); 849 assert (lib.isBool flag); 850 mesonOption condition (lib.boolToString flag); 851 852 /* Create a -D<feature>={enabled,disabled} string that can be passed to 853 typical Meson invocations. 854 855 Type: mesonEnable :: string -> bool -> string 856 857 @param feature The feature to be enabled or disabled 858 @param flag The controlling flag 859 860 Example: 861 mesonEnable "docs" true 862 => "-Ddocs=enabled" 863 mesonEnable "savage" false 864 => "-Dsavage=disabled" 865 */ 866 mesonEnable = feature: flag: 867 assert (lib.isString feature); 868 assert (lib.isBool flag); 869 mesonOption feature (if flag then "enabled" else "disabled"); 870 871 /* Create an --{enable,disable}-<feature> string that can be passed to 872 standard GNU Autoconf scripts. 873 874 Example: 875 enableFeature true "shared" 876 => "--enable-shared" 877 enableFeature false "shared" 878 => "--disable-shared" 879 */ 880 enableFeature = flag: feature: 881 assert lib.isBool flag; 882 assert lib.isString feature; # e.g. passing openssl instead of "openssl" 883 "--${if flag then "enable" else "disable"}-${feature}"; 884 885 /* Create an --{enable-<feature>=<value>,disable-<feature>} string that can be passed to 886 standard GNU Autoconf scripts. 887 888 Example: 889 enableFeatureAs true "shared" "foo" 890 => "--enable-shared=foo" 891 enableFeatureAs false "shared" (throw "ignored") 892 => "--disable-shared" 893 */ 894 enableFeatureAs = flag: feature: value: 895 enableFeature flag feature + optionalString flag "=${value}"; 896 897 /* Create an --{with,without}-<feature> string that can be passed to 898 standard GNU Autoconf scripts. 899 900 Example: 901 withFeature true "shared" 902 => "--with-shared" 903 withFeature false "shared" 904 => "--without-shared" 905 */ 906 withFeature = flag: feature: 907 assert isString feature; # e.g. passing openssl instead of "openssl" 908 "--${if flag then "with" else "without"}-${feature}"; 909 910 /* Create an --{with-<feature>=<value>,without-<feature>} string that can be passed to 911 standard GNU Autoconf scripts. 912 913 Example: 914 withFeatureAs true "shared" "foo" 915 => "--with-shared=foo" 916 withFeatureAs false "shared" (throw "ignored") 917 => "--without-shared" 918 */ 919 withFeatureAs = flag: feature: value: 920 withFeature flag feature + optionalString flag "=${value}"; 921 922 /* Create a fixed width string with additional prefix to match 923 required width. 924 925 This function will fail if the input string is longer than the 926 requested length. 927 928 Type: fixedWidthString :: int -> string -> string -> string 929 930 Example: 931 fixedWidthString 5 "0" (toString 15) 932 => "00015" 933 */ 934 fixedWidthString = width: filler: str: 935 let 936 strw = lib.stringLength str; 937 reqWidth = width - (lib.stringLength filler); 938 in 939 assert lib.assertMsg (strw <= width) 940 "fixedWidthString: requested string length (${ 941 toString width}) must not be shorter than actual length (${ 942 toString strw})"; 943 if strw == width then str else filler + fixedWidthString reqWidth filler str; 944 945 /* Format a number adding leading zeroes up to fixed width. 946 947 Example: 948 fixedWidthNumber 5 15 949 => "00015" 950 */ 951 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n); 952 953 /* Convert a float to a string, but emit a warning when precision is lost 954 during the conversion 955 956 Example: 957 floatToString 0.000001 958 => "0.000001" 959 floatToString 0.0000001 960 => trace: warning: Imprecise conversion from float to string 0.000000 961 "0.000000" 962 */ 963 floatToString = float: let 964 result = toString float; 965 precise = float == fromJSON result; 966 in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}" 967 result; 968 969 /* Soft-deprecated function. While the original implementation is available as 970 isConvertibleWithToString, consider using isStringLike instead, if suitable. */ 971 isCoercibleToString = lib.warnIf (lib.isInOldestRelease 2305) 972 "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." 973 isConvertibleWithToString; 974 975 /* Check whether a list or other value can be passed to toString. 976 977 Many types of value are coercible to string this way, including int, float, 978 null, bool, list of similarly coercible values. 979 */ 980 isConvertibleWithToString = x: 981 isStringLike x || 982 elem (typeOf x) [ "null" "int" "float" "bool" ] || 983 (isList x && lib.all isConvertibleWithToString x); 984 985 /* Check whether a value can be coerced to a string. 986 The value must be a string, path, or attribute set. 987 988 String-like values can be used without explicit conversion in 989 string interpolations and in most functions that expect a string. 990 */ 991 isStringLike = x: 992 isString x || 993 isPath x || 994 x ? outPath || 995 x ? __toString; 996 997 /* Check whether a value is a store path. 998 999 Example: 1000 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python" 1001 => false 1002 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11" 1003 => true 1004 isStorePath pkgs.python 1005 => true 1006 isStorePath [] || isStorePath 42 || isStorePath {} || 1007 => false 1008 */ 1009 isStorePath = x: 1010 if isStringLike x then 1011 let str = toString x; in 1012 substring 0 1 str == "/" 1013 && dirOf str == storeDir 1014 else 1015 false; 1016 1017 /* Parse a string as an int. Does not support parsing of integers with preceding zero due to 1018 ambiguity between zero-padded and octal numbers. See toIntBase10. 1019 1020 Type: string -> int 1021 1022 Example: 1023 1024 toInt "1337" 1025 => 1337 1026 1027 toInt "-4" 1028 => -4 1029 1030 toInt " 123 " 1031 => 123 1032 1033 toInt "00024" 1034 => error: Ambiguity in interpretation of 00024 between octal and zero padded integer. 1035 1036 toInt "3.14" 1037 => error: floating point JSON numbers are not supported 1038 */ 1039 toInt = str: 1040 let 1041 # RegEx: Match any leading whitespace, possibly a '-', one or more digits, 1042 # and finally match any trailing whitespace. 1043 strippedInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*" str; 1044 1045 # RegEx: Match a leading '0' then one or more digits. 1046 isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == []; 1047 1048 # Attempt to parse input 1049 parsedInput = fromJSON (head strippedInput); 1050 1051 generalError = "toInt: Could not convert ${escapeNixString str} to int."; 1052 1053 octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}" 1054 + " between octal and zero padded integer."; 1055 1056 in 1057 # Error on presence of non digit characters. 1058 if strippedInput == null 1059 then throw generalError 1060 # Error on presence of leading zero/octal ambiguity. 1061 else if isLeadingZero 1062 then throw octalAmbigError 1063 # Error if parse function fails. 1064 else if !isInt parsedInput 1065 then throw generalError 1066 # Return result. 1067 else parsedInput; 1068 1069 1070 /* Parse a string as a base 10 int. This supports parsing of zero-padded integers. 1071 1072 Type: string -> int 1073 1074 Example: 1075 toIntBase10 "1337" 1076 => 1337 1077 1078 toIntBase10 "-4" 1079 => -4 1080 1081 toIntBase10 " 123 " 1082 => 123 1083 1084 toIntBase10 "00024" 1085 => 24 1086 1087 toIntBase10 "3.14" 1088 => error: floating point JSON numbers are not supported 1089 */ 1090 toIntBase10 = str: 1091 let 1092 # RegEx: Match any leading whitespace, then match any zero padding, 1093 # capture possibly a '-' followed by one or more digits, 1094 # and finally match any trailing whitespace. 1095 strippedInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*" str; 1096 1097 # RegEx: Match at least one '0'. 1098 isZero = match "0+" (head strippedInput) == []; 1099 1100 # Attempt to parse input 1101 parsedInput = fromJSON (head strippedInput); 1102 1103 generalError = "toIntBase10: Could not convert ${escapeNixString str} to int."; 1104 1105 in 1106 # Error on presence of non digit characters. 1107 if strippedInput == null 1108 then throw generalError 1109 # In the special case zero-padded zero (00000), return early. 1110 else if isZero 1111 then 0 1112 # Error if parse function fails. 1113 else if !isInt parsedInput 1114 then throw generalError 1115 # Return result. 1116 else parsedInput; 1117 1118 /* Read a list of paths from `file`, relative to the `rootPath`. 1119 Lines beginning with `#` are treated as comments and ignored. 1120 Whitespace is significant. 1121 1122 NOTE: This function is not performant and should be avoided. 1123 1124 Example: 1125 readPathsFromFile /prefix 1126 ./pkgs/development/libraries/qt-5/5.4/qtbase/series 1127 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch" 1128 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch" 1129 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch" 1130 "/prefix/nix-profiles-library-paths.patch" 1131 "/prefix/compose-search-path.patch" ] 1132 */ 1133 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead" 1134 (rootPath: file: 1135 let 1136 lines = lib.splitString "\n" (readFile file); 1137 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line)); 1138 relativePaths = removeComments lines; 1139 absolutePaths = map (path: rootPath + "/${path}") relativePaths; 1140 in 1141 absolutePaths); 1142 1143 /* Read the contents of a file removing the trailing \n 1144 1145 Type: fileContents :: path -> string 1146 1147 Example: 1148 $ echo "1.0" > ./version 1149 1150 fileContents ./version 1151 => "1.0" 1152 */ 1153 fileContents = file: removeSuffix "\n" (readFile file); 1154 1155 1156 /* Creates a valid derivation name from a potentially invalid one. 1157 1158 Type: sanitizeDerivationName :: String -> String 1159 1160 Example: 1161 sanitizeDerivationName "../hello.bar # foo" 1162 => "-hello.bar-foo" 1163 sanitizeDerivationName "" 1164 => "unknown" 1165 sanitizeDerivationName pkgs.hello 1166 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10" 1167 */ 1168 sanitizeDerivationName = 1169 let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*"; 1170 in 1171 string: 1172 # First detect the common case of already valid strings, to speed those up 1173 if stringLength string <= 207 && okRegex string != null 1174 then unsafeDiscardStringContext string 1175 else lib.pipe string [ 1176 # Get rid of string context. This is safe under the assumption that the 1177 # resulting string is only used as a derivation name 1178 unsafeDiscardStringContext 1179 # Strip all leading "." 1180 (x: elemAt (match "\\.*(.*)" x) 0) 1181 # Split out all invalid characters 1182 # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112 1183 # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125 1184 (split "[^[:alnum:]+._?=-]+") 1185 # Replace invalid character ranges with a "-" 1186 (concatMapStrings (s: if lib.isList s then "-" else s)) 1187 # Limit to 211 characters (minus 4 chars for ".drv") 1188 (x: substring (lib.max (stringLength x - 207) 0) (-1) x) 1189 # If the result is empty, replace it with "unknown" 1190 (x: if stringLength x == 0 then "unknown" else x) 1191 ]; 1192 1193 /* Computes the Levenshtein distance between two strings. 1194 Complexity O(n*m) where n and m are the lengths of the strings. 1195 Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742 1196 1197 Type: levenshtein :: string -> string -> int 1198 1199 Example: 1200 levenshtein "foo" "foo" 1201 => 0 1202 levenshtein "book" "hook" 1203 => 1 1204 levenshtein "hello" "Heyo" 1205 => 3 1206 */ 1207 levenshtein = a: b: let 1208 # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1) 1209 arr = lib.genList (i: 1210 lib.genList (j: 1211 dist i j 1212 ) (stringLength b + 1) 1213 ) (stringLength a + 1); 1214 d = x: y: lib.elemAt (lib.elemAt arr x) y; 1215 dist = i: j: 1216 let c = if substring (i - 1) 1 a == substring (j - 1) 1 b 1217 then 0 else 1; 1218 in 1219 if j == 0 then i 1220 else if i == 0 then j 1221 else lib.min 1222 ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1)) 1223 ( d (i - 1) (j - 1) + c ); 1224 in d (stringLength a) (stringLength b); 1225 1226 /* Returns the length of the prefix common to both strings. 1227 */ 1228 commonPrefixLength = a: b: 1229 let 1230 m = lib.min (stringLength a) (stringLength b); 1231 go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i; 1232 in go 0; 1233 1234 /* Returns the length of the suffix common to both strings. 1235 */ 1236 commonSuffixLength = a: b: 1237 let 1238 m = lib.min (stringLength a) (stringLength b); 1239 go = i: if i >= m then m else if substring (stringLength a - i - 1) 1 a == substring (stringLength b - i - 1) 1 b then go (i + 1) else i; 1240 in go 0; 1241 1242 /* Returns whether the levenshtein distance between two strings is at most some value 1243 Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise 1244 1245 Type: levenshteinAtMost :: int -> string -> string -> bool 1246 1247 Example: 1248 levenshteinAtMost 0 "foo" "foo" 1249 => true 1250 levenshteinAtMost 1 "foo" "boa" 1251 => false 1252 levenshteinAtMost 2 "foo" "boa" 1253 => true 1254 levenshteinAtMost 2 "This is a sentence" "this is a sentense." 1255 => false 1256 levenshteinAtMost 3 "This is a sentence" "this is a sentense." 1257 => true 1258 1259 */ 1260 levenshteinAtMost = let 1261 infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1; 1262 1263 # This function takes two strings stripped by their common pre and suffix, 1264 # and returns whether they differ by at most two by Levenshtein distance. 1265 # Because of this stripping, if they do indeed differ by at most two edits, 1266 # we know that those edits were (if at all) done at the start or the end, 1267 # while the middle has to have stayed the same. This fact is used in the 1268 # implementation. 1269 infixDifferAtMost2 = x: y: 1270 let 1271 xlen = stringLength x; 1272 ylen = stringLength y; 1273 # This function is only called with |x| >= |y| and |x| - |y| <= 2, so 1274 # diff is one of 0, 1 or 2 1275 diff = xlen - ylen; 1276 1277 # Infix of x and y, stripped by the left and right most character 1278 xinfix = substring 1 (xlen - 2) x; 1279 yinfix = substring 1 (ylen - 2) y; 1280 1281 # x and y but a character deleted at the left or right 1282 xdelr = substring 0 (xlen - 1) x; 1283 xdell = substring 1 (xlen - 1) x; 1284 ydelr = substring 0 (ylen - 1) y; 1285 ydell = substring 1 (ylen - 1) y; 1286 in 1287 # A length difference of 2 can only be gotten with 2 delete edits, 1288 # which have to have happened at the start and end of x 1289 # Example: "abcdef" -> "bcde" 1290 if diff == 2 then xinfix == y 1291 # A length difference of 1 can only be gotten with a deletion on the 1292 # right and a replacement on the left or vice versa. 1293 # Example: "abcdef" -> "bcdez" or "zbcde" 1294 else if diff == 1 then xinfix == ydelr || xinfix == ydell 1295 # No length difference can either happen through replacements on both 1296 # sides, or a deletion on the left and an insertion on the right or 1297 # vice versa 1298 # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde" 1299 else xinfix == yinfix || xdelr == ydell || xdell == ydelr; 1300 1301 in k: if k <= 0 then a: b: a == b else 1302 let f = a: b: 1303 let 1304 alen = stringLength a; 1305 blen = stringLength b; 1306 prelen = commonPrefixLength a b; 1307 suflen = commonSuffixLength a b; 1308 presuflen = prelen + suflen; 1309 ainfix = substring prelen (alen - presuflen) a; 1310 binfix = substring prelen (blen - presuflen) b; 1311 in 1312 # Make a be the bigger string 1313 if alen < blen then f b a 1314 # If a has over k more characters than b, even with k deletes on a, b can't be reached 1315 else if alen - blen > k then false 1316 else if k == 1 then infixDifferAtMost1 ainfix binfix 1317 else if k == 2 then infixDifferAtMost2 ainfix binfix 1318 else levenshtein ainfix binfix <= k; 1319 in f; 1320}