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}