Merge pull request #270175 from ShamrockLee/backport-23.11-apptainer-localstatedir
[NixPkgs.git] / lib / strings.nix
blob695aaaacd34889cf31231e85bae07ac76612468e
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   /*
148     Replicate a string n times,
149     and concatenate the parts into a new string.
151     Type: replicate :: int -> string -> string
153     Example:
154       replicate 3 "v"
155       => "vvv"
156       replicate 5 "hello"
157       => "hellohellohellohellohello"
158   */
159   replicate = n: s: concatStrings (lib.lists.replicate n s);
161   /* Construct a Unix-style, colon-separated search path consisting of
162      the given `subDir` appended to each of the given paths.
164      Type: makeSearchPath :: string -> [string] -> string
166      Example:
167        makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
168        => "/root/bin:/usr/bin:/usr/local/bin"
169        makeSearchPath "bin" [""]
170        => "/bin"
171   */
172   makeSearchPath =
173     # Directory name to append
174     subDir:
175     # List of base paths
176     paths:
177     concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
179   /* Construct a Unix-style search path by appending the given
180      `subDir` to the specified `output` of each of the packages. If no
181      output by the given name is found, fallback to `.out` and then to
182      the default.
184      Type: string -> string -> [package] -> string
186      Example:
187        makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
188        => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
189   */
190   makeSearchPathOutput =
191     # Package output to use
192     output:
193     # Directory name to append
194     subDir:
195     # List of packages
196     pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
198   /* Construct a library search path (such as RPATH) containing the
199      libraries for a set of packages
201      Example:
202        makeLibraryPath [ "/usr" "/usr/local" ]
203        => "/usr/lib:/usr/local/lib"
204        pkgs = import <nixpkgs> { }
205        makeLibraryPath [ pkgs.openssl pkgs.zlib ]
206        => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
207   */
208   makeLibraryPath = makeSearchPathOutput "lib" "lib";
210   /* Construct a binary search path (such as $PATH) containing the
211      binaries for a set of packages.
213      Example:
214        makeBinPath ["/root" "/usr" "/usr/local"]
215        => "/root/bin:/usr/bin:/usr/local/bin"
216   */
217   makeBinPath = makeSearchPathOutput "bin" "bin";
219   /* Normalize path, removing extraneous /s
221      Type: normalizePath :: string -> string
223      Example:
224        normalizePath "/a//b///c/"
225        => "/a/b/c/"
226   */
227   normalizePath = s:
228     warnIf
229       (isPath s)
230       ''
231         lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported.
232             Path values are always normalised in Nix, so there's no need to call this function on them.
233             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.
234             This behavior is deprecated and will throw an error in the future.''
235       (
236         builtins.foldl'
237           (x: y: if y == "/" && hasSuffix "/" x then x else x+y)
238           ""
239           (stringToCharacters s)
240       );
242   /* Depending on the boolean `cond', return either the given string
243      or the empty string. Useful to concatenate against a bigger string.
245      Type: optionalString :: bool -> string -> string
247      Example:
248        optionalString true "some-string"
249        => "some-string"
250        optionalString false "some-string"
251        => ""
252   */
253   optionalString =
254     # Condition
255     cond:
256     # String to return if condition is true
257     string: if cond then string else "";
259   /* Determine whether a string has given prefix.
261      Type: hasPrefix :: string -> string -> bool
263      Example:
264        hasPrefix "foo" "foobar"
265        => true
266        hasPrefix "foo" "barfoo"
267        => false
268   */
269   hasPrefix =
270     # Prefix to check for
271     pref:
272     # Input string
273     str:
274     # Before 23.05, paths would be copied to the store before converting them
275     # to strings and comparing. This was surprising and confusing.
276     warnIf
277       (isPath pref)
278       ''
279         lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported.
280             There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
281             This function also copies the path to the Nix store, which may not be what you want.
282             This behavior is deprecated and will throw an error in the future.
283             You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.''
284       (substring 0 (stringLength pref) str == pref);
286   /* Determine whether a string has given suffix.
288      Type: hasSuffix :: string -> string -> bool
290      Example:
291        hasSuffix "foo" "foobar"
292        => false
293        hasSuffix "foo" "barfoo"
294        => true
295   */
296   hasSuffix =
297     # Suffix to check for
298     suffix:
299     # Input string
300     content:
301     let
302       lenContent = stringLength content;
303       lenSuffix = stringLength suffix;
304     in
305     # Before 23.05, paths would be copied to the store before converting them
306     # to strings and comparing. This was surprising and confusing.
307     warnIf
308       (isPath suffix)
309       ''
310         lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
311             There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
312             This function also copies the path to the Nix store, which may not be what you want.
313             This behavior is deprecated and will throw an error in the future.''
314       (
315         lenContent >= lenSuffix
316         && substring (lenContent - lenSuffix) lenContent content == suffix
317       );
319   /* Determine whether a string contains the given infix
321     Type: hasInfix :: string -> string -> bool
323     Example:
324       hasInfix "bc" "abcd"
325       => true
326       hasInfix "ab" "abcd"
327       => true
328       hasInfix "cd" "abcd"
329       => true
330       hasInfix "foo" "abcd"
331       => false
332   */
333   hasInfix = infix: content:
334     # Before 23.05, paths would be copied to the store before converting them
335     # to strings and comparing. This was surprising and confusing.
336     warnIf
337       (isPath infix)
338       ''
339         lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported.
340             There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
341             This function also copies the path to the Nix store, which may not be what you want.
342             This behavior is deprecated and will throw an error in the future.''
343       (builtins.match ".*${escapeRegex infix}.*" "${content}" != null);
345   /* Convert a string to a list of characters (i.e. singleton strings).
346      This allows you to, e.g., map a function over each character.  However,
347      note that this will likely be horribly inefficient; Nix is not a
348      general purpose programming language. Complex string manipulations
349      should, if appropriate, be done in a derivation.
350      Also note that Nix treats strings as a list of bytes and thus doesn't
351      handle unicode.
353      Type: stringToCharacters :: string -> [string]
355      Example:
356        stringToCharacters ""
357        => [ ]
358        stringToCharacters "abc"
359        => [ "a" "b" "c" ]
360        stringToCharacters "🦄"
361        => [ "�" "�" "�" "�" ]
362   */
363   stringToCharacters = s:
364     genList (p: substring p 1 s) (stringLength s);
366   /* Manipulate a string character by character and replace them by
367      strings before concatenating the results.
369      Type: stringAsChars :: (string -> string) -> string -> string
371      Example:
372        stringAsChars (x: if x == "a" then "i" else x) "nax"
373        => "nix"
374   */
375   stringAsChars =
376     # Function to map over each individual character
377     f:
378     # Input string
379     s: concatStrings (
380       map f (stringToCharacters s)
381     );
383   /* Convert char to ascii value, must be in printable range
385      Type: charToInt :: string -> int
387      Example:
388        charToInt "A"
389        => 65
390        charToInt "("
391        => 40
393   */
394   charToInt = c: builtins.getAttr c asciiTable;
396   /* Escape occurrence of the elements of `list` in `string` by
397      prefixing it with a backslash.
399      Type: escape :: [string] -> string -> string
401      Example:
402        escape ["(" ")"] "(foo)"
403        => "\\(foo\\)"
404   */
405   escape = list: replaceStrings list (map (c: "\\${c}") list);
407   /* Escape occurrence of the element of `list` in `string` by
408      converting to its ASCII value and prefixing it with \\x.
409      Only works for printable ascii characters.
411      Type: escapeC = [string] -> string -> string
413      Example:
414        escapeC [" "] "foo bar"
415        => "foo\\x20bar"
417   */
418   escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
420   /* Escape the string so it can be safely placed inside a URL
421      query.
423      Type: escapeURL :: string -> string
425      Example:
426        escapeURL "foo/bar baz"
427        => "foo%2Fbar%20baz"
428   */
429   escapeURL = let
430     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" "-" "_" "." "~" ];
431     toEscape = builtins.removeAttrs asciiTable unreserved;
432   in
433     replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape);
435   /* Quote string to be used safely within the Bourne shell.
437      Type: escapeShellArg :: string -> string
439      Example:
440        escapeShellArg "esc'ape\nme"
441        => "'esc'\\''ape\nme'"
442   */
443   escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
445   /* Quote all arguments to be safely passed to the Bourne shell.
447      Type: escapeShellArgs :: [string] -> string
449      Example:
450        escapeShellArgs ["one" "two three" "four'five"]
451        => "'one' 'two three' 'four'\\''five'"
452   */
453   escapeShellArgs = concatMapStringsSep " " escapeShellArg;
455   /* Test whether the given name is a valid POSIX shell variable name.
457      Type: string -> bool
459      Example:
460        isValidPosixName "foo_bar000"
461        => true
462        isValidPosixName "0-bad.jpg"
463        => false
464   */
465   isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
467   /* Translate a Nix value into a shell variable declaration, with proper escaping.
469      The value can be a string (mapped to a regular variable), a list of strings
470      (mapped to a Bash-style array) or an attribute set of strings (mapped to a
471      Bash-style associative array). Note that "string" includes string-coercible
472      values like paths or derivations.
474      Strings are translated into POSIX sh-compatible code; lists and attribute sets
475      assume a shell that understands Bash syntax (e.g. Bash or ZSH).
477      Type: string -> (string | listOf string | attrsOf string) -> string
479      Example:
480        ''
481          ${toShellVar "foo" "some string"}
482          [[ "$foo" == "some string" ]]
483        ''
484   */
485   toShellVar = name: value:
486     lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
487     if isAttrs value && ! isStringLike value then
488       "declare -A ${name}=(${
489         concatStringsSep " " (lib.mapAttrsToList (n: v:
490           "[${escapeShellArg n}]=${escapeShellArg v}"
491         ) value)
492       })"
493     else if isList value then
494       "declare -a ${name}=(${escapeShellArgs value})"
495     else
496       "${name}=${escapeShellArg value}"
497     );
499   /* Translate an attribute set into corresponding shell variable declarations
500      using `toShellVar`.
502      Type: attrsOf (string | listOf string | attrsOf string) -> string
504      Example:
505        let
506          foo = "value";
507          bar = foo;
508        in ''
509          ${toShellVars { inherit foo bar; }}
510          [[ "$foo" == "$bar" ]]
511        ''
512   */
513   toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
515   /* Turn a string into a Nix expression representing that string
517      Type: string -> string
519      Example:
520        escapeNixString "hello\${}\n"
521        => "\"hello\\\${}\\n\""
522   */
523   escapeNixString = s: escape ["$"] (toJSON s);
525   /* Turn a string into an exact regular expression
527      Type: string -> string
529      Example:
530        escapeRegex "[^a-z]*"
531        => "\\[\\^a-z]\\*"
532   */
533   escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
535   /* Quotes a string if it can't be used as an identifier directly.
537      Type: string -> string
539      Example:
540        escapeNixIdentifier "hello"
541        => "hello"
542        escapeNixIdentifier "0abc"
543        => "\"0abc\""
544   */
545   escapeNixIdentifier = s:
546     # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
547     if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
548     then s else escapeNixString s;
550   /* Escapes a string such that it is safe to include verbatim in an XML
551      document.
553      Type: string -> string
555      Example:
556        escapeXML ''"test" 'test' < & >''
557        => "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;"
558   */
559   escapeXML = builtins.replaceStrings
560     ["\"" "'" "<" ">" "&"]
561     ["&quot;" "&apos;" "&lt;" "&gt;" "&amp;"];
563   # warning added 12-12-2022
564   replaceChars = lib.warn "replaceChars is a deprecated alias of replaceStrings, replace usages of it with replaceStrings." builtins.replaceStrings;
566   # Case conversion utilities.
567   lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
568   upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
570   /* Converts an ASCII string to lower-case.
572      Type: toLower :: string -> string
574      Example:
575        toLower "HOME"
576        => "home"
577   */
578   toLower = replaceStrings upperChars lowerChars;
580   /* Converts an ASCII string to upper-case.
582      Type: toUpper :: string -> string
584      Example:
585        toUpper "home"
586        => "HOME"
587   */
588   toUpper = replaceStrings lowerChars upperChars;
590   /* Appends string context from another string.  This is an implementation
591      detail of Nix and should be used carefully.
593      Strings in Nix carry an invisible `context` which is a list of strings
594      representing store paths.  If the string is later used in a derivation
595      attribute, the derivation will properly populate the inputDrvs and
596      inputSrcs.
598      Example:
599        pkgs = import <nixpkgs> { };
600        addContextFrom pkgs.coreutils "bar"
601        => "bar"
602   */
603   addContextFrom = a: b: substring 0 0 a + b;
605   /* Cut a string with a separator and produces a list of strings which
606      were separated by this separator.
608      Example:
609        splitString "." "foo.bar.baz"
610        => [ "foo" "bar" "baz" ]
611        splitString "/" "/usr/local/bin"
612        => [ "" "usr" "local" "bin" ]
613   */
614   splitString = sep: s:
615     let
616       splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s));
617     in
618       map (addContextFrom s) splits;
620   /* Return a string without the specified prefix, if the prefix matches.
622      Type: string -> string -> string
624      Example:
625        removePrefix "foo." "foo.bar.baz"
626        => "bar.baz"
627        removePrefix "xxx" "foo.bar.baz"
628        => "foo.bar.baz"
629   */
630   removePrefix =
631     # Prefix to remove if it matches
632     prefix:
633     # Input string
634     str:
635     # Before 23.05, paths would be copied to the store before converting them
636     # to strings and comparing. This was surprising and confusing.
637     warnIf
638       (isPath prefix)
639       ''
640         lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported.
641             There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case.
642             This function also copies the path to the Nix store, which may not be what you want.
643             This behavior is deprecated and will throw an error in the future.''
644     (let
645       preLen = stringLength prefix;
646     in
647       if substring 0 preLen str == prefix then
648         # -1 will take the string until the end
649         substring preLen (-1) str
650       else
651         str);
653   /* Return a string without the specified suffix, if the suffix matches.
655      Type: string -> string -> string
657      Example:
658        removeSuffix "front" "homefront"
659        => "home"
660        removeSuffix "xxx" "homefront"
661        => "homefront"
662   */
663   removeSuffix =
664     # Suffix to remove if it matches
665     suffix:
666     # Input string
667     str:
668     # Before 23.05, paths would be copied to the store before converting them
669     # to strings and comparing. This was surprising and confusing.
670     warnIf
671       (isPath suffix)
672       ''
673         lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
674             There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case.
675             This function also copies the path to the Nix store, which may not be what you want.
676             This behavior is deprecated and will throw an error in the future.''
677     (let
678       sufLen = stringLength suffix;
679       sLen = stringLength str;
680     in
681       if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
682         substring 0 (sLen - sufLen) str
683       else
684         str);
686   /* Return true if string v1 denotes a version older than v2.
688      Example:
689        versionOlder "1.1" "1.2"
690        => true
691        versionOlder "1.1" "1.1"
692        => false
693   */
694   versionOlder = v1: v2: compareVersions v2 v1 == 1;
696   /* Return true if string v1 denotes a version equal to or newer than v2.
698      Example:
699        versionAtLeast "1.1" "1.0"
700        => true
701        versionAtLeast "1.1" "1.1"
702        => true
703        versionAtLeast "1.1" "1.2"
704        => false
705   */
706   versionAtLeast = v1: v2: !versionOlder v1 v2;
708   /* This function takes an argument that's either a derivation or a
709      derivation's "name" attribute and extracts the name part from that
710      argument.
712      Example:
713        getName "youtube-dl-2016.01.01"
714        => "youtube-dl"
715        getName pkgs.youtube-dl
716        => "youtube-dl"
717   */
718   getName = x:
719    let
720      parse = drv: (parseDrvName drv).name;
721    in if isString x
722       then parse x
723       else x.pname or (parse x.name);
725   /* This function takes an argument that's either a derivation or a
726      derivation's "name" attribute and extracts the version part from that
727      argument.
729      Example:
730        getVersion "youtube-dl-2016.01.01"
731        => "2016.01.01"
732        getVersion pkgs.youtube-dl
733        => "2016.01.01"
734   */
735   getVersion = x:
736    let
737      parse = drv: (parseDrvName drv).version;
738    in if isString x
739       then parse x
740       else x.version or (parse x.name);
742   /* Extract name with version from URL. Ask for separator which is
743      supposed to start extension.
745      Example:
746        nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
747        => "nix"
748        nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
749        => "nix-1.7-x86"
750   */
751   nameFromURL = url: sep:
752     let
753       components = splitString "/" url;
754       filename = lib.last components;
755       name = head (splitString sep filename);
756     in assert name != filename; name;
758   /* Create a "-D<feature>:<type>=<value>" string that can be passed to typical
759      CMake invocations.
761     Type: cmakeOptionType :: string -> string -> string -> string
763      @param feature The feature to be set
764      @param type The type of the feature to be set, as described in
765                  https://cmake.org/cmake/help/latest/command/set.html
766                  the possible values (case insensitive) are:
767                  BOOL FILEPATH PATH STRING INTERNAL
768      @param value The desired value
770      Example:
771        cmakeOptionType "string" "ENGINE" "sdl2"
772        => "-DENGINE:STRING=sdl2"
773   */
774   cmakeOptionType = type: feature: value:
775     assert (lib.elem (lib.toUpper type)
776       [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" ]);
777     assert (lib.isString feature);
778     assert (lib.isString value);
779     "-D${feature}:${lib.toUpper type}=${value}";
781   /* Create a -D<condition>={TRUE,FALSE} string that can be passed to typical
782      CMake invocations.
784     Type: cmakeBool :: string -> bool -> string
786      @param condition The condition to be made true or false
787      @param flag The controlling flag of the condition
789      Example:
790        cmakeBool "ENABLE_STATIC_LIBS" false
791        => "-DENABLESTATIC_LIBS:BOOL=FALSE"
792   */
793   cmakeBool = condition: flag:
794     assert (lib.isString condition);
795     assert (lib.isBool flag);
796     cmakeOptionType "bool" condition (lib.toUpper (lib.boolToString flag));
798   /* Create a -D<feature>:STRING=<value> string that can be passed to typical
799      CMake invocations.
800      This is the most typical usage, so it deserves a special case.
802     Type: cmakeFeature :: string -> string -> string
804      @param condition The condition to be made true or false
805      @param flag The controlling flag of the condition
807      Example:
808        cmakeFeature "MODULES" "badblock"
809        => "-DMODULES:STRING=badblock"
810   */
811   cmakeFeature = feature: value:
812     assert (lib.isString feature);
813     assert (lib.isString value);
814     cmakeOptionType "string" feature value;
816   /* Create a -D<feature>=<value> string that can be passed to typical Meson
817      invocations.
819     Type: mesonOption :: string -> string -> string
821      @param feature The feature to be set
822      @param value The desired value
824      Example:
825        mesonOption "engine" "opengl"
826        => "-Dengine=opengl"
827   */
828   mesonOption = feature: value:
829     assert (lib.isString feature);
830     assert (lib.isString value);
831     "-D${feature}=${value}";
833   /* Create a -D<condition>={true,false} string that can be passed to typical
834      Meson invocations.
836     Type: mesonBool :: string -> bool -> string
838      @param condition The condition to be made true or false
839      @param flag The controlling flag of the condition
841      Example:
842        mesonBool "hardened" true
843        => "-Dhardened=true"
844        mesonBool "static" false
845        => "-Dstatic=false"
846   */
847   mesonBool = condition: flag:
848     assert (lib.isString condition);
849     assert (lib.isBool flag);
850     mesonOption condition (lib.boolToString flag);
852   /* Create a -D<feature>={enabled,disabled} string that can be passed to
853      typical Meson invocations.
855     Type: mesonEnable :: string -> bool -> string
857      @param feature The feature to be enabled or disabled
858      @param flag The controlling flag
860      Example:
861        mesonEnable "docs" true
862        => "-Ddocs=enabled"
863        mesonEnable "savage" false
864        => "-Dsavage=disabled"
865   */
866   mesonEnable = feature: flag:
867     assert (lib.isString feature);
868     assert (lib.isBool flag);
869     mesonOption feature (if flag then "enabled" else "disabled");
871   /* Create an --{enable,disable}-<feature> string that can be passed to
872      standard GNU Autoconf scripts.
874      Example:
875        enableFeature true "shared"
876        => "--enable-shared"
877        enableFeature false "shared"
878        => "--disable-shared"
879   */
880   enableFeature = flag: feature:
881     assert lib.isBool flag;
882     assert lib.isString feature; # e.g. passing openssl instead of "openssl"
883     "--${if flag then "enable" else "disable"}-${feature}";
885   /* Create an --{enable-<feature>=<value>,disable-<feature>} string that can be passed to
886      standard GNU Autoconf scripts.
888      Example:
889        enableFeatureAs true "shared" "foo"
890        => "--enable-shared=foo"
891        enableFeatureAs false "shared" (throw "ignored")
892        => "--disable-shared"
893   */
894   enableFeatureAs = flag: feature: value:
895     enableFeature flag feature + optionalString flag "=${value}";
897   /* Create an --{with,without}-<feature> string that can be passed to
898      standard GNU Autoconf scripts.
900      Example:
901        withFeature true "shared"
902        => "--with-shared"
903        withFeature false "shared"
904        => "--without-shared"
905   */
906   withFeature = flag: feature:
907     assert isString feature; # e.g. passing openssl instead of "openssl"
908     "--${if flag then "with" else "without"}-${feature}";
910   /* Create an --{with-<feature>=<value>,without-<feature>} string that can be passed to
911      standard GNU Autoconf scripts.
913      Example:
914        withFeatureAs true "shared" "foo"
915        => "--with-shared=foo"
916        withFeatureAs false "shared" (throw "ignored")
917        => "--without-shared"
918   */
919   withFeatureAs = flag: feature: value:
920     withFeature flag feature + optionalString flag "=${value}";
922   /* Create a fixed width string with additional prefix to match
923      required width.
925      This function will fail if the input string is longer than the
926      requested length.
928      Type: fixedWidthString :: int -> string -> string -> string
930      Example:
931        fixedWidthString 5 "0" (toString 15)
932        => "00015"
933   */
934   fixedWidthString = width: filler: str:
935     let
936       strw = lib.stringLength str;
937       reqWidth = width - (lib.stringLength filler);
938     in
939       assert lib.assertMsg (strw <= width)
940         "fixedWidthString: requested string length (${
941           toString width}) must not be shorter than actual length (${
942             toString strw})";
943       if strw == width then str else filler + fixedWidthString reqWidth filler str;
945   /* Format a number adding leading zeroes up to fixed width.
947      Example:
948        fixedWidthNumber 5 15
949        => "00015"
950   */
951   fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
953   /* Convert a float to a string, but emit a warning when precision is lost
954      during the conversion
956      Example:
957        floatToString 0.000001
958        => "0.000001"
959        floatToString 0.0000001
960        => trace: warning: Imprecise conversion from float to string 0.000000
961           "0.000000"
962   */
963   floatToString = float: let
964     result = toString float;
965     precise = float == fromJSON result;
966   in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}"
967     result;
969   /* Soft-deprecated function. While the original implementation is available as
970      isConvertibleWithToString, consider using isStringLike instead, if suitable. */
971   isCoercibleToString = lib.warnIf (lib.isInOldestRelease 2305)
972     "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."
973     isConvertibleWithToString;
975   /* Check whether a list or other value can be passed to toString.
977      Many types of value are coercible to string this way, including int, float,
978      null, bool, list of similarly coercible values.
979   */
980   isConvertibleWithToString = x:
981     isStringLike x ||
982     elem (typeOf x) [ "null" "int" "float" "bool" ] ||
983     (isList x && lib.all isConvertibleWithToString x);
985   /* Check whether a value can be coerced to a string.
986      The value must be a string, path, or attribute set.
988      String-like values can be used without explicit conversion in
989      string interpolations and in most functions that expect a string.
990    */
991   isStringLike = x:
992     isString x ||
993     isPath x ||
994     x ? outPath ||
995     x ? __toString;
997   /* Check whether a value is a store path.
999      Example:
1000        isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
1001        => false
1002        isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
1003        => true
1004        isStorePath pkgs.python
1005        => true
1006        isStorePath [] || isStorePath 42 || isStorePath {} || â€¦
1007        => false
1008   */
1009   isStorePath = x:
1010     if isStringLike x then
1011       let str = toString x; in
1012       substring 0 1 str == "/"
1013       && dirOf str == storeDir
1014     else
1015       false;
1017   /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
1018   ambiguity between zero-padded and octal numbers. See toIntBase10.
1020      Type: string -> int
1022      Example:
1024        toInt "1337"
1025        => 1337
1027        toInt "-4"
1028        => -4
1030        toInt " 123 "
1031        => 123
1033        toInt "00024"
1034        => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
1036        toInt "3.14"
1037        => error: floating point JSON numbers are not supported
1038   */
1039   toInt = str:
1040     let
1041       # RegEx: Match any leading whitespace, possibly a '-', one or more digits,
1042       # and finally match any trailing whitespace.
1043       strippedInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*" str;
1045       # RegEx: Match a leading '0' then one or more digits.
1046       isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == [];
1048       # Attempt to parse input
1049       parsedInput = fromJSON (head strippedInput);
1051       generalError = "toInt: Could not convert ${escapeNixString str} to int.";
1053       octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}"
1054       + " between octal and zero padded integer.";
1056     in
1057       # Error on presence of non digit characters.
1058       if strippedInput == null
1059       then throw generalError
1060       # Error on presence of leading zero/octal ambiguity.
1061       else if isLeadingZero
1062       then throw octalAmbigError
1063       # Error if parse function fails.
1064       else if !isInt parsedInput
1065       then throw generalError
1066       # Return result.
1067       else parsedInput;
1070   /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
1072      Type: string -> int
1074      Example:
1075        toIntBase10 "1337"
1076        => 1337
1078        toIntBase10 "-4"
1079        => -4
1081        toIntBase10 " 123 "
1082        => 123
1084        toIntBase10 "00024"
1085        => 24
1087        toIntBase10 "3.14"
1088        => error: floating point JSON numbers are not supported
1089   */
1090   toIntBase10 = str:
1091     let
1092       # RegEx: Match any leading whitespace, then match any zero padding,
1093       # capture possibly a '-' followed by one or more digits,
1094       # and finally match any trailing whitespace.
1095       strippedInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*" str;
1097       # RegEx: Match at least one '0'.
1098       isZero = match "0+" (head strippedInput) == [];
1100       # Attempt to parse input
1101       parsedInput = fromJSON (head strippedInput);
1103       generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
1105     in
1106       # Error on presence of non digit characters.
1107       if strippedInput == null
1108       then throw generalError
1109       # In the special case zero-padded zero (00000), return early.
1110       else if isZero
1111       then 0
1112       # Error if parse function fails.
1113       else if !isInt parsedInput
1114       then throw generalError
1115       # Return result.
1116       else parsedInput;
1118   /* Read a list of paths from `file`, relative to the `rootPath`.
1119      Lines beginning with `#` are treated as comments and ignored.
1120      Whitespace is significant.
1122      NOTE: This function is not performant and should be avoided.
1124      Example:
1125        readPathsFromFile /prefix
1126          ./pkgs/development/libraries/qt-5/5.4/qtbase/series
1127        => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
1128             "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
1129             "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
1130             "/prefix/nix-profiles-library-paths.patch"
1131             "/prefix/compose-search-path.patch" ]
1132   */
1133   readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead"
1134     (rootPath: file:
1135       let
1136         lines = lib.splitString "\n" (readFile file);
1137         removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
1138         relativePaths = removeComments lines;
1139         absolutePaths = map (path: rootPath + "/${path}") relativePaths;
1140       in
1141         absolutePaths);
1143   /* Read the contents of a file removing the trailing \n
1145      Type: fileContents :: path -> string
1147      Example:
1148        $ echo "1.0" > ./version
1150        fileContents ./version
1151        => "1.0"
1152   */
1153   fileContents = file: removeSuffix "\n" (readFile file);
1156   /* Creates a valid derivation name from a potentially invalid one.
1158      Type: sanitizeDerivationName :: String -> String
1160      Example:
1161        sanitizeDerivationName "../hello.bar # foo"
1162        => "-hello.bar-foo"
1163        sanitizeDerivationName ""
1164        => "unknown"
1165        sanitizeDerivationName pkgs.hello
1166        => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
1167   */
1168   sanitizeDerivationName =
1169   let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
1170   in
1171   string:
1172   # First detect the common case of already valid strings, to speed those up
1173   if stringLength string <= 207 && okRegex string != null
1174   then unsafeDiscardStringContext string
1175   else lib.pipe string [
1176     # Get rid of string context. This is safe under the assumption that the
1177     # resulting string is only used as a derivation name
1178     unsafeDiscardStringContext
1179     # Strip all leading "."
1180     (x: elemAt (match "\\.*(.*)" x) 0)
1181     # Split out all invalid characters
1182     # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
1183     # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
1184     (split "[^[:alnum:]+._?=-]+")
1185     # Replace invalid character ranges with a "-"
1186     (concatMapStrings (s: if lib.isList s then "-" else s))
1187     # Limit to 211 characters (minus 4 chars for ".drv")
1188     (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
1189     # If the result is empty, replace it with "unknown"
1190     (x: if stringLength x == 0 then "unknown" else x)
1191   ];
1193   /* Computes the Levenshtein distance between two strings.
1194      Complexity O(n*m) where n and m are the lengths of the strings.
1195      Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
1197      Type: levenshtein :: string -> string -> int
1199      Example:
1200        levenshtein "foo" "foo"
1201        => 0
1202        levenshtein "book" "hook"
1203        => 1
1204        levenshtein "hello" "Heyo"
1205        => 3
1206   */
1207   levenshtein = a: b: let
1208     # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
1209     arr = lib.genList (i:
1210       lib.genList (j:
1211         dist i j
1212       ) (stringLength b + 1)
1213     ) (stringLength a + 1);
1214     d = x: y: lib.elemAt (lib.elemAt arr x) y;
1215     dist = i: j:
1216       let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
1217         then 0 else 1;
1218       in
1219       if j == 0 then i
1220       else if i == 0 then j
1221       else lib.min
1222         ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1))
1223         ( d (i - 1) (j - 1) + c );
1224   in d (stringLength a) (stringLength b);
1226   /* Returns the length of the prefix common to both strings.
1227   */
1228   commonPrefixLength = a: b:
1229     let
1230       m = lib.min (stringLength a) (stringLength b);
1231       go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
1232     in go 0;
1234   /* Returns the length of the suffix common to both strings.
1235   */
1236   commonSuffixLength = a: b:
1237     let
1238       m = lib.min (stringLength a) (stringLength b);
1239       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;
1240     in go 0;
1242   /* Returns whether the levenshtein distance between two strings is at most some value
1243      Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
1245      Type: levenshteinAtMost :: int -> string -> string -> bool
1247      Example:
1248        levenshteinAtMost 0 "foo" "foo"
1249        => true
1250        levenshteinAtMost 1 "foo" "boa"
1251        => false
1252        levenshteinAtMost 2 "foo" "boa"
1253        => true
1254        levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1255        => false
1256        levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1257        => true
1259   */
1260   levenshteinAtMost = let
1261     infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1263     # This function takes two strings stripped by their common pre and suffix,
1264     # and returns whether they differ by at most two by Levenshtein distance.
1265     # Because of this stripping, if they do indeed differ by at most two edits,
1266     # we know that those edits were (if at all) done at the start or the end,
1267     # while the middle has to have stayed the same. This fact is used in the
1268     # implementation.
1269     infixDifferAtMost2 = x: y:
1270       let
1271         xlen = stringLength x;
1272         ylen = stringLength y;
1273         # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
1274         # diff is one of 0, 1 or 2
1275         diff = xlen - ylen;
1277         # Infix of x and y, stripped by the left and right most character
1278         xinfix = substring 1 (xlen - 2) x;
1279         yinfix = substring 1 (ylen - 2) y;
1281         # x and y but a character deleted at the left or right
1282         xdelr = substring 0 (xlen - 1) x;
1283         xdell = substring 1 (xlen - 1) x;
1284         ydelr = substring 0 (ylen - 1) y;
1285         ydell = substring 1 (ylen - 1) y;
1286       in
1287         # A length difference of 2 can only be gotten with 2 delete edits,
1288         # which have to have happened at the start and end of x
1289         # Example: "abcdef" -> "bcde"
1290         if diff == 2 then xinfix == y
1291         # A length difference of 1 can only be gotten with a deletion on the
1292         # right and a replacement on the left or vice versa.
1293         # Example: "abcdef" -> "bcdez" or "zbcde"
1294         else if diff == 1 then xinfix == ydelr || xinfix == ydell
1295         # No length difference can either happen through replacements on both
1296         # sides, or a deletion on the left and an insertion on the right or
1297         # vice versa
1298         # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1299         else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1301     in k: if k <= 0 then a: b: a == b else
1302       let f = a: b:
1303         let
1304           alen = stringLength a;
1305           blen = stringLength b;
1306           prelen = commonPrefixLength a b;
1307           suflen = commonSuffixLength a b;
1308           presuflen = prelen + suflen;
1309           ainfix = substring prelen (alen - presuflen) a;
1310           binfix = substring prelen (blen - presuflen) b;
1311         in
1312         # Make a be the bigger string
1313         if alen < blen then f b a
1314         # If a has over k more characters than b, even with k deletes on a, b can't be reached
1315         else if alen - blen > k then false
1316         else if k == 1 then infixDifferAtMost1 ainfix binfix
1317         else if k == 2 then infixDifferAtMost2 ainfix binfix
1318         else levenshtein ainfix binfix <= k;
1319       in f;