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