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}