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