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