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}