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}