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