at 18.03-beta 15 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) 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.NetSMTP ] 125 => "/nix/store/n0m1fk9c960d8wlrs62sncnadygqqc6y-perl-Net-SMTP-1.25/lib/perl5/site_perl" 126 */ 127 makePerlPath = makeSearchPathOutput "lib" "lib/perl5/site_perl"; 128 129 /* Depending on the boolean `cond', return either the given string 130 or the empty string. Useful to concatenate against a bigger string. 131 132 Example: 133 optionalString true "some-string" 134 => "some-string" 135 optionalString false "some-string" 136 => "" 137 */ 138 optionalString = cond: string: if cond then string else ""; 139 140 /* Determine whether a string has given prefix. 141 142 Example: 143 hasPrefix "foo" "foobar" 144 => true 145 hasPrefix "foo" "barfoo" 146 => false 147 */ 148 hasPrefix = pref: str: 149 substring 0 (stringLength pref) str == pref; 150 151 /* Determine whether a string has given suffix. 152 153 Example: 154 hasSuffix "foo" "foobar" 155 => false 156 hasSuffix "foo" "barfoo" 157 => true 158 */ 159 hasSuffix = suffix: content: 160 let 161 lenContent = stringLength content; 162 lenSuffix = stringLength suffix; 163 in lenContent >= lenSuffix && 164 substring (lenContent - lenSuffix) lenContent content == suffix; 165 166 /* Convert a string to a list of characters (i.e. singleton strings). 167 This allows you to, e.g., map a function over each character. However, 168 note that this will likely be horribly inefficient; Nix is not a 169 general purpose programming language. Complex string manipulations 170 should, if appropriate, be done in a derivation. 171 Also note that Nix treats strings as a list of bytes and thus doesn't 172 handle unicode. 173 174 Example: 175 stringToCharacters "" 176 => [ ] 177 stringToCharacters "abc" 178 => [ "a" "b" "c" ] 179 stringToCharacters "💩" 180 => [ "" "" "" "" ] 181 */ 182 stringToCharacters = s: 183 map (p: substring p 1 s) (lib.range 0 (stringLength s - 1)); 184 185 /* Manipulate a string character by character and replace them by 186 strings before concatenating the results. 187 188 Example: 189 stringAsChars (x: if x == "a" then "i" else x) "nax" 190 => "nix" 191 */ 192 stringAsChars = f: s: 193 concatStrings ( 194 map f (stringToCharacters s) 195 ); 196 197 /* Escape occurrence of the elements of list in string by 198 prefixing it with a backslash. 199 200 Example: 201 escape ["(" ")"] "(foo)" 202 => "\\(foo\\)" 203 */ 204 escape = list: replaceChars list (map (c: "\\${c}") list); 205 206 /* Quote string to be used safely within the Bourne shell. 207 208 Example: 209 escapeShellArg "esc'ape\nme" 210 => "'esc'\\''ape\nme'" 211 */ 212 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'"; 213 214 /* Quote all arguments to be safely passed to the Bourne shell. 215 216 Example: 217 escapeShellArgs ["one" "two three" "four'five"] 218 => "'one' 'two three' 'four'\\''five'" 219 */ 220 escapeShellArgs = concatMapStringsSep " " escapeShellArg; 221 222 /* Turn a string into a Nix expression representing that string 223 224 Example: 225 escapeNixString "hello\${}\n" 226 => "\"hello\\\${}\\n\"" 227 */ 228 escapeNixString = s: escape ["$"] (builtins.toJSON s); 229 230 /* Obsolete - use replaceStrings instead. */ 231 replaceChars = builtins.replaceStrings or ( 232 del: new: s: 233 let 234 substList = lib.zipLists del new; 235 subst = c: 236 let found = lib.findFirst (sub: sub.fst == c) null substList; in 237 if found == null then 238 c 239 else 240 found.snd; 241 in 242 stringAsChars subst s); 243 244 # Case conversion utilities. 245 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz"; 246 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 247 248 /* Converts an ASCII string to lower-case. 249 250 Example: 251 toLower "HOME" 252 => "home" 253 */ 254 toLower = replaceChars upperChars lowerChars; 255 256 /* Converts an ASCII string to upper-case. 257 258 Example: 259 toUpper "home" 260 => "HOME" 261 */ 262 toUpper = replaceChars lowerChars upperChars; 263 264 /* Appends string context from another string. This is an implementation 265 detail of Nix. 266 267 Strings in Nix carry an invisible `context' which is a list of strings 268 representing store paths. If the string is later used in a derivation 269 attribute, the derivation will properly populate the inputDrvs and 270 inputSrcs. 271 272 Example: 273 pkgs = import <nixpkgs> { }; 274 addContextFrom pkgs.coreutils "bar" 275 => "bar" 276 */ 277 addContextFrom = a: b: substring 0 0 a + b; 278 279 /* Cut a string with a separator and produces a list of strings which 280 were separated by this separator. 281 282 NOTE: this function is not performant and should never be used. 283 284 Example: 285 splitString "." "foo.bar.baz" 286 => [ "foo" "bar" "baz" ] 287 splitString "/" "/usr/local/bin" 288 => [ "" "usr" "local" "bin" ] 289 */ 290 splitString = _sep: _s: 291 let 292 sep = addContextFrom _s _sep; 293 s = addContextFrom _sep _s; 294 sepLen = stringLength sep; 295 sLen = stringLength s; 296 lastSearch = sLen - sepLen; 297 startWithSep = startAt: 298 substring startAt sepLen s == sep; 299 300 recurse = index: startAt: 301 let cutUntil = i: [(substring startAt (i - startAt) s)]; in 302 if index <= lastSearch then 303 if startWithSep index then 304 let restartAt = index + sepLen; in 305 cutUntil index ++ recurse restartAt restartAt 306 else 307 recurse (index + 1) startAt 308 else 309 cutUntil sLen; 310 in 311 recurse 0 0; 312 313 /* Return the suffix of the second argument if the first argument matches 314 its prefix. 315 316 Example: 317 removePrefix "foo." "foo.bar.baz" 318 => "bar.baz" 319 removePrefix "xxx" "foo.bar.baz" 320 => "foo.bar.baz" 321 */ 322 removePrefix = pre: s: 323 let 324 preLen = stringLength pre; 325 sLen = stringLength s; 326 in 327 if hasPrefix pre s then 328 substring preLen (sLen - preLen) s 329 else 330 s; 331 332 /* Return the prefix of the second argument if the first argument matches 333 its suffix. 334 335 Example: 336 removeSuffix "front" "homefront" 337 => "home" 338 removeSuffix "xxx" "homefront" 339 => "homefront" 340 */ 341 removeSuffix = suf: s: 342 let 343 sufLen = stringLength suf; 344 sLen = stringLength s; 345 in 346 if sufLen <= sLen && suf == substring (sLen - sufLen) sufLen s then 347 substring 0 (sLen - sufLen) s 348 else 349 s; 350 351 /* Return true iff string v1 denotes a version older than v2. 352 353 Example: 354 versionOlder "1.1" "1.2" 355 => true 356 versionOlder "1.1" "1.1" 357 => false 358 */ 359 versionOlder = v1: v2: builtins.compareVersions v2 v1 == 1; 360 361 /* Return true iff string v1 denotes a version equal to or newer than v2. 362 363 Example: 364 versionAtLeast "1.1" "1.0" 365 => true 366 versionAtLeast "1.1" "1.1" 367 => true 368 versionAtLeast "1.1" "1.2" 369 => false 370 */ 371 versionAtLeast = v1: v2: !versionOlder v1 v2; 372 373 /* This function takes an argument that's either a derivation or a 374 derivation's "name" attribute and extracts the version part from that 375 argument. 376 377 Example: 378 getVersion "youtube-dl-2016.01.01" 379 => "2016.01.01" 380 getVersion pkgs.youtube-dl 381 => "2016.01.01" 382 */ 383 getVersion = x: 384 let 385 parse = drv: (builtins.parseDrvName drv).version; 386 in if isString x 387 then parse x 388 else x.version or (parse x.name); 389 390 /* Extract name with version from URL. Ask for separator which is 391 supposed to start extension. 392 393 Example: 394 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-" 395 => "nix" 396 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_" 397 => "nix-1.7-x86" 398 */ 399 nameFromURL = url: sep: 400 let 401 components = splitString "/" url; 402 filename = lib.last components; 403 name = builtins.head (splitString sep filename); 404 in assert name != filename; name; 405 406 /* Create an --{enable,disable}-<feat> string that can be passed to 407 standard GNU Autoconf scripts. 408 409 Example: 410 enableFeature true "shared" 411 => "--enable-shared" 412 enableFeature false "shared" 413 => "--disable-shared" 414 */ 415 enableFeature = enable: feat: "--${if enable then "enable" else "disable"}-${feat}"; 416 417 /* Create a fixed width string with additional prefix to match 418 required width. 419 420 Example: 421 fixedWidthString 5 "0" (toString 15) 422 => "00015" 423 */ 424 fixedWidthString = width: filler: str: 425 let 426 strw = lib.stringLength str; 427 reqWidth = width - (lib.stringLength filler); 428 in 429 assert strw <= width; 430 if strw == width then str else filler + fixedWidthString reqWidth filler str; 431 432 /* Format a number adding leading zeroes up to fixed width. 433 434 Example: 435 fixedWidthNumber 5 15 436 => "00015" 437 */ 438 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n); 439 440 /* Check whether a value is a store path. 441 442 Example: 443 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python" 444 => false 445 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/" 446 => true 447 isStorePath pkgs.python 448 => true 449 isStorePath [] || isStorePath 42 || isStorePath {} || 450 => false 451 */ 452 isStorePath = x: 453 builtins.isString x 454 && builtins.substring 0 1 (toString x) == "/" 455 && dirOf (builtins.toPath x) == builtins.storeDir; 456 457 /* Convert string to int 458 Obviously, it is a bit hacky to use fromJSON that way. 459 460 Example: 461 toInt "1337" 462 => 1337 463 toInt "-4" 464 => -4 465 toInt "3.14" 466 => error: floating point JSON numbers are not supported 467 */ 468 toInt = str: 469 let may_be_int = builtins.fromJSON str; in 470 if builtins.isInt may_be_int 471 then may_be_int 472 else throw "Could not convert ${str} to int."; 473 474 /* Read a list of paths from `file', relative to the `rootPath'. Lines 475 beginning with `#' are treated as comments and ignored. Whitespace 476 is significant. 477 478 NOTE: this function is not performant and should be avoided 479 480 Example: 481 readPathsFromFile /prefix 482 ./pkgs/development/libraries/qt-5/5.4/qtbase/series 483 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch" 484 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch" 485 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch" 486 "/prefix/nix-profiles-library-paths.patch" 487 "/prefix/compose-search-path.patch" ] 488 */ 489 readPathsFromFile = rootPath: file: 490 let 491 root = toString rootPath; 492 lines = lib.splitString "\n" (builtins.readFile file); 493 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line)); 494 relativePaths = removeComments lines; 495 absolutePaths = builtins.map (path: builtins.toPath (root + "/" + path)) relativePaths; 496 in 497 absolutePaths; 498 499 /* Read the contents of a file removing the trailing \n 500 501 Example: 502 $ echo "1.0" > ./version 503 504 fileContents ./version 505 => "1.0" 506 */ 507 fileContents = file: removeSuffix "\n" (builtins.readFile file); 508}