at 16.09-beta 15 kB view raw
1/* String manipulation functions. */ 2 3let lib = import ./default.nix; 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.imap 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.imap 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 /* Dependening on the boolean `cond', return either the given string 130 or the empty string. Useful to contatenate 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 /* Obsolete - use replaceStrings instead. */ 223 replaceChars = builtins.replaceStrings or ( 224 del: new: s: 225 let 226 substList = lib.zipLists del new; 227 subst = c: 228 let found = lib.findFirst (sub: sub.fst == c) null substList; in 229 if found == null then 230 c 231 else 232 found.snd; 233 in 234 stringAsChars subst s); 235 236 # Case conversion utilities. 237 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz"; 238 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 239 240 /* Converts an ASCII string to lower-case. 241 242 Example: 243 toLower "HOME" 244 => "home" 245 */ 246 toLower = replaceChars upperChars lowerChars; 247 248 /* Converts an ASCII string to upper-case. 249 250 Example: 251 toUpper "home" 252 => "HOME" 253 */ 254 toUpper = replaceChars lowerChars upperChars; 255 256 /* Appends string context from another string. This is an implementation 257 detail of Nix. 258 259 Strings in Nix carry an invisible `context' which is a list of strings 260 representing store paths. If the string is later used in a derivation 261 attribute, the derivation will properly populate the inputDrvs and 262 inputSrcs. 263 264 Example: 265 pkgs = import <nixpkgs> { }; 266 addContextFrom pkgs.coreutils "bar" 267 => "bar" 268 */ 269 addContextFrom = a: b: substring 0 0 a + b; 270 271 /* Cut a string with a separator and produces a list of strings which 272 were separated by this separator. 273 274 NOTE: this function is not performant and should never be used. 275 276 Example: 277 splitString "." "foo.bar.baz" 278 => [ "foo" "bar" "baz" ] 279 splitString "/" "/usr/local/bin" 280 => [ "" "usr" "local" "bin" ] 281 */ 282 splitString = _sep: _s: 283 let 284 sep = addContextFrom _s _sep; 285 s = addContextFrom _sep _s; 286 sepLen = stringLength sep; 287 sLen = stringLength s; 288 lastSearch = sLen - sepLen; 289 startWithSep = startAt: 290 substring startAt sepLen s == sep; 291 292 recurse = index: startAt: 293 let cutUntil = i: [(substring startAt (i - startAt) s)]; in 294 if index < lastSearch then 295 if startWithSep index then 296 let restartAt = index + sepLen; in 297 cutUntil index ++ recurse restartAt restartAt 298 else 299 recurse (index + 1) startAt 300 else 301 cutUntil sLen; 302 in 303 recurse 0 0; 304 305 /* Return the suffix of the second argument if the first argument matches 306 its prefix. 307 308 Example: 309 removePrefix "foo." "foo.bar.baz" 310 => "bar.baz" 311 removePrefix "xxx" "foo.bar.baz" 312 => "foo.bar.baz" 313 */ 314 removePrefix = pre: s: 315 let 316 preLen = stringLength pre; 317 sLen = stringLength s; 318 in 319 if hasPrefix pre s then 320 substring preLen (sLen - preLen) s 321 else 322 s; 323 324 /* Return the prefix of the second argument if the first argument matches 325 its suffix. 326 327 Example: 328 removeSuffix "front" "homefront" 329 => "home" 330 removeSuffix "xxx" "homefront" 331 => "homefront" 332 */ 333 removeSuffix = suf: s: 334 let 335 sufLen = stringLength suf; 336 sLen = stringLength s; 337 in 338 if sufLen <= sLen && suf == substring (sLen - sufLen) sufLen s then 339 substring 0 (sLen - sufLen) s 340 else 341 s; 342 343 /* Return true iff string v1 denotes a version older than v2. 344 345 Example: 346 versionOlder "1.1" "1.2" 347 => true 348 versionOlder "1.1" "1.1" 349 => false 350 */ 351 versionOlder = v1: v2: builtins.compareVersions v2 v1 == 1; 352 353 /* Return true iff string v1 denotes a version equal to or newer than v2. 354 355 Example: 356 versionAtLeast "1.1" "1.0" 357 => true 358 versionAtLeast "1.1" "1.1" 359 => true 360 versionAtLeast "1.1" "1.2" 361 => false 362 */ 363 versionAtLeast = v1: v2: !versionOlder v1 v2; 364 365 /* This function takes an argument that's either a derivation or a 366 derivation's "name" attribute and extracts the version part from that 367 argument. 368 369 Example: 370 getVersion "youtube-dl-2016.01.01" 371 => "2016.01.01" 372 getVersion pkgs.youtube-dl 373 => "2016.01.01" 374 */ 375 getVersion = x: 376 let 377 parse = drv: (builtins.parseDrvName drv).version; 378 in if isString x 379 then parse x 380 else x.version or (parse x.name); 381 382 /* Extract name with version from URL. Ask for separator which is 383 supposed to start extension. 384 385 Example: 386 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-" 387 => "nix" 388 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_" 389 => "nix-1.7-x86" 390 */ 391 nameFromURL = url: sep: 392 let 393 components = splitString "/" url; 394 filename = lib.last components; 395 name = builtins.head (splitString sep filename); 396 in assert name != filename; name; 397 398 /* Create an --{enable,disable}-<feat> string that can be passed to 399 standard GNU Autoconf scripts. 400 401 Example: 402 enableFeature true "shared" 403 => "--enable-shared" 404 enableFeature false "shared" 405 => "--disable-shared" 406 */ 407 enableFeature = enable: feat: "--${if enable then "enable" else "disable"}-${feat}"; 408 409 /* Create a fixed width string with additional prefix to match 410 required width. 411 412 Example: 413 fixedWidthString 5 "0" (toString 15) 414 => "00015" 415 */ 416 fixedWidthString = width: filler: str: 417 let 418 strw = lib.stringLength str; 419 reqWidth = width - (lib.stringLength filler); 420 in 421 assert strw <= width; 422 if strw == width then str else filler + fixedWidthString reqWidth filler str; 423 424 /* Format a number adding leading zeroes up to fixed width. 425 426 Example: 427 fixedWidthNumber 5 15 428 => "00015" 429 */ 430 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n); 431 432 /* Check whether a value is a store path. 433 434 Example: 435 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python" 436 => false 437 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/" 438 => true 439 isStorePath pkgs.python 440 => true 441 */ 442 isStorePath = x: builtins.substring 0 1 (toString x) == "/" && dirOf (builtins.toPath x) == builtins.storeDir; 443 444 /* Convert string to int 445 Obviously, it is a bit hacky to use fromJSON that way. 446 447 Example: 448 toInt "1337" 449 => 1337 450 toInt "-4" 451 => -4 452 toInt "3.14" 453 => error: floating point JSON numbers are not supported 454 */ 455 toInt = str: 456 let may_be_int = builtins.fromJSON str; in 457 if builtins.isInt may_be_int 458 then may_be_int 459 else throw "Could not convert ${str} to int."; 460 461 /* Read a list of paths from `file', relative to the `rootPath'. Lines 462 beginning with `#' are treated as comments and ignored. Whitespace 463 is significant. 464 465 NOTE: this function is not performant and should be avoided 466 467 Example: 468 readPathsFromFile /prefix 469 ./pkgs/development/libraries/qt-5/5.4/qtbase/series 470 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch" 471 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch" 472 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch" 473 "/prefix/nix-profiles-library-paths.patch" 474 "/prefix/compose-search-path.patch" ] 475 */ 476 readPathsFromFile = rootPath: file: 477 let 478 root = toString rootPath; 479 lines = 480 builtins.map (lib.removeSuffix "\n") 481 (lib.splitString "\n" (builtins.readFile file)); 482 removeComments = lib.filter (line: !(lib.hasPrefix "#" line)); 483 relativePaths = removeComments lines; 484 absolutePaths = builtins.map (path: builtins.toPath (root + "/" + path)) relativePaths; 485 in 486 absolutePaths; 487 488 /* Read the contents of a file removing the trailing \n 489 490 Example: 491 $ echo "1.0" > ./version 492 493 fileContents ./version 494 => "1.0" 495 */ 496 fileContents = file: removeSuffix "\n" (builtins.readFile file); 497}