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