git.iko.soy/typeable/nixpkgs
on 8a9d1aa help sign in
typeable/nixpkgs/lib/strings.nix
764 lines 22 kB history raw
1/* String manipulation functions. */
2{ lib }:
3let
5inherit (builtins) length;
7in
9rec {
11 inherit (builtins)
12 compareVersions
13 elem
14 elemAt
15 filter
16 fromJSON
17 head
18 isInt
19 isList
20 isString
21 match
22 parseDrvName
23 readFile
24 replaceStrings
25 split
26 storeDir
27 stringLength
28 substring
29 tail
30 toJSON
31 typeOf
32 unsafeDiscardStringContext
33 ;
35 /* Concatenate a list of strings.
37 Type: concatStrings :: [string] -> string
39 Example:
40 concatStrings ["foo" "bar"]
41 => "foobar"
42 */
43 concatStrings = builtins.concatStringsSep "";
45 /* Map a function over a list and concatenate the resulting strings.
47 Type: concatMapStrings :: (a -> string) -> [a] -> string
49 Example:
50 concatMapStrings (x: "a" + x) ["foo" "bar"]
51 => "afooabar"
52 */
53 concatMapStrings = f: list: concatStrings (map f list);
55 /* Like `concatMapStrings` except that the f functions also gets the
56 position as a parameter.
58 Type: concatImapStrings :: (int -> a -> string) -> [a] -> string
60 Example:
61 concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"]
62 => "1-foo2-bar"
63 */
64 concatImapStrings = f: list: concatStrings (lib.imap1 f list);
66 /* Place an element between each element of a list
68 Type: intersperse :: a -> [a] -> [a]
70 Example:
71 intersperse "/" ["usr" "local" "bin"]
72 => ["usr" "/" "local" "/" "bin"].
73 */
74 intersperse =
75 # Separator to add between elements
76 separator:
77 # Input list
78 list:
79 if list == [] || length list == 1
80 then list
81 else tail (lib.concatMap (x: [separator x]) list);
83 /* Concatenate a list of strings with a separator between each element
85 Type: concatStringsSep :: string -> [string] -> string
87 Example:
88 concatStringsSep "/" ["usr" "local" "bin"]
89 => "usr/local/bin"
90 */
91 concatStringsSep = builtins.concatStringsSep or (separator: list:
92 concatStrings (intersperse separator list));
94 /* Maps a function over a list of strings and then concatenates the
95 result with the specified separator interspersed between
96 elements.
98 Type: concatMapStringsSep :: string -> (string -> string) -> [string] -> string
100 Example:
101 concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"]
102 => "FOO-BAR-BAZ"
103 */
104 concatMapStringsSep =
105 # Separator to add between elements
106 sep:
107 # Function to map over the list
108 f:
109 # List of input strings
110 list: concatStringsSep sep (map f list);
112 /* Same as `concatMapStringsSep`, but the mapping function
113 additionally receives the position of its argument.
115 Type: concatIMapStringsSep :: string -> (int -> string -> string) -> [string] -> string
117 Example:
118 concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
119 => "6-3-2"
120 */
121 concatImapStringsSep =
122 # Separator to add between elements
123 sep:
124 # Function that receives elements and their positions
125 f:
126 # List of input strings
127 list: concatStringsSep sep (lib.imap1 f list);
129 /* Construct a Unix-style, colon-separated search path consisting of
130 the given `subDir` appended to each of the given paths.
132 Type: makeSearchPath :: string -> [string] -> string
134 Example:
135 makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
136 => "/root/bin:/usr/bin:/usr/local/bin"
137 makeSearchPath "bin" [""]
138 => "/bin"
139 */
140 makeSearchPath =
141 # Directory name to append
142 subDir:
143 # List of base paths
144 paths:
145 concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
147 /* Construct a Unix-style search path by appending the given
148 `subDir` to the specified `output` of each of the packages. If no
149 output by the given name is found, fallback to `.out` and then to
150 the default.
152 Type: string -> string -> [package] -> string
154 Example:
155 makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
156 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
157 */
158 makeSearchPathOutput =
159 # Package output to use
160 output:
161 # Directory name to append
162 subDir:
163 # List of packages
164 pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
166 /* Construct a library search path (such as RPATH) containing the
167 libraries for a set of packages
169 Example:
170 makeLibraryPath [ "/usr" "/usr/local" ]
171 => "/usr/lib:/usr/local/lib"
172 pkgs = import <nixpkgs> { }
173 makeLibraryPath [ pkgs.openssl pkgs.zlib ]
174 => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
175 */
176 makeLibraryPath = makeSearchPathOutput "lib" "lib";
178 /* Construct a binary search path (such as $PATH) containing the
179 binaries for a set of packages.
181 Example:
182 makeBinPath ["/root" "/usr" "/usr/local"]
183 => "/root/bin:/usr/bin:/usr/local/bin"
184 */
185 makeBinPath = makeSearchPathOutput "bin" "bin";
187 /* Depending on the boolean `cond', return either the given string
188 or the empty string. Useful to concatenate against a bigger string.
190 Type: optionalString :: bool -> string -> string
192 Example:
193 optionalString true "some-string"
194 => "some-string"
195 optionalString false "some-string"
196 => ""
197 */
198 optionalString =
199 # Condition
200 cond:
201 # String to return if condition is true
202 string: if cond then string else "";
204 /* Determine whether a string has given prefix.
206 Type: hasPrefix :: string -> string -> bool
208 Example:
209 hasPrefix "foo" "foobar"
210 => true
211 hasPrefix "foo" "barfoo"
212 => false
213 */
214 hasPrefix =
215 # Prefix to check for
216 pref:
217 # Input string
218 str: substring 0 (stringLength pref) str == pref;
220 /* Determine whether a string has given suffix.
222 Type: hasSuffix :: string -> string -> bool
224 Example:
225 hasSuffix "foo" "foobar"
226 => false
227 hasSuffix "foo" "barfoo"
228 => true
229 */
230 hasSuffix =
231 # Suffix to check for
232 suffix:
233 # Input string
234 content:
235 let
236 lenContent = stringLength content;
237 lenSuffix = stringLength suffix;
238 in lenContent >= lenSuffix &&
239 substring (lenContent - lenSuffix) lenContent content == suffix;
241 /* Determine whether a string contains the given infix
243 Type: hasInfix :: string -> string -> bool
245 Example:
246 hasInfix "bc" "abcd"
247 => true
248 hasInfix "ab" "abcd"
249 => true
250 hasInfix "cd" "abcd"
251 => true
252 hasInfix "foo" "abcd"
253 => false
254 */
255 hasInfix = infix: content:
256 let
257 drop = x: substring 1 (stringLength x) x;
258 in hasPrefix infix content
259 || content != "" && hasInfix infix (drop content);
261 /* Convert a string to a list of characters (i.e. singleton strings).
262 This allows you to, e.g., map a function over each character. However,
263 note that this will likely be horribly inefficient; Nix is not a
264 general purpose programming language. Complex string manipulations
265 should, if appropriate, be done in a derivation.
266 Also note that Nix treats strings as a list of bytes and thus doesn't
267 handle unicode.
269 Type: stringToCharacters :: string -> [string]
271 Example:
272 stringToCharacters ""
273 => [ ]
274 stringToCharacters "abc"
275 => [ "a" "b" "c" ]
276 stringToCharacters "💩"
277 => [ "�" "�" "�" "�" ]
278 */
279 stringToCharacters = s:
280 map (p: substring p 1 s) (lib.range 0 (stringLength s - 1));
282 /* Manipulate a string character by character and replace them by
283 strings before concatenating the results.
285 Type: stringAsChars :: (string -> string) -> string -> string
287 Example:
288 stringAsChars (x: if x == "a" then "i" else x) "nax"
289 => "nix"
290 */
291 stringAsChars =
292 # Function to map over each individual character
293 f:
294 # Input string
295 s: concatStrings (
296 map f (stringToCharacters s)
297 );
299 /* Escape occurrence of the elements of `list` in `string` by
300 prefixing it with a backslash.
302 Type: escape :: [string] -> string -> string
304 Example:
305 escape ["(" ")"] "(foo)"
306 => "\\(foo\\)"
307 */
308 escape = list: replaceChars list (map (c: "\\${c}") list);
310 /* Quote string to be used safely within the Bourne shell.
312 Type: escapeShellArg :: string -> string
314 Example:
315 escapeShellArg "esc'ape\nme"
316 => "'esc'\\''ape\nme'"
317 */
318 escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
320 /* Quote all arguments to be safely passed to the Bourne shell.
322 Type: escapeShellArgs :: [string] -> string
324 Example:
325 escapeShellArgs ["one" "two three" "four'five"]
326 => "'one' 'two three' 'four'\\''five'"
327 */
328 escapeShellArgs = concatMapStringsSep " " escapeShellArg;
330 /* Turn a string into a Nix expression representing that string
332 Type: string -> string
334 Example:
335 escapeNixString "hello\${}\n"
336 => "\"hello\\\${}\\n\""
337 */
338 escapeNixString = s: escape ["$"] (toJSON s);
340 /* Turn a string into an exact regular expression
342 Type: string -> string
344 Example:
345 escapeRegex "[^a-z]*"
346 => "\\[\\^a-z]\\*"
347 */
348 escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
350 /* Quotes a string if it can't be used as an identifier directly.
352 Type: string -> string
354 Example:
355 escapeNixIdentifier "hello"
356 => "hello"
357 escapeNixIdentifier "0abc"
358 => "\"0abc\""
359 */
360 escapeNixIdentifier = s:
361 # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
362 if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
363 then s else escapeNixString s;
365 # Obsolete - use replaceStrings instead.
366 replaceChars = builtins.replaceStrings or (
367 del: new: s:
368 let
369 substList = lib.zipLists del new;
370 subst = c:
371 let found = lib.findFirst (sub: sub.fst == c) null substList; in
372 if found == null then
373 c
374 else
375 found.snd;
376 in
377 stringAsChars subst s);
379 # Case conversion utilities.
380 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
381 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
383 /* Converts an ASCII string to lower-case.
385 Type: toLower :: string -> string
387 Example:
388 toLower "HOME"
389 => "home"
390 */
391 toLower = replaceChars upperChars lowerChars;
393 /* Converts an ASCII string to upper-case.
395 Type: toUpper :: string -> string
397 Example:
398 toUpper "home"
399 => "HOME"
400 */
401 toUpper = replaceChars lowerChars upperChars;
403 /* Appends string context from another string. This is an implementation
404 detail of Nix.
406 Strings in Nix carry an invisible `context` which is a list of strings
407 representing store paths. If the string is later used in a derivation
408 attribute, the derivation will properly populate the inputDrvs and
409 inputSrcs.
411 Example:
412 pkgs = import <nixpkgs> { };
413 addContextFrom pkgs.coreutils "bar"
414 => "bar"
415 */
416 addContextFrom = a: b: substring 0 0 a + b;
418 /* Cut a string with a separator and produces a list of strings which
419 were separated by this separator.
421 Example:
422 splitString "." "foo.bar.baz"
423 => [ "foo" "bar" "baz" ]
424 splitString "/" "/usr/local/bin"
425 => [ "" "usr" "local" "bin" ]
426 */
427 splitString = _sep: _s:
428 let
429 sep = builtins.unsafeDiscardStringContext _sep;
430 s = builtins.unsafeDiscardStringContext _s;
431 splits = builtins.filter builtins.isString (builtins.split (escapeRegex sep) s);
432 in
433 map (v: addContextFrom _sep (addContextFrom _s v)) splits;
435 /* Return a string without the specified prefix, if the prefix matches.
437 Type: string -> string -> string
439 Example:
440 removePrefix "foo." "foo.bar.baz"
441 => "bar.baz"
442 removePrefix "xxx" "foo.bar.baz"
443 => "foo.bar.baz"
444 */
445 removePrefix =
446 # Prefix to remove if it matches
447 prefix:
448 # Input string
449 str:
450 let
451 preLen = stringLength prefix;
452 sLen = stringLength str;
453 in
454 if hasPrefix prefix str then
455 substring preLen (sLen - preLen) str
456 else
457 str;
459 /* Return a string without the specified suffix, if the suffix matches.
461 Type: string -> string -> string
463 Example:
464 removeSuffix "front" "homefront"
465 => "home"
466 removeSuffix "xxx" "homefront"
467 => "homefront"
468 */
469 removeSuffix =
470 # Suffix to remove if it matches
471 suffix:
472 # Input string
473 str:
474 let
475 sufLen = stringLength suffix;
476 sLen = stringLength str;
477 in
478 if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
479 substring 0 (sLen - sufLen) str
480 else
481 str;
483 /* Return true if string v1 denotes a version older than v2.
485 Example:
486 versionOlder "1.1" "1.2"
487 => true
488 versionOlder "1.1" "1.1"
489 => false
490 */
491 versionOlder = v1: v2: compareVersions v2 v1 == 1;
493 /* Return true if string v1 denotes a version equal to or newer than v2.
495 Example:
496 versionAtLeast "1.1" "1.0"
497 => true
498 versionAtLeast "1.1" "1.1"
499 => true
500 versionAtLeast "1.1" "1.2"
501 => false
502 */
503 versionAtLeast = v1: v2: !versionOlder v1 v2;
505 /* This function takes an argument that's either a derivation or a
506 derivation's "name" attribute and extracts the name part from that
507 argument.
509 Example:
510 getName "youtube-dl-2016.01.01"
511 => "youtube-dl"
512 getName pkgs.youtube-dl
513 => "youtube-dl"
514 */
515 getName = x:
516 let
517 parse = drv: (parseDrvName drv).name;
518 in if isString x
519 then parse x
520 else x.pname or (parse x.name);
522 /* This function takes an argument that's either a derivation or a
523 derivation's "name" attribute and extracts the version part from that
524 argument.
526 Example:
527 getVersion "youtube-dl-2016.01.01"
528 => "2016.01.01"
529 getVersion pkgs.youtube-dl
530 => "2016.01.01"
531 */
532 getVersion = x:
533 let
534 parse = drv: (parseDrvName drv).version;
535 in if isString x
536 then parse x
537 else x.version or (parse x.name);
539 /* Extract name with version from URL. Ask for separator which is
540 supposed to start extension.
542 Example:
543 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
544 => "nix"
545 nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
546 => "nix-1.7-x86"
547 */
548 nameFromURL = url: sep:
549 let
550 components = splitString "/" url;
551 filename = lib.last components;
552 name = head (splitString sep filename);
553 in assert name != filename; name;
555 /* Create an --{enable,disable}-<feat> string that can be passed to
556 standard GNU Autoconf scripts.
558 Example:
559 enableFeature true "shared"
560 => "--enable-shared"
561 enableFeature false "shared"
562 => "--disable-shared"
563 */
564 enableFeature = enable: feat:
565 assert isString feat; # e.g. passing openssl instead of "openssl"
566 "--${if enable then "enable" else "disable"}-${feat}";
568 /* Create an --{enable-<feat>=<value>,disable-<feat>} string that can be passed to
569 standard GNU Autoconf scripts.
571 Example:
572 enableFeatureAs true "shared" "foo"
573 => "--enable-shared=foo"
574 enableFeatureAs false "shared" (throw "ignored")
575 => "--disable-shared"
576 */
577 enableFeatureAs = enable: feat: value: enableFeature enable feat + optionalString enable "=${value}";
579 /* Create an --{with,without}-<feat> string that can be passed to
580 standard GNU Autoconf scripts.
582 Example:
583 withFeature true "shared"
584 => "--with-shared"
585 withFeature false "shared"
586 => "--without-shared"
587 */
588 withFeature = with_: feat:
589 assert isString feat; # e.g. passing openssl instead of "openssl"
590 "--${if with_ then "with" else "without"}-${feat}";
592 /* Create an --{with-<feat>=<value>,without-<feat>} string that can be passed to
593 standard GNU Autoconf scripts.
595 Example:
596 withFeatureAs true "shared" "foo"
597 => "--with-shared=foo"
598 withFeatureAs false "shared" (throw "ignored")
599 => "--without-shared"
600 */
601 withFeatureAs = with_: feat: value: withFeature with_ feat + optionalString with_ "=${value}";
603 /* Create a fixed width string with additional prefix to match
604 required width.
606 This function will fail if the input string is longer than the
607 requested length.
609 Type: fixedWidthString :: int -> string -> string
611 Example:
612 fixedWidthString 5 "0" (toString 15)
613 => "00015"
614 */
615 fixedWidthString = width: filler: str:
616 let
617 strw = lib.stringLength str;
618 reqWidth = width - (lib.stringLength filler);
619 in
620 assert lib.assertMsg (strw <= width)
621 "fixedWidthString: requested string length (${
622 toString width}) must not be shorter than actual length (${
623 toString strw})";
624 if strw == width then str else filler + fixedWidthString reqWidth filler str;
626 /* Format a number adding leading zeroes up to fixed width.
628 Example:
629 fixedWidthNumber 5 15
630 => "00015"
631 */
632 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
634 /* Convert a float to a string, but emit a warning when precision is lost
635 during the conversion
637 Example:
638 floatToString 0.000001
639 => "0.000001"
640 floatToString 0.0000001
641 => trace: warning: Imprecise conversion from float to string 0.000000
642 "0.000000"
643 */
644 floatToString = float: let
645 result = toString float;
646 precise = float == fromJSON result;
647 in if precise then result
648 else lib.warn "Imprecise conversion from float to string ${result}" result;
650 /* Check whether a value can be coerced to a string */
651 isCoercibleToString = x:
652 elem (typeOf x) [ "path" "string" "null" "int" "float" "bool" ] ||
653 (isList x && lib.all isCoercibleToString x) ||
654 x ? outPath ||
655 x ? __toString;
657 /* Check whether a value is a store path.
659 Example:
660 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
661 => false
662 isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/"
663 => true
664 isStorePath pkgs.python
665 => true
666 isStorePath [] || isStorePath 42 || isStorePath {} || …
667 => false
668 */
669 isStorePath = x:
670 if isCoercibleToString x then
671 let str = toString x; in
672 substring 0 1 str == "/"
673 && dirOf str == storeDir
674 else
675 false;
677 /* Parse a string as an int.
679 Type: string -> int
681 Example:
682 toInt "1337"
683 => 1337
684 toInt "-4"
685 => -4
686 toInt "3.14"
687 => error: floating point JSON numbers are not supported
688 */
689 # Obviously, it is a bit hacky to use fromJSON this way.
690 toInt = str:
691 let may_be_int = fromJSON str; in
692 if isInt may_be_int
693 then may_be_int
694 else throw "Could not convert ${str} to int.";
696 /* Read a list of paths from `file`, relative to the `rootPath`.
697 Lines beginning with `#` are treated as comments and ignored.
698 Whitespace is significant.
700 NOTE: This function is not performant and should be avoided.
702 Example:
703 readPathsFromFile /prefix
704 ./pkgs/development/libraries/qt-5/5.4/qtbase/series
705 => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
706 "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
707 "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
708 "/prefix/nix-profiles-library-paths.patch"
709 "/prefix/compose-search-path.patch" ]
710 */
711 readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead"
712 (rootPath: file:
713 let
714 lines = lib.splitString "\n" (readFile file);
715 removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
716 relativePaths = removeComments lines;
717 absolutePaths = map (path: rootPath + "/${path}") relativePaths;
718 in
719 absolutePaths);
721 /* Read the contents of a file removing the trailing \n
723 Type: fileContents :: path -> string
725 Example:
726 $ echo "1.0" > ./version
728 fileContents ./version
729 => "1.0"
730 */
731 fileContents = file: removeSuffix "\n" (readFile file);
734 /* Creates a valid derivation name from a potentially invalid one.
736 Type: sanitizeDerivationName :: String -> String
738 Example:
739 sanitizeDerivationName "../hello.bar # foo"
740 => "-hello.bar-foo"
741 sanitizeDerivationName ""
742 => "unknown"
743 sanitizeDerivationName pkgs.hello
744 => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
745 */
746 sanitizeDerivationName = string: lib.pipe string [
747 # Get rid of string context. This is safe under the assumption that the
748 # resulting string is only used as a derivation name
749 unsafeDiscardStringContext
750 # Strip all leading "."
751 (x: elemAt (match "\\.*(.*)" x) 0)
752 # Split out all invalid characters
753 # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
754 # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
755 (split "[^[:alnum:]+._?=-]+")
756 # Replace invalid character ranges with a "-"
757 (concatMapStrings (s: if lib.isList s then "-" else s))
758 # Limit to 211 characters (minus 4 chars for ".drv")
759 (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
760 # If the result is empty, replace it with "unknown"
761 (x: if stringLength x == 0 then "unknown" else x)
762 ];