fblog: 4.4.0 -> 4.5.0
[NixPkgs.git] / lib / strings.nix
blob628669d86bbd482a4ec9aa3080657476b3c83aa9
1 /* String manipulation functions. */
2 { lib }:
3 let
5   inherit (builtins) length;
7   inherit (lib.trivial) warnIf;
9 asciiTable = import ./ascii-table.nix;
13 rec {
15   inherit (builtins)
16     compareVersions
17     elem
18     elemAt
19     filter
20     fromJSON
21     genList
22     head
23     isInt
24     isList
25     isAttrs
26     isPath
27     isString
28     match
29     parseDrvName
30     readFile
31     replaceStrings
32     split
33     storeDir
34     stringLength
35     substring
36     tail
37     toJSON
38     typeOf
39     unsafeDiscardStringContext
40     ;
42   /* Concatenate a list of strings.
44     Type: concatStrings :: [string] -> string
46      Example:
47        concatStrings ["foo" "bar"]
48        => "foobar"
49   */
50   concatStrings = builtins.concatStringsSep "";
52   /* Map a function over a list and concatenate the resulting strings.
54     Type: concatMapStrings :: (a -> string) -> [a] -> string
56      Example:
57        concatMapStrings (x: "a" + x) ["foo" "bar"]
58        => "afooabar"
59   */
60   concatMapStrings = f: list: concatStrings (map f list);
62   /* Like `concatMapStrings` except that the f functions also gets the
63      position as a parameter.
65      Type: concatImapStrings :: (int -> a -> string) -> [a] -> string
67      Example:
68        concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"]
69        => "1-foo2-bar"
70   */
71   concatImapStrings = f: list: concatStrings (lib.imap1 f list);
73   /* Place an element between each element of a list
75      Type: intersperse :: a -> [a] -> [a]
77      Example:
78        intersperse "/" ["usr" "local" "bin"]
79        => ["usr" "/" "local" "/" "bin"].
80   */
81   intersperse =
82     # Separator to add between elements
83     separator:
84     # Input list
85     list:
86     if list == [] || length list == 1
87     then list
88     else tail (lib.concatMap (x: [separator x]) list);
90   /* Concatenate a list of strings with a separator between each element
92      Type: concatStringsSep :: string -> [string] -> string
94      Example:
95         concatStringsSep "/" ["usr" "local" "bin"]
96         => "usr/local/bin"
97   */
98   concatStringsSep = builtins.concatStringsSep or (separator: list:
99     lib.foldl' (x: y: x + y) "" (intersperse separator list));
101   /* Maps a function over a list of strings and then concatenates the
102      result with the specified separator interspersed between
103      elements.
105      Type: concatMapStringsSep :: string -> (a -> string) -> [a] -> string
107      Example:
108         concatMapStringsSep "-" (x: toUpper x)  ["foo" "bar" "baz"]
109         => "FOO-BAR-BAZ"
110   */
111   concatMapStringsSep =
112     # Separator to add between elements
113     sep:
114     # Function to map over the list
115     f:
116     # List of input strings
117     list: concatStringsSep sep (map f list);
119   /* Same as `concatMapStringsSep`, but the mapping function
120      additionally receives the position of its argument.
122      Type: concatIMapStringsSep :: string -> (int -> a -> string) -> [a] -> string
124      Example:
125        concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
126        => "6-3-2"
127   */
128   concatImapStringsSep =
129     # Separator to add between elements
130     sep:
131     # Function that receives elements and their positions
132     f:
133     # List of input strings
134     list: concatStringsSep sep (lib.imap1 f list);
136   /* Concatenate a list of strings, adding a newline at the end of each one.
137      Defined as `concatMapStrings (s: s + "\n")`.
139      Type: concatLines :: [string] -> string
141      Example:
142        concatLines [ "foo" "bar" ]
143        => "foo\nbar\n"
144   */
145   concatLines = concatMapStrings (s: s + "\n");
147   /* Construct a Unix-style, colon-separated search path consisting of
148      the given `subDir` appended to each of the given paths.
150      Type: makeSearchPath :: string -> [string] -> string
152      Example:
153        makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
154        => "/root/bin:/usr/bin:/usr/local/bin"
155        makeSearchPath "bin" [""]
156        => "/bin"
157   */
158   makeSearchPath =
159     # Directory name to append
160     subDir:
161     # List of base paths
162     paths:
163     concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
165   /* Construct a Unix-style search path by appending the given
166      `subDir` to the specified `output` of each of the packages. If no
167      output by the given name is found, fallback to `.out` and then to
168      the default.
170      Type: string -> string -> [package] -> string
172      Example:
173        makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
174        => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
175   */
176   makeSearchPathOutput =
177     # Package output to use
178     output:
179     # Directory name to append
180     subDir:
181     # List of packages
182     pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
184   /* Construct a library search path (such as RPATH) containing the
185      libraries for a set of packages
187      Example:
188        makeLibraryPath [ "/usr" "/usr/local" ]
189        => "/usr/lib:/usr/local/lib"
190        pkgs = import <nixpkgs> { }
191        makeLibraryPath [ pkgs.openssl pkgs.zlib ]
192        => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
193   */
194   makeLibraryPath = makeSearchPathOutput "lib" "lib";
196   /* Construct a binary search path (such as $PATH) containing the
197      binaries for a set of packages.
199      Example:
200        makeBinPath ["/root" "/usr" "/usr/local"]
201        => "/root/bin:/usr/bin:/usr/local/bin"
202   */
203   makeBinPath = makeSearchPathOutput "bin" "bin";
205   /* Normalize path, removing extraneous /s
207      Type: normalizePath :: string -> string
209      Example:
210        normalizePath "/a//b///c/"
211        => "/a/b/c/"
212   */
213   normalizePath = s:
214     warnIf
215       (isPath s)
216       ''
217         lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported.
218             Path values are always normalised in Nix, so there's no need to call this function on them.
219             This function also copies the path to the Nix store and returns the store path, the same as "''${path}" will, which may not be what you want.
220             This behavior is deprecated and will throw an error in the future.''
221       (
222         builtins.foldl'
223           (x: y: if y == "/" && hasSuffix "/" x then x else x+y)
224           ""
225           (stringToCharacters s)
226       );
228   /* Depending on the boolean `cond', return either the given string
229      or the empty string. Useful to concatenate against a bigger string.
231      Type: optionalString :: bool -> string -> string
233      Example:
234        optionalString true "some-string"
235        => "some-string"
236        optionalString false "some-string"
237        => ""
238   */
239   optionalString =
240     # Condition
241     cond:
242     # String to return if condition is true
243     string: if cond then string else "";
245   /* Determine whether a string has given prefix.
247      Type: hasPrefix :: string -> string -> bool
249      Example:
250        hasPrefix "foo" "foobar"
251        => true
252        hasPrefix "foo" "barfoo"
253        => false
254   */
255   hasPrefix =
256     # Prefix to check for
257     pref:
258     # Input string
259     str:
260     # Before 23.05, paths would be copied to the store before converting them
261     # to strings and comparing. This was surprising and confusing.
262     warnIf
263       (isPath pref)
264       ''
265         lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported.
266             There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
267             This function also copies the path to the Nix store, which may not be what you want.
268             This behavior is deprecated and will throw an error in the future.
269             You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.''
270       (substring 0 (stringLength pref) str == pref);
272   /* Determine whether a string has given suffix.
274      Type: hasSuffix :: string -> string -> bool
276      Example:
277        hasSuffix "foo" "foobar"
278        => false
279        hasSuffix "foo" "barfoo"
280        => true
281   */
282   hasSuffix =
283     # Suffix to check for
284     suffix:
285     # Input string
286     content:
287     let
288       lenContent = stringLength content;
289       lenSuffix = stringLength suffix;
290     in
291     # Before 23.05, paths would be copied to the store before converting them
292     # to strings and comparing. This was surprising and confusing.
293     warnIf
294       (isPath suffix)
295       ''
296         lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
297             There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
298             This function also copies the path to the Nix store, which may not be what you want.
299             This behavior is deprecated and will throw an error in the future.''
300       (
301         lenContent >= lenSuffix
302         && substring (lenContent - lenSuffix) lenContent content == suffix
303       );
305   /* Determine whether a string contains the given infix
307     Type: hasInfix :: string -> string -> bool
309     Example:
310       hasInfix "bc" "abcd"
311       => true
312       hasInfix "ab" "abcd"
313       => true
314       hasInfix "cd" "abcd"
315       => true
316       hasInfix "foo" "abcd"
317       => false
318   */
319   hasInfix = infix: content:
320     # Before 23.05, paths would be copied to the store before converting them
321     # to strings and comparing. This was surprising and confusing.
322     warnIf
323       (isPath infix)
324       ''
325         lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported.
326             There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
327             This function also copies the path to the Nix store, which may not be what you want.
328             This behavior is deprecated and will throw an error in the future.''
329       (builtins.match ".*${escapeRegex infix}.*" "${content}" != null);
331   /* Convert a string to a list of characters (i.e. singleton strings).
332      This allows you to, e.g., map a function over each character.  However,
333      note that this will likely be horribly inefficient; Nix is not a
334      general purpose programming language. Complex string manipulations
335      should, if appropriate, be done in a derivation.
336      Also note that Nix treats strings as a list of bytes and thus doesn't
337      handle unicode.
339      Type: stringToCharacters :: string -> [string]
341      Example:
342        stringToCharacters ""
343        => [ ]
344        stringToCharacters "abc"
345        => [ "a" "b" "c" ]
346        stringToCharacters "🦄"
347        => [ "�" "�" "�" "�" ]
348   */
349   stringToCharacters = s:
350     genList (p: substring p 1 s) (stringLength s);
352   /* Manipulate a string character by character and replace them by
353      strings before concatenating the results.
355      Type: stringAsChars :: (string -> string) -> string -> string
357      Example:
358        stringAsChars (x: if x == "a" then "i" else x) "nax"
359        => "nix"
360   */
361   stringAsChars =
362     # Function to map over each individual character
363     f:
364     # Input string
365     s: concatStrings (
366       map f (stringToCharacters s)
367     );
369   /* Convert char to ascii value, must be in printable range
371      Type: charToInt :: string -> int
373      Example:
374        charToInt "A"
375        => 65
376        charToInt "("
377        => 40
379   */
380   charToInt = c: builtins.getAttr c asciiTable;
382   /* Escape occurrence of the elements of `list` in `string` by
383      prefixing it with a backslash.
385      Type: escape :: [string] -> string -> string
387      Example:
388        escape ["(" ")"] "(foo)"
389        => "\\(foo\\)"
390   */
391   escape = list: replaceStrings list (map (c: "\\${c}") list);
393   /* Escape occurrence of the element of `list` in `string` by
394      converting to its ASCII value and prefixing it with \\x.
395      Only works for printable ascii characters.
397      Type: escapeC = [string] -> string -> string
399      Example:
400        escapeC [" "] "foo bar"
401        => "foo\\x20bar"
403   */
404   escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
406   /* Escape the string so it can be safely placed inside a URL
407      query.
409      Type: escapeURL :: string -> string
411      Example:
412        escapeURL "foo/bar baz"
413        => "foo%2Fbar%20baz"
414   */
415   escapeURL = let
416     unreserved = [ "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "-" "_" "." "~" ];
417     toEscape = builtins.removeAttrs asciiTable unreserved;
418   in
419     replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape);
421   /* Quote string to be used safely within the Bourne shell.
423      Type: escapeShellArg :: string -> string
425      Example:
426        escapeShellArg "esc'ape\nme"
427        => "'esc'\\''ape\nme'"
428   */
429   escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
431   /* Quote all arguments to be safely passed to the Bourne shell.
433      Type: escapeShellArgs :: [string] -> string
435      Example:
436        escapeShellArgs ["one" "two three" "four'five"]
437        => "'one' 'two three' 'four'\\''five'"
438   */
439   escapeShellArgs = concatMapStringsSep " " escapeShellArg;
441   /* Test whether the given name is a valid POSIX shell variable name.
443      Type: string -> bool
445      Example:
446        isValidPosixName "foo_bar000"
447        => true
448        isValidPosixName "0-bad.jpg"
449        => false
450   */
451   isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
453   /* Translate a Nix value into a shell variable declaration, with proper escaping.
455      The value can be a string (mapped to a regular variable), a list of strings
456      (mapped to a Bash-style array) or an attribute set of strings (mapped to a
457      Bash-style associative array). Note that "string" includes string-coercible
458      values like paths or derivations.
460      Strings are translated into POSIX sh-compatible code; lists and attribute sets
461      assume a shell that understands Bash syntax (e.g. Bash or ZSH).
463      Type: string -> (string | listOf string | attrsOf string) -> string
465      Example:
466        ''
467          ${toShellVar "foo" "some string"}
468          [[ "$foo" == "some string" ]]
469        ''
470   */
471   toShellVar = name: value:
472     lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
473     if isAttrs value && ! isStringLike value then
474       "declare -A ${name}=(${
475         concatStringsSep " " (lib.mapAttrsToList (n: v:
476           "[${escapeShellArg n}]=${escapeShellArg v}"
477         ) value)
478       })"
479     else if isList value then
480       "declare -a ${name}=(${escapeShellArgs value})"
481     else
482       "${name}=${escapeShellArg value}"
483     );
485   /* Translate an attribute set into corresponding shell variable declarations
486      using `toShellVar`.
488      Type: attrsOf (string | listOf string | attrsOf string) -> string
490      Example:
491        let
492          foo = "value";
493          bar = foo;
494        in ''
495          ${toShellVars { inherit foo bar; }}
496          [[ "$foo" == "$bar" ]]
497        ''
498   */
499   toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
501   /* Turn a string into a Nix expression representing that string
503      Type: string -> string
505      Example:
506        escapeNixString "hello\${}\n"
507        => "\"hello\\\${}\\n\""
508   */
509   escapeNixString = s: escape ["$"] (toJSON s);
511   /* Turn a string into an exact regular expression
513      Type: string -> string
515      Example:
516        escapeRegex "[^a-z]*"
517        => "\\[\\^a-z]\\*"
518   */
519   escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
521   /* Quotes a string if it can't be used as an identifier directly.
523      Type: string -> string
525      Example:
526        escapeNixIdentifier "hello"
527        => "hello"
528        escapeNixIdentifier "0abc"
529        => "\"0abc\""
530   */
531   escapeNixIdentifier = s:
532     # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
533     if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
534     then s else escapeNixString s;
536   /* Escapes a string such that it is safe to include verbatim in an XML
537      document.
539      Type: string -> string
541      Example:
542        escapeXML ''"test" 'test' < & >''
543        => "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;"
544   */
545   escapeXML = builtins.replaceStrings
546     ["\"" "'" "<" ">" "&"]
547     ["&quot;" "&apos;" "&lt;" "&gt;" "&amp;"];
549   # warning added 12-12-2022
550   replaceChars = lib.warn "replaceChars is a deprecated alias of replaceStrings, replace usages of it with replaceStrings." builtins.replaceStrings;
552   # Case conversion utilities.
553   lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
554   upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
556   /* Converts an ASCII string to lower-case.
558      Type: toLower :: string -> string
560      Example:
561        toLower "HOME"
562        => "home"
563   */
564   toLower = replaceStrings upperChars lowerChars;
566   /* Converts an ASCII string to upper-case.
568      Type: toUpper :: string -> string
570      Example:
571        toUpper "home"
572        => "HOME"
573   */
574   toUpper = replaceStrings lowerChars upperChars;
576   /* Appends string context from another string.  This is an implementation
577      detail of Nix and should be used carefully.
579      Strings in Nix carry an invisible `context` which is a list of strings
580      representing store paths.  If the string is later used in a derivation
581      attribute, the derivation will properly populate the inputDrvs and
582      inputSrcs.
584      Example:
585        pkgs = import <nixpkgs> { };
586        addContextFrom pkgs.coreutils "bar"
587        => "bar"
588   */
589   addContextFrom = a: b: substring 0 0 a + b;
591   /* Cut a string with a separator and produces a list of strings which
592      were separated by this separator.
594      Example:
595        splitString "." "foo.bar.baz"
596        => [ "foo" "bar" "baz" ]
597        splitString "/" "/usr/local/bin"
598        => [ "" "usr" "local" "bin" ]
599   */
600   splitString = sep: s:
601     let
602       splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s));
603     in
604       map (addContextFrom s) splits;
606   /* Return a string without the specified prefix, if the prefix matches.
608      Type: string -> string -> string
610      Example:
611        removePrefix "foo." "foo.bar.baz"
612        => "bar.baz"
613        removePrefix "xxx" "foo.bar.baz"
614        => "foo.bar.baz"
615   */
616   removePrefix =
617     # Prefix to remove if it matches
618     prefix:
619     # Input string
620     str:
621     # Before 23.05, paths would be copied to the store before converting them
622     # to strings and comparing. This was surprising and confusing.
623     warnIf
624       (isPath prefix)
625       ''
626         lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported.
627             There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case.
628             This function also copies the path to the Nix store, which may not be what you want.
629             This behavior is deprecated and will throw an error in the future.''
630     (let
631       preLen = stringLength prefix;
632     in
633       if substring 0 preLen str == prefix then
634         # -1 will take the string until the end
635         substring preLen (-1) str
636       else
637         str);
639   /* Return a string without the specified suffix, if the suffix matches.
641      Type: string -> string -> string
643      Example:
644        removeSuffix "front" "homefront"
645        => "home"
646        removeSuffix "xxx" "homefront"
647        => "homefront"
648   */
649   removeSuffix =
650     # Suffix to remove if it matches
651     suffix:
652     # Input string
653     str:
654     # Before 23.05, paths would be copied to the store before converting them
655     # to strings and comparing. This was surprising and confusing.
656     warnIf
657       (isPath suffix)
658       ''
659         lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
660             There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case.
661             This function also copies the path to the Nix store, which may not be what you want.
662             This behavior is deprecated and will throw an error in the future.''
663     (let
664       sufLen = stringLength suffix;
665       sLen = stringLength str;
666     in
667       if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
668         substring 0 (sLen - sufLen) str
669       else
670         str);
672   /* Return true if string v1 denotes a version older than v2.
674      Example:
675        versionOlder "1.1" "1.2"
676        => true
677        versionOlder "1.1" "1.1"
678        => false
679   */
680   versionOlder = v1: v2: compareVersions v2 v1 == 1;
682   /* Return true if string v1 denotes a version equal to or newer than v2.
684      Example:
685        versionAtLeast "1.1" "1.0"
686        => true
687        versionAtLeast "1.1" "1.1"
688        => true
689        versionAtLeast "1.1" "1.2"
690        => false
691   */
692   versionAtLeast = v1: v2: !versionOlder v1 v2;
694   /* This function takes an argument that's either a derivation or a
695      derivation's "name" attribute and extracts the name part from that
696      argument.
698      Example:
699        getName "youtube-dl-2016.01.01"
700        => "youtube-dl"
701        getName pkgs.youtube-dl
702        => "youtube-dl"
703   */
704   getName = x:
705    let
706      parse = drv: (parseDrvName drv).name;
707    in if isString x
708       then parse x
709       else x.pname or (parse x.name);
711   /* This function takes an argument that's either a derivation or a
712      derivation's "name" attribute and extracts the version part from that
713      argument.
715      Example:
716        getVersion "youtube-dl-2016.01.01"
717        => "2016.01.01"
718        getVersion pkgs.youtube-dl
719        => "2016.01.01"
720   */
721   getVersion = x:
722    let
723      parse = drv: (parseDrvName drv).version;
724    in if isString x
725       then parse x
726       else x.version or (parse x.name);
728   /* Extract name with version from URL. Ask for separator which is
729      supposed to start extension.
731      Example:
732        nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
733        => "nix"
734        nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
735        => "nix-1.7-x86"
736   */
737   nameFromURL = url: sep:
738     let
739       components = splitString "/" url;
740       filename = lib.last components;
741       name = head (splitString sep filename);
742     in assert name != filename; name;
744   /* Create a "-D<feature>:<type>=<value>" string that can be passed to typical
745      CMake invocations.
747     Type: cmakeOptionType :: string -> string -> string -> string
749      @param feature The feature to be set
750      @param type The type of the feature to be set, as described in
751                  https://cmake.org/cmake/help/latest/command/set.html
752                  the possible values (case insensitive) are:
753                  BOOL FILEPATH PATH STRING INTERNAL
754      @param value The desired value
756      Example:
757        cmakeOptionType "string" "ENGINE" "sdl2"
758        => "-DENGINE:STRING=sdl2"
759   */
760   cmakeOptionType = type: feature: value:
761     assert (lib.elem (lib.toUpper type)
762       [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" ]);
763     assert (lib.isString feature);
764     assert (lib.isString value);
765     "-D${feature}:${lib.toUpper type}=${value}";
767   /* Create a -D<condition>={TRUE,FALSE} string that can be passed to typical
768      CMake invocations.
770     Type: cmakeBool :: string -> bool -> string
772      @param condition The condition to be made true or false
773      @param flag The controlling flag of the condition
775      Example:
776        cmakeBool "ENABLE_STATIC_LIBS" false
777        => "-DENABLESTATIC_LIBS:BOOL=FALSE"
778   */
779   cmakeBool = condition: flag:
780     assert (lib.isString condition);
781     assert (lib.isBool flag);
782     cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag));
784   /* Create a -D<feature>:STRING=<value> string that can be passed to typical
785      CMake invocations.
786      This is the most typical usage, so it deserves a special case.
788     Type: cmakeFeature :: string -> string -> string
790      @param condition The condition to be made true or false
791      @param flag The controlling flag of the condition
793      Example:
794        cmakeFeature "MODULES" "badblock"
795        => "-DMODULES:STRING=badblock"
796   */
797   cmakeFeature = feature: value:
798     assert (lib.isString feature);
799     assert (lib.isString value);
800     cmakeOptionType "string" feature value;
802   /* Create a -D<feature>=<value> string that can be passed to typical Meson
803      invocations.
805     Type: mesonOption :: string -> string -> string
807      @param feature The feature to be set
808      @param value The desired value
810      Example:
811        mesonOption "engine" "opengl"
812        => "-Dengine=opengl"
813   */
814   mesonOption = feature: value:
815     assert (lib.isString feature);
816     assert (lib.isString value);
817     "-D${feature}=${value}";
819   /* Create a -D<condition>={true,false} string that can be passed to typical
820      Meson invocations.
822     Type: mesonBool :: string -> bool -> string
824      @param condition The condition to be made true or false
825      @param flag The controlling flag of the condition
827      Example:
828        mesonBool "hardened" true
829        => "-Dhardened=true"
830        mesonBool "static" false
831        => "-Dstatic=false"
832   */
833   mesonBool = condition: flag:
834     assert (lib.isString condition);
835     assert (lib.isBool flag);
836     mesonOption condition (lib.boolToString flag);
838   /* Create a -D<feature>={enabled,disabled} string that can be passed to
839      typical Meson invocations.
841     Type: mesonEnable :: string -> bool -> string
843      @param feature The feature to be enabled or disabled
844      @param flag The controlling flag
846      Example:
847        mesonEnable "docs" true
848        => "-Ddocs=enabled"
849        mesonEnable "savage" false
850        => "-Dsavage=disabled"
851   */
852   mesonEnable = feature: flag:
853     assert (lib.isString feature);
854     assert (lib.isBool flag);
855     mesonOption feature (if flag then "enabled" else "disabled");
857   /* Create an --{enable,disable}-<feature> string that can be passed to
858      standard GNU Autoconf scripts.
860      Example:
861        enableFeature true "shared"
862        => "--enable-shared"
863        enableFeature false "shared"
864        => "--disable-shared"
865   */
866   enableFeature = flag: feature:
867     assert lib.isBool flag;
868     assert lib.isString feature; # e.g. passing openssl instead of "openssl"
869     "--${if flag then "enable" else "disable"}-${feature}";
871   /* Create an --{enable-<feature>=<value>,disable-<feature>} string that can be passed to
872      standard GNU Autoconf scripts.
874      Example:
875        enableFeatureAs true "shared" "foo"
876        => "--enable-shared=foo"
877        enableFeatureAs false "shared" (throw "ignored")
878        => "--disable-shared"
879   */
880   enableFeatureAs = flag: feature: value:
881     enableFeature flag feature + optionalString flag "=${value}";
883   /* Create an --{with,without}-<feature> string that can be passed to
884      standard GNU Autoconf scripts.
886      Example:
887        withFeature true "shared"
888        => "--with-shared"
889        withFeature false "shared"
890        => "--without-shared"
891   */
892   withFeature = flag: feature:
893     assert isString feature; # e.g. passing openssl instead of "openssl"
894     "--${if flag then "with" else "without"}-${feature}";
896   /* Create an --{with-<feature>=<value>,without-<feature>} string that can be passed to
897      standard GNU Autoconf scripts.
899      Example:
900        withFeatureAs true "shared" "foo"
901        => "--with-shared=foo"
902        withFeatureAs false "shared" (throw "ignored")
903        => "--without-shared"
904   */
905   withFeatureAs = flag: feature: value:
906     withFeature flag feature + optionalString flag "=${value}";
908   /* Create a fixed width string with additional prefix to match
909      required width.
911      This function will fail if the input string is longer than the
912      requested length.
914      Type: fixedWidthString :: int -> string -> string -> string
916      Example:
917        fixedWidthString 5 "0" (toString 15)
918        => "00015"
919   */
920   fixedWidthString = width: filler: str:
921     let
922       strw = lib.stringLength str;
923       reqWidth = width - (lib.stringLength filler);
924     in
925       assert lib.assertMsg (strw <= width)
926         "fixedWidthString: requested string length (${
927           toString width}) must not be shorter than actual length (${
928             toString strw})";
929       if strw == width then str else filler + fixedWidthString reqWidth filler str;
931   /* Format a number adding leading zeroes up to fixed width.
933      Example:
934        fixedWidthNumber 5 15
935        => "00015"
936   */
937   fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
939   /* Convert a float to a string, but emit a warning when precision is lost
940      during the conversion
942      Example:
943        floatToString 0.000001
944        => "0.000001"
945        floatToString 0.0000001
946        => trace: warning: Imprecise conversion from float to string 0.000000
947           "0.000000"
948   */
949   floatToString = float: let
950     result = toString float;
951     precise = float == fromJSON result;
952   in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}"
953     result;
955   /* Soft-deprecated function. While the original implementation is available as
956      isConvertibleWithToString, consider using isStringLike instead, if suitable. */
957   isCoercibleToString = lib.warnIf (lib.isInOldestRelease 2305)
958     "lib.strings.isCoercibleToString is deprecated in favor of either isStringLike or isConvertibleWithToString. Only use the latter if it needs to return true for null, numbers, booleans and list of similarly coercibles."
959     isConvertibleWithToString;
961   /* Check whether a list or other value can be passed to toString.
963      Many types of value are coercible to string this way, including int, float,
964      null, bool, list of similarly coercible values.
965   */
966   isConvertibleWithToString = x:
967     isStringLike x ||
968     elem (typeOf x) [ "null" "int" "float" "bool" ] ||
969     (isList x && lib.all isConvertibleWithToString x);
971   /* Check whether a value can be coerced to a string.
972      The value must be a string, path, or attribute set.
974      String-like values can be used without explicit conversion in
975      string interpolations and in most functions that expect a string.
976    */
977   isStringLike = x:
978     isString x ||
979     isPath x ||
980     x ? outPath ||
981     x ? __toString;
983   /* Check whether a value is a store path.
985      Example:
986        isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
987        => false
988        isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
989        => true
990        isStorePath pkgs.python
991        => true
992        isStorePath [] || isStorePath 42 || isStorePath {} || â€¦
993        => false
994   */
995   isStorePath = x:
996     if isStringLike x then
997       let str = toString x; in
998       substring 0 1 str == "/"
999       && dirOf str == storeDir
1000     else
1001       false;
1003   /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
1004   ambiguity between zero-padded and octal numbers. See toIntBase10.
1006      Type: string -> int
1008      Example:
1010        toInt "1337"
1011        => 1337
1013        toInt "-4"
1014        => -4
1016        toInt " 123 "
1017        => 123
1019        toInt "00024"
1020        => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
1022        toInt "3.14"
1023        => error: floating point JSON numbers are not supported
1024   */
1025   toInt = str:
1026     let
1027       # RegEx: Match any leading whitespace, possibly a '-', one or more digits,
1028       # and finally match any trailing whitespace.
1029       strippedInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*" str;
1031       # RegEx: Match a leading '0' then one or more digits.
1032       isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == [];
1034       # Attempt to parse input
1035       parsedInput = fromJSON (head strippedInput);
1037       generalError = "toInt: Could not convert ${escapeNixString str} to int.";
1039       octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}"
1040       + " between octal and zero padded integer.";
1042     in
1043       # Error on presence of non digit characters.
1044       if strippedInput == null
1045       then throw generalError
1046       # Error on presence of leading zero/octal ambiguity.
1047       else if isLeadingZero
1048       then throw octalAmbigError
1049       # Error if parse function fails.
1050       else if !isInt parsedInput
1051       then throw generalError
1052       # Return result.
1053       else parsedInput;
1056   /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
1058      Type: string -> int
1060      Example:
1061        toIntBase10 "1337"
1062        => 1337
1064        toIntBase10 "-4"
1065        => -4
1067        toIntBase10 " 123 "
1068        => 123
1070        toIntBase10 "00024"
1071        => 24
1073        toIntBase10 "3.14"
1074        => error: floating point JSON numbers are not supported
1075   */
1076   toIntBase10 = str:
1077     let
1078       # RegEx: Match any leading whitespace, then match any zero padding,
1079       # capture possibly a '-' followed by one or more digits,
1080       # and finally match any trailing whitespace.
1081       strippedInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*" str;
1083       # RegEx: Match at least one '0'.
1084       isZero = match "0+" (head strippedInput) == [];
1086       # Attempt to parse input
1087       parsedInput = fromJSON (head strippedInput);
1089       generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
1091     in
1092       # Error on presence of non digit characters.
1093       if strippedInput == null
1094       then throw generalError
1095       # In the special case zero-padded zero (00000), return early.
1096       else if isZero
1097       then 0
1098       # Error if parse function fails.
1099       else if !isInt parsedInput
1100       then throw generalError
1101       # Return result.
1102       else parsedInput;
1104   /* Read a list of paths from `file`, relative to the `rootPath`.
1105      Lines beginning with `#` are treated as comments and ignored.
1106      Whitespace is significant.
1108      NOTE: This function is not performant and should be avoided.
1110      Example:
1111        readPathsFromFile /prefix
1112          ./pkgs/development/libraries/qt-5/5.4/qtbase/series
1113        => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
1114             "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
1115             "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
1116             "/prefix/nix-profiles-library-paths.patch"
1117             "/prefix/compose-search-path.patch" ]
1118   */
1119   readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead"
1120     (rootPath: file:
1121       let
1122         lines = lib.splitString "\n" (readFile file);
1123         removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
1124         relativePaths = removeComments lines;
1125         absolutePaths = map (path: rootPath + "/${path}") relativePaths;
1126       in
1127         absolutePaths);
1129   /* Read the contents of a file removing the trailing \n
1131      Type: fileContents :: path -> string
1133      Example:
1134        $ echo "1.0" > ./version
1136        fileContents ./version
1137        => "1.0"
1138   */
1139   fileContents = file: removeSuffix "\n" (readFile file);
1142   /* Creates a valid derivation name from a potentially invalid one.
1144      Type: sanitizeDerivationName :: String -> String
1146      Example:
1147        sanitizeDerivationName "../hello.bar # foo"
1148        => "-hello.bar-foo"
1149        sanitizeDerivationName ""
1150        => "unknown"
1151        sanitizeDerivationName pkgs.hello
1152        => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
1153   */
1154   sanitizeDerivationName =
1155   let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
1156   in
1157   string:
1158   # First detect the common case of already valid strings, to speed those up
1159   if stringLength string <= 207 && okRegex string != null
1160   then unsafeDiscardStringContext string
1161   else lib.pipe string [
1162     # Get rid of string context. This is safe under the assumption that the
1163     # resulting string is only used as a derivation name
1164     unsafeDiscardStringContext
1165     # Strip all leading "."
1166     (x: elemAt (match "\\.*(.*)" x) 0)
1167     # Split out all invalid characters
1168     # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
1169     # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
1170     (split "[^[:alnum:]+._?=-]+")
1171     # Replace invalid character ranges with a "-"
1172     (concatMapStrings (s: if lib.isList s then "-" else s))
1173     # Limit to 211 characters (minus 4 chars for ".drv")
1174     (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
1175     # If the result is empty, replace it with "unknown"
1176     (x: if stringLength x == 0 then "unknown" else x)
1177   ];
1179   /* Computes the Levenshtein distance between two strings.
1180      Complexity O(n*m) where n and m are the lengths of the strings.
1181      Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
1183      Type: levenshtein :: string -> string -> int
1185      Example:
1186        levenshtein "foo" "foo"
1187        => 0
1188        levenshtein "book" "hook"
1189        => 1
1190        levenshtein "hello" "Heyo"
1191        => 3
1192   */
1193   levenshtein = a: b: let
1194     # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
1195     arr = lib.genList (i:
1196       lib.genList (j:
1197         dist i j
1198       ) (stringLength b + 1)
1199     ) (stringLength a + 1);
1200     d = x: y: lib.elemAt (lib.elemAt arr x) y;
1201     dist = i: j:
1202       let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
1203         then 0 else 1;
1204       in
1205       if j == 0 then i
1206       else if i == 0 then j
1207       else lib.min
1208         ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1))
1209         ( d (i - 1) (j - 1) + c );
1210   in d (stringLength a) (stringLength b);
1212   /* Returns the length of the prefix common to both strings.
1213   */
1214   commonPrefixLength = a: b:
1215     let
1216       m = lib.min (stringLength a) (stringLength b);
1217       go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
1218     in go 0;
1220   /* Returns the length of the suffix common to both strings.
1221   */
1222   commonSuffixLength = a: b:
1223     let
1224       m = lib.min (stringLength a) (stringLength b);
1225       go = i: if i >= m then m else if substring (stringLength a - i - 1) 1 a == substring (stringLength b - i - 1) 1 b then go (i + 1) else i;
1226     in go 0;
1228   /* Returns whether the levenshtein distance between two strings is at most some value
1229      Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
1231      Type: levenshteinAtMost :: int -> string -> string -> bool
1233      Example:
1234        levenshteinAtMost 0 "foo" "foo"
1235        => true
1236        levenshteinAtMost 1 "foo" "boa"
1237        => false
1238        levenshteinAtMost 2 "foo" "boa"
1239        => true
1240        levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1241        => false
1242        levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1243        => true
1245   */
1246   levenshteinAtMost = let
1247     infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1249     # This function takes two strings stripped by their common pre and suffix,
1250     # and returns whether they differ by at most two by Levenshtein distance.
1251     # Because of this stripping, if they do indeed differ by at most two edits,
1252     # we know that those edits were (if at all) done at the start or the end,
1253     # while the middle has to have stayed the same. This fact is used in the
1254     # implementation.
1255     infixDifferAtMost2 = x: y:
1256       let
1257         xlen = stringLength x;
1258         ylen = stringLength y;
1259         # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
1260         # diff is one of 0, 1 or 2
1261         diff = xlen - ylen;
1263         # Infix of x and y, stripped by the left and right most character
1264         xinfix = substring 1 (xlen - 2) x;
1265         yinfix = substring 1 (ylen - 2) y;
1267         # x and y but a character deleted at the left or right
1268         xdelr = substring 0 (xlen - 1) x;
1269         xdell = substring 1 (xlen - 1) x;
1270         ydelr = substring 0 (ylen - 1) y;
1271         ydell = substring 1 (ylen - 1) y;
1272       in
1273         # A length difference of 2 can only be gotten with 2 delete edits,
1274         # which have to have happened at the start and end of x
1275         # Example: "abcdef" -> "bcde"
1276         if diff == 2 then xinfix == y
1277         # A length difference of 1 can only be gotten with a deletion on the
1278         # right and a replacement on the left or vice versa.
1279         # Example: "abcdef" -> "bcdez" or "zbcde"
1280         else if diff == 1 then xinfix == ydelr || xinfix == ydell
1281         # No length difference can either happen through replacements on both
1282         # sides, or a deletion on the left and an insertion on the right or
1283         # vice versa
1284         # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1285         else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1287     in k: if k <= 0 then a: b: a == b else
1288       let f = a: b:
1289         let
1290           alen = stringLength a;
1291           blen = stringLength b;
1292           prelen = commonPrefixLength a b;
1293           suflen = commonSuffixLength a b;
1294           presuflen = prelen + suflen;
1295           ainfix = substring prelen (alen - presuflen) a;
1296           binfix = substring prelen (blen - presuflen) b;
1297         in
1298         # Make a be the bigger string
1299         if alen < blen then f b a
1300         # If a has over k more characters than b, even with k deletes on a, b can't be reached
1301         else if alen - blen > k then false
1302         else if k == 1 then infixDifferAtMost1 ainfix binfix
1303         else if k == 2 then infixDifferAtMost2 ainfix binfix
1304         else levenshtein ainfix binfix <= k;
1305       in f;