at 22.05-pre 22 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 isString 21 match 22 parseDrvName 23 readFile 24 replaceStrings 25 split 26 storeDir 27 stringLength 28 substring 29 tail 30 toJSON 31 typeOf 32 unsafeDiscardStringContext 33 ; 34 35 /* Concatenate a list of strings. 36 37 Type: concatStrings :: [string] -> string 38 39 Example: 40 concatStrings ["foo" "bar"] 41 => "foobar" 42 */ 43 concatStrings = builtins.concatStringsSep ""; 44 45 /* Map a function over a list and concatenate the resulting strings. 46 47 Type: concatMapStrings :: (a -> string) -> [a] -> string 48 49 Example: 50 concatMapStrings (x: "a" + x) ["foo" "bar"] 51 => "afooabar" 52 */ 53 concatMapStrings = f: list: concatStrings (map f list); 54 55 /* Like `concatMapStrings` except that the f functions also gets the 56 position as a parameter. 57 58 Type: concatImapStrings :: (int -> a -> string) -> [a] -> string 59 60 Example: 61 concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"] 62 => "1-foo2-bar" 63 */ 64 concatImapStrings = f: list: concatStrings (lib.imap1 f list); 65 66 /* Place an element between each element of a list 67 68 Type: intersperse :: a -> [a] -> [a] 69 70 Example: 71 intersperse "/" ["usr" "local" "bin"] 72 => ["usr" "/" "local" "/" "bin"]. 73 */ 74 intersperse = 75 # Separator to add between elements 76 separator: 77 # Input list 78 list: 79 if list == [] || length list == 1 80 then list 81 else tail (lib.concatMap (x: [separator x]) list); 82 83 /* Concatenate a list of strings with a separator between each element 84 85 Type: concatStringsSep :: string -> [string] -> string 86 87 Example: 88 concatStringsSep "/" ["usr" "local" "bin"] 89 => "usr/local/bin" 90 */ 91 concatStringsSep = builtins.concatStringsSep or (separator: list: 92 lib.foldl' (x: y: x + y) "" (intersperse separator list)); 93 94 /* Maps a function over a list of strings and then concatenates the 95 result with the specified separator interspersed between 96 elements. 97 98 Type: concatMapStringsSep :: string -> (a -> string) -> [a] -> string 99 100 Example: 101 concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"] 102 => "FOO-BAR-BAZ" 103 */ 104 concatMapStringsSep = 105 # Separator to add between elements 106 sep: 107 # Function to map over the list 108 f: 109 # List of input strings 110 list: concatStringsSep sep (map f list); 111 112 /* Same as `concatMapStringsSep`, but the mapping function 113 additionally receives the position of its argument. 114 115 Type: concatIMapStringsSep :: string -> (int -> a -> string) -> [a] -> string 116 117 Example: 118 concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ] 119 => "6-3-2" 120 */ 121 concatImapStringsSep = 122 # Separator to add between elements 123 sep: 124 # Function that receives elements and their positions 125 f: 126 # List of input strings 127 list: concatStringsSep sep (lib.imap1 f list); 128 129 /* Construct a Unix-style, colon-separated search path consisting of 130 the given `subDir` appended to each of the given paths. 131 132 Type: makeSearchPath :: string -> [string] -> string 133 134 Example: 135 makeSearchPath "bin" ["/root" "/usr" "/usr/local"] 136 => "/root/bin:/usr/bin:/usr/local/bin" 137 makeSearchPath "bin" [""] 138 => "/bin" 139 */ 140 makeSearchPath = 141 # Directory name to append 142 subDir: 143 # List of base paths 144 paths: 145 concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths)); 146 147 /* Construct a Unix-style search path by appending the given 148 `subDir` to the specified `output` of each of the packages. If no 149 output by the given name is found, fallback to `.out` and then to 150 the default. 151 152 Type: string -> string -> [package] -> string 153 154 Example: 155 makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ] 156 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin" 157 */ 158 makeSearchPathOutput = 159 # Package output to use 160 output: 161 # Directory name to append 162 subDir: 163 # List of packages 164 pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs); 165 166 /* Construct a library search path (such as RPATH) containing the 167 libraries for a set of packages 168 169 Example: 170 makeLibraryPath [ "/usr" "/usr/local" ] 171 => "/usr/lib:/usr/local/lib" 172 pkgs = import <nixpkgs> { } 173 makeLibraryPath [ pkgs.openssl pkgs.zlib ] 174 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib" 175 */ 176 makeLibraryPath = makeSearchPathOutput "lib" "lib"; 177 178 /* Construct a binary search path (such as $PATH) containing the 179 binaries for a set of packages. 180 181 Example: 182 makeBinPath ["/root" "/usr" "/usr/local"] 183 => "/root/bin:/usr/bin:/usr/local/bin" 184 */ 185 makeBinPath = makeSearchPathOutput "bin" "bin"; 186 187 /* Depending on the boolean `cond', return either the given string 188 or the empty string. Useful to concatenate against a bigger string. 189 190 Type: optionalString :: bool -> string -> string 191 192 Example: 193 optionalString true "some-string" 194 => "some-string" 195 optionalString false "some-string" 196 => "" 197 */ 198 optionalString = 199 # Condition 200 cond: 201 # String to return if condition is true 202 string: if cond then string else ""; 203 204 /* Determine whether a string has given prefix. 205 206 Type: hasPrefix :: string -> string -> bool 207 208 Example: 209 hasPrefix "foo" "foobar" 210 => true 211 hasPrefix "foo" "barfoo" 212 => false 213 */ 214 hasPrefix = 215 # Prefix to check for 216 pref: 217 # Input string 218 str: substring 0 (stringLength pref) str == pref; 219 220 /* Determine whether a string has given suffix. 221 222 Type: hasSuffix :: string -> string -> bool 223 224 Example: 225 hasSuffix "foo" "foobar" 226 => false 227 hasSuffix "foo" "barfoo" 228 => true 229 */ 230 hasSuffix = 231 # Suffix to check for 232 suffix: 233 # Input string 234 content: 235 let 236 lenContent = stringLength content; 237 lenSuffix = stringLength suffix; 238 in lenContent >= lenSuffix && 239 substring (lenContent - lenSuffix) lenContent content == suffix; 240 241 /* Determine whether a string contains the given infix 242 243 Type: hasInfix :: string -> string -> bool 244 245 Example: 246 hasInfix "bc" "abcd" 247 => true 248 hasInfix "ab" "abcd" 249 => true 250 hasInfix "cd" "abcd" 251 => true 252 hasInfix "foo" "abcd" 253 => false 254 */ 255 hasInfix = infix: content: 256 let 257 drop = x: substring 1 (stringLength x) x; 258 in hasPrefix infix content 259 || content != "" && hasInfix infix (drop content); 260 261 /* Convert a string to a list of characters (i.e. singleton strings). 262 This allows you to, e.g., map a function over each character. However, 263 note that this will likely be horribly inefficient; Nix is not a 264 general purpose programming language. Complex string manipulations 265 should, if appropriate, be done in a derivation. 266 Also note that Nix treats strings as a list of bytes and thus doesn't 267 handle unicode. 268 269 Type: stringToCharacters :: string -> [string] 270 271 Example: 272 stringToCharacters "" 273 => [ ] 274 stringToCharacters "abc" 275 => [ "a" "b" "c" ] 276 stringToCharacters "💩" 277 => [ "" "" "" "" ] 278 */ 279 stringToCharacters = s: 280 map (p: substring p 1 s) (lib.range 0 (stringLength s - 1)); 281 282 /* Manipulate a string character by character and replace them by 283 strings before concatenating the results. 284 285 Type: stringAsChars :: (string -> string) -> string -> string 286 287 Example: 288 stringAsChars (x: if x == "a" then "i" else x) "nax" 289 => "nix" 290 */ 291 stringAsChars = 292 # Function to map over each individual character 293 f: 294 # Input string 295 s: concatStrings ( 296 map f (stringToCharacters s) 297 ); 298 299 /* Escape occurrence of the elements of `list` in `string` by 300 prefixing it with a backslash. 301 302 Type: escape :: [string] -> string -> string 303 304 Example: 305 escape ["(" ")"] "(foo)" 306 => "\\(foo\\)" 307 */ 308 escape = list: replaceChars list (map (c: "\\${c}") list); 309 310 /* Quote string to be used safely within the Bourne shell. 311 312 Type: escapeShellArg :: string -> string 313 314 Example: 315 escapeShellArg "esc'ape\nme" 316 => "'esc'\\''ape\nme'" 317 */ 318 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'"; 319 320 /* Quote all arguments to be safely passed to the Bourne shell. 321 322 Type: escapeShellArgs :: [string] -> string 323 324 Example: 325 escapeShellArgs ["one" "two three" "four'five"] 326 => "'one' 'two three' 'four'\\''five'" 327 */ 328 escapeShellArgs = concatMapStringsSep " " escapeShellArg; 329 330 /* Turn a string into a Nix expression representing that string 331 332 Type: string -> string 333 334 Example: 335 escapeNixString "hello\${}\n" 336 => "\"hello\\\${}\\n\"" 337 */ 338 escapeNixString = s: escape ["$"] (toJSON s); 339 340 /* Turn a string into an exact regular expression 341 342 Type: string -> string 343 344 Example: 345 escapeRegex "[^a-z]*" 346 => "\\[\\^a-z]\\*" 347 */ 348 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|."); 349 350 /* Quotes a string if it can't be used as an identifier directly. 351 352 Type: string -> string 353 354 Example: 355 escapeNixIdentifier "hello" 356 => "hello" 357 escapeNixIdentifier "0abc" 358 => "\"0abc\"" 359 */ 360 escapeNixIdentifier = s: 361 # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91 362 if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null 363 then s else escapeNixString s; 364 365 /* Escapes a string such that it is safe to include verbatim in an XML 366 document. 367 368 Type: string -> string 369 370 Example: 371 escapeXML ''"test" 'test' < & >'' 372 => "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;" 373 */ 374 escapeXML = builtins.replaceStrings 375 ["\"" "'" "<" ">" "&"] 376 ["&quot;" "&apos;" "&lt;" "&gt;" "&amp;"]; 377 378 # Obsolete - use replaceStrings instead. 379 replaceChars = builtins.replaceStrings or ( 380 del: new: s: 381 let 382 substList = lib.zipLists del new; 383 subst = c: 384 let found = lib.findFirst (sub: sub.fst == c) null substList; in 385 if found == null then 386 c 387 else 388 found.snd; 389 in 390 stringAsChars subst s); 391 392 # Case conversion utilities. 393 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz"; 394 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 395 396 /* Converts an ASCII string to lower-case. 397 398 Type: toLower :: string -> string 399 400 Example: 401 toLower "HOME" 402 => "home" 403 */ 404 toLower = replaceChars upperChars lowerChars; 405 406 /* Converts an ASCII string to upper-case. 407 408 Type: toUpper :: string -> string 409 410 Example: 411 toUpper "home" 412 => "HOME" 413 */ 414 toUpper = replaceChars lowerChars upperChars; 415 416 /* Appends string context from another string. This is an implementation 417 detail of Nix. 418 419 Strings in Nix carry an invisible `context` which is a list of strings 420 representing store paths. If the string is later used in a derivation 421 attribute, the derivation will properly populate the inputDrvs and 422 inputSrcs. 423 424 Example: 425 pkgs = import <nixpkgs> { }; 426 addContextFrom pkgs.coreutils "bar" 427 => "bar" 428 */ 429 addContextFrom = a: b: substring 0 0 a + b; 430 431 /* Cut a string with a separator and produces a list of strings which 432 were separated by this separator. 433 434 Example: 435 splitString "." "foo.bar.baz" 436 => [ "foo" "bar" "baz" ] 437 splitString "/" "/usr/local/bin" 438 => [ "" "usr" "local" "bin" ] 439 */ 440 splitString = _sep: _s: 441 let 442 sep = builtins.unsafeDiscardStringContext _sep; 443 s = builtins.unsafeDiscardStringContext _s; 444 splits = builtins.filter builtins.isString (builtins.split (escapeRegex sep) s); 445 in 446 map (v: addContextFrom _sep (addContextFrom _s v)) splits; 447 448 /* Return a string without the specified prefix, if the prefix matches. 449 450 Type: string -> string -> string 451 452 Example: 453 removePrefix "foo." "foo.bar.baz" 454 => "bar.baz" 455 removePrefix "xxx" "foo.bar.baz" 456 => "foo.bar.baz" 457 */ 458 removePrefix = 459 # Prefix to remove if it matches 460 prefix: 461 # Input string 462 str: 463 let 464 preLen = stringLength prefix; 465 sLen = stringLength str; 466 in 467 if hasPrefix prefix str then 468 substring preLen (sLen - preLen) str 469 else 470 str; 471 472 /* Return a string without the specified suffix, if the suffix matches. 473 474 Type: string -> string -> string 475 476 Example: 477 removeSuffix "front" "homefront" 478 => "home" 479 removeSuffix "xxx" "homefront" 480 => "homefront" 481 */ 482 removeSuffix = 483 # Suffix to remove if it matches 484 suffix: 485 # Input string 486 str: 487 let 488 sufLen = stringLength suffix; 489 sLen = stringLength str; 490 in 491 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then 492 substring 0 (sLen - sufLen) str 493 else 494 str; 495 496 /* Return true if string v1 denotes a version older than v2. 497 498 Example: 499 versionOlder "1.1" "1.2" 500 => true 501 versionOlder "1.1" "1.1" 502 => false 503 */ 504 versionOlder = v1: v2: compareVersions v2 v1 == 1; 505 506 /* Return true if string v1 denotes a version equal to or newer than v2. 507 508 Example: 509 versionAtLeast "1.1" "1.0" 510 => true 511 versionAtLeast "1.1" "1.1" 512 => true 513 versionAtLeast "1.1" "1.2" 514 => false 515 */ 516 versionAtLeast = v1: v2: !versionOlder v1 v2; 517 518 /* This function takes an argument that's either a derivation or a 519 derivation's "name" attribute and extracts the name part from that 520 argument. 521 522 Example: 523 getName "youtube-dl-2016.01.01" 524 => "youtube-dl" 525 getName pkgs.youtube-dl 526 => "youtube-dl" 527 */ 528 getName = x: 529 let 530 parse = drv: (parseDrvName drv).name; 531 in if isString x 532 then parse x 533 else x.pname or (parse x.name); 534 535 /* This function takes an argument that's either a derivation or a 536 derivation's "name" attribute and extracts the version part from that 537 argument. 538 539 Example: 540 getVersion "youtube-dl-2016.01.01" 541 => "2016.01.01" 542 getVersion pkgs.youtube-dl 543 => "2016.01.01" 544 */ 545 getVersion = x: 546 let 547 parse = drv: (parseDrvName drv).version; 548 in if isString x 549 then parse x 550 else x.version or (parse x.name); 551 552 /* Extract name with version from URL. Ask for separator which is 553 supposed to start extension. 554 555 Example: 556 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-" 557 => "nix" 558 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_" 559 => "nix-1.7-x86" 560 */ 561 nameFromURL = url: sep: 562 let 563 components = splitString "/" url; 564 filename = lib.last components; 565 name = head (splitString sep filename); 566 in assert name != filename; name; 567 568 /* Create an --{enable,disable}-<feat> string that can be passed to 569 standard GNU Autoconf scripts. 570 571 Example: 572 enableFeature true "shared" 573 => "--enable-shared" 574 enableFeature false "shared" 575 => "--disable-shared" 576 */ 577 enableFeature = enable: feat: 578 assert isString feat; # e.g. passing openssl instead of "openssl" 579 "--${if enable then "enable" else "disable"}-${feat}"; 580 581 /* Create an --{enable-<feat>=<value>,disable-<feat>} string that can be passed to 582 standard GNU Autoconf scripts. 583 584 Example: 585 enableFeatureAs true "shared" "foo" 586 => "--enable-shared=foo" 587 enableFeatureAs false "shared" (throw "ignored") 588 => "--disable-shared" 589 */ 590 enableFeatureAs = enable: feat: value: enableFeature enable feat + optionalString enable "=${value}"; 591 592 /* Create an --{with,without}-<feat> string that can be passed to 593 standard GNU Autoconf scripts. 594 595 Example: 596 withFeature true "shared" 597 => "--with-shared" 598 withFeature false "shared" 599 => "--without-shared" 600 */ 601 withFeature = with_: feat: 602 assert isString feat; # e.g. passing openssl instead of "openssl" 603 "--${if with_ then "with" else "without"}-${feat}"; 604 605 /* Create an --{with-<feat>=<value>,without-<feat>} string that can be passed to 606 standard GNU Autoconf scripts. 607 608 Example: 609 withFeatureAs true "shared" "foo" 610 => "--with-shared=foo" 611 withFeatureAs false "shared" (throw "ignored") 612 => "--without-shared" 613 */ 614 withFeatureAs = with_: feat: value: withFeature with_ feat + optionalString with_ "=${value}"; 615 616 /* Create a fixed width string with additional prefix to match 617 required width. 618 619 This function will fail if the input string is longer than the 620 requested length. 621 622 Type: fixedWidthString :: int -> string -> string -> string 623 624 Example: 625 fixedWidthString 5 "0" (toString 15) 626 => "00015" 627 */ 628 fixedWidthString = width: filler: str: 629 let 630 strw = lib.stringLength str; 631 reqWidth = width - (lib.stringLength filler); 632 in 633 assert lib.assertMsg (strw <= width) 634 "fixedWidthString: requested string length (${ 635 toString width}) must not be shorter than actual length (${ 636 toString strw})"; 637 if strw == width then str else filler + fixedWidthString reqWidth filler str; 638 639 /* Format a number adding leading zeroes up to fixed width. 640 641 Example: 642 fixedWidthNumber 5 15 643 => "00015" 644 */ 645 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n); 646 647 /* Convert a float to a string, but emit a warning when precision is lost 648 during the conversion 649 650 Example: 651 floatToString 0.000001 652 => "0.000001" 653 floatToString 0.0000001 654 => trace: warning: Imprecise conversion from float to string 0.000000 655 "0.000000" 656 */ 657 floatToString = float: let 658 result = toString float; 659 precise = float == fromJSON result; 660 in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}" 661 result; 662 663 /* Check whether a value can be coerced to a string */ 664 isCoercibleToString = x: 665 elem (typeOf x) [ "path" "string" "null" "int" "float" "bool" ] || 666 (isList x && lib.all isCoercibleToString x) || 667 x ? outPath || 668 x ? __toString; 669 670 /* Check whether a value is a store path. 671 672 Example: 673 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python" 674 => false 675 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11" 676 => true 677 isStorePath pkgs.python 678 => true 679 isStorePath [] || isStorePath 42 || isStorePath {} || 680 => false 681 */ 682 isStorePath = x: 683 if !(isList x) && isCoercibleToString x then 684 let str = toString x; in 685 substring 0 1 str == "/" 686 && dirOf str == storeDir 687 else 688 false; 689 690 /* Parse a string as an int. 691 692 Type: string -> int 693 694 Example: 695 toInt "1337" 696 => 1337 697 toInt "-4" 698 => -4 699 toInt "3.14" 700 => error: floating point JSON numbers are not supported 701 */ 702 # Obviously, it is a bit hacky to use fromJSON this way. 703 toInt = str: 704 let may_be_int = fromJSON str; in 705 if isInt may_be_int 706 then may_be_int 707 else throw "Could not convert ${str} to int."; 708 709 /* Read a list of paths from `file`, relative to the `rootPath`. 710 Lines beginning with `#` are treated as comments and ignored. 711 Whitespace is significant. 712 713 NOTE: This function is not performant and should be avoided. 714 715 Example: 716 readPathsFromFile /prefix 717 ./pkgs/development/libraries/qt-5/5.4/qtbase/series 718 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch" 719 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch" 720 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch" 721 "/prefix/nix-profiles-library-paths.patch" 722 "/prefix/compose-search-path.patch" ] 723 */ 724 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead" 725 (rootPath: file: 726 let 727 lines = lib.splitString "\n" (readFile file); 728 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line)); 729 relativePaths = removeComments lines; 730 absolutePaths = map (path: rootPath + "/${path}") relativePaths; 731 in 732 absolutePaths); 733 734 /* Read the contents of a file removing the trailing \n 735 736 Type: fileContents :: path -> string 737 738 Example: 739 $ echo "1.0" > ./version 740 741 fileContents ./version 742 => "1.0" 743 */ 744 fileContents = file: removeSuffix "\n" (readFile file); 745 746 747 /* Creates a valid derivation name from a potentially invalid one. 748 749 Type: sanitizeDerivationName :: String -> String 750 751 Example: 752 sanitizeDerivationName "../hello.bar # foo" 753 => "-hello.bar-foo" 754 sanitizeDerivationName "" 755 => "unknown" 756 sanitizeDerivationName pkgs.hello 757 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10" 758 */ 759 sanitizeDerivationName = string: lib.pipe string [ 760 # Get rid of string context. This is safe under the assumption that the 761 # resulting string is only used as a derivation name 762 unsafeDiscardStringContext 763 # Strip all leading "." 764 (x: elemAt (match "\\.*(.*)" x) 0) 765 # Split out all invalid characters 766 # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112 767 # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125 768 (split "[^[:alnum:]+._?=-]+") 769 # Replace invalid character ranges with a "-" 770 (concatMapStrings (s: if lib.isList s then "-" else s)) 771 # Limit to 211 characters (minus 4 chars for ".drv") 772 (x: substring (lib.max (stringLength x - 207) 0) (-1) x) 773 # If the result is empty, replace it with "unknown" 774 (x: if stringLength x == 0 then "unknown" else x) 775 ]; 776 777}