Merge pull request #298967 from vbgl/ocaml-5.2.0
[NixPkgs.git] / lib / strings.nix
blob32efc9bdb70e9e563e7b3d6fc2708180041e34c1
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 a binary search path (such as $PATH) containing the
210      binaries for a set of packages.
212      Example:
213        makeBinPath ["/root" "/usr" "/usr/local"]
214        => "/root/bin:/usr/bin:/usr/local/bin"
215   */
216   makeBinPath = makeSearchPathOutput "bin" "bin";
218   /* Normalize path, removing extraneous /s
220      Type: normalizePath :: string -> string
222      Example:
223        normalizePath "/a//b///c/"
224        => "/a/b/c/"
225   */
226   normalizePath = s:
227     warnIf
228       (isPath s)
229       ''
230         lib.strings.normalizePath: The argument (${toString s}) is a path value, but only strings are supported.
231             Path values are always normalised in Nix, so there's no need to call this function on them.
232             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.
233             This behavior is deprecated and will throw an error in the future.''
234       (
235         builtins.foldl'
236           (x: y: if y == "/" && hasSuffix "/" x then x else x+y)
237           ""
238           (stringToCharacters s)
239       );
241   /* Depending on the boolean `cond', return either the given string
242      or the empty string. Useful to concatenate against a bigger string.
244      Type: optionalString :: bool -> string -> string
246      Example:
247        optionalString true "some-string"
248        => "some-string"
249        optionalString false "some-string"
250        => ""
251   */
252   optionalString =
253     # Condition
254     cond:
255     # String to return if condition is true
256     string: if cond then string else "";
258   /* Determine whether a string has given prefix.
260      Type: hasPrefix :: string -> string -> bool
262      Example:
263        hasPrefix "foo" "foobar"
264        => true
265        hasPrefix "foo" "barfoo"
266        => false
267   */
268   hasPrefix =
269     # Prefix to check for
270     pref:
271     # Input string
272     str:
273     # Before 23.05, paths would be copied to the store before converting them
274     # to strings and comparing. This was surprising and confusing.
275     warnIf
276       (isPath pref)
277       ''
278         lib.strings.hasPrefix: The first argument (${toString pref}) is a path value, but only strings are supported.
279             There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
280             This function also copies the path to the Nix store, which may not be what you want.
281             This behavior is deprecated and will throw an error in the future.
282             You might want to use `lib.path.hasPrefix` instead, which correctly supports paths.''
283       (substring 0 (stringLength pref) str == pref);
285   /* Determine whether a string has given suffix.
287      Type: hasSuffix :: string -> string -> bool
289      Example:
290        hasSuffix "foo" "foobar"
291        => false
292        hasSuffix "foo" "barfoo"
293        => true
294   */
295   hasSuffix =
296     # Suffix to check for
297     suffix:
298     # Input string
299     content:
300     let
301       lenContent = stringLength content;
302       lenSuffix = stringLength suffix;
303     in
304     # Before 23.05, paths would be copied to the store before converting them
305     # to strings and comparing. This was surprising and confusing.
306     warnIf
307       (isPath suffix)
308       ''
309         lib.strings.hasSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
310             There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
311             This function also copies the path to the Nix store, which may not be what you want.
312             This behavior is deprecated and will throw an error in the future.''
313       (
314         lenContent >= lenSuffix
315         && substring (lenContent - lenSuffix) lenContent content == suffix
316       );
318   /* Determine whether a string contains the given infix
320     Type: hasInfix :: string -> string -> bool
322     Example:
323       hasInfix "bc" "abcd"
324       => true
325       hasInfix "ab" "abcd"
326       => true
327       hasInfix "cd" "abcd"
328       => true
329       hasInfix "foo" "abcd"
330       => false
331   */
332   hasInfix = infix: content:
333     # Before 23.05, paths would be copied to the store before converting them
334     # to strings and comparing. This was surprising and confusing.
335     warnIf
336       (isPath infix)
337       ''
338         lib.strings.hasInfix: The first argument (${toString infix}) is a path value, but only strings are supported.
339             There is almost certainly a bug in the calling code, since this function always returns `false` in such a case.
340             This function also copies the path to the Nix store, which may not be what you want.
341             This behavior is deprecated and will throw an error in the future.''
342       (builtins.match ".*${escapeRegex infix}.*" "${content}" != null);
344   /* Convert a string to a list of characters (i.e. singleton strings).
345      This allows you to, e.g., map a function over each character.  However,
346      note that this will likely be horribly inefficient; Nix is not a
347      general purpose programming language. Complex string manipulations
348      should, if appropriate, be done in a derivation.
349      Also note that Nix treats strings as a list of bytes and thus doesn't
350      handle unicode.
352      Type: stringToCharacters :: string -> [string]
354      Example:
355        stringToCharacters ""
356        => [ ]
357        stringToCharacters "abc"
358        => [ "a" "b" "c" ]
359        stringToCharacters "🦄"
360        => [ "�" "�" "�" "�" ]
361   */
362   stringToCharacters = s:
363     genList (p: substring p 1 s) (stringLength s);
365   /* Manipulate a string character by character and replace them by
366      strings before concatenating the results.
368      Type: stringAsChars :: (string -> string) -> string -> string
370      Example:
371        stringAsChars (x: if x == "a" then "i" else x) "nax"
372        => "nix"
373   */
374   stringAsChars =
375     # Function to map over each individual character
376     f:
377     # Input string
378     s: concatStrings (
379       map f (stringToCharacters s)
380     );
382   /* Convert char to ascii value, must be in printable range
384      Type: charToInt :: string -> int
386      Example:
387        charToInt "A"
388        => 65
389        charToInt "("
390        => 40
392   */
393   charToInt = c: builtins.getAttr c asciiTable;
395   /* Escape occurrence of the elements of `list` in `string` by
396      prefixing it with a backslash.
398      Type: escape :: [string] -> string -> string
400      Example:
401        escape ["(" ")"] "(foo)"
402        => "\\(foo\\)"
403   */
404   escape = list: replaceStrings list (map (c: "\\${c}") list);
406   /* Escape occurrence of the element of `list` in `string` by
407      converting to its ASCII value and prefixing it with \\x.
408      Only works for printable ascii characters.
410      Type: escapeC = [string] -> string -> string
412      Example:
413        escapeC [" "] "foo bar"
414        => "foo\\x20bar"
416   */
417   escapeC = list: replaceStrings list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
419   /* Escape the string so it can be safely placed inside a URL
420      query.
422      Type: escapeURL :: string -> string
424      Example:
425        escapeURL "foo/bar baz"
426        => "foo%2Fbar%20baz"
427   */
428   escapeURL = let
429     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" "-" "_" "." "~" ];
430     toEscape = builtins.removeAttrs asciiTable unreserved;
431   in
432     replaceStrings (builtins.attrNames toEscape) (lib.mapAttrsToList (_: c: "%${fixedWidthString 2 "0" (lib.toHexString c)}") toEscape);
434   /* Quote string to be used safely within the Bourne shell.
436      Type: escapeShellArg :: string -> string
438      Example:
439        escapeShellArg "esc'ape\nme"
440        => "'esc'\\''ape\nme'"
441   */
442   escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
444   /* Quote all arguments to be safely passed to the Bourne shell.
446      Type: escapeShellArgs :: [string] -> string
448      Example:
449        escapeShellArgs ["one" "two three" "four'five"]
450        => "'one' 'two three' 'four'\\''five'"
451   */
452   escapeShellArgs = concatMapStringsSep " " escapeShellArg;
454   /* Test whether the given name is a valid POSIX shell variable name.
456      Type: string -> bool
458      Example:
459        isValidPosixName "foo_bar000"
460        => true
461        isValidPosixName "0-bad.jpg"
462        => false
463   */
464   isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
466   /* Translate a Nix value into a shell variable declaration, with proper escaping.
468      The value can be a string (mapped to a regular variable), a list of strings
469      (mapped to a Bash-style array) or an attribute set of strings (mapped to a
470      Bash-style associative array). Note that "string" includes string-coercible
471      values like paths or derivations.
473      Strings are translated into POSIX sh-compatible code; lists and attribute sets
474      assume a shell that understands Bash syntax (e.g. Bash or ZSH).
476      Type: string -> (string | listOf string | attrsOf string) -> string
478      Example:
479        ''
480          ${toShellVar "foo" "some string"}
481          [[ "$foo" == "some string" ]]
482        ''
483   */
484   toShellVar = name: value:
485     lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
486     if isAttrs value && ! isStringLike value then
487       "declare -A ${name}=(${
488         concatStringsSep " " (lib.mapAttrsToList (n: v:
489           "[${escapeShellArg n}]=${escapeShellArg v}"
490         ) value)
491       })"
492     else if isList value then
493       "declare -a ${name}=(${escapeShellArgs value})"
494     else
495       "${name}=${escapeShellArg value}"
496     );
498   /* Translate an attribute set into corresponding shell variable declarations
499      using `toShellVar`.
501      Type: attrsOf (string | listOf string | attrsOf string) -> string
503      Example:
504        let
505          foo = "value";
506          bar = foo;
507        in ''
508          ${toShellVars { inherit foo bar; }}
509          [[ "$foo" == "$bar" ]]
510        ''
511   */
512   toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
514   /* Turn a string into a Nix expression representing that string
516      Type: string -> string
518      Example:
519        escapeNixString "hello\${}\n"
520        => "\"hello\\\${}\\n\""
521   */
522   escapeNixString = s: escape ["$"] (toJSON s);
524   /* Turn a string into an exact regular expression
526      Type: string -> string
528      Example:
529        escapeRegex "[^a-z]*"
530        => "\\[\\^a-z]\\*"
531   */
532   escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
534   /* Quotes a string if it can't be used as an identifier directly.
536      Type: string -> string
538      Example:
539        escapeNixIdentifier "hello"
540        => "hello"
541        escapeNixIdentifier "0abc"
542        => "\"0abc\""
543   */
544   escapeNixIdentifier = s:
545     # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
546     if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
547     then s else escapeNixString s;
549   /* Escapes a string such that it is safe to include verbatim in an XML
550      document.
552      Type: string -> string
554      Example:
555        escapeXML ''"test" 'test' < & >''
556        => "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;"
557   */
558   escapeXML = builtins.replaceStrings
559     ["\"" "'" "<" ">" "&"]
560     ["&quot;" "&apos;" "&lt;" "&gt;" "&amp;"];
562   # warning added 12-12-2022
563   replaceChars = lib.warn "lib.replaceChars is a deprecated alias of lib.replaceStrings." builtins.replaceStrings;
565   # Case conversion utilities.
566   lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
567   upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
569   /* Converts an ASCII string to lower-case.
571      Type: toLower :: string -> string
573      Example:
574        toLower "HOME"
575        => "home"
576   */
577   toLower = replaceStrings upperChars lowerChars;
579   /* Converts an ASCII string to upper-case.
581      Type: toUpper :: string -> string
583      Example:
584        toUpper "home"
585        => "HOME"
586   */
587   toUpper = replaceStrings lowerChars upperChars;
589   /* Appends string context from another string.  This is an implementation
590      detail of Nix and should be used carefully.
592      Strings in Nix carry an invisible `context` which is a list of strings
593      representing store paths.  If the string is later used in a derivation
594      attribute, the derivation will properly populate the inputDrvs and
595      inputSrcs.
597      Example:
598        pkgs = import <nixpkgs> { };
599        addContextFrom pkgs.coreutils "bar"
600        => "bar"
601   */
602   addContextFrom = a: b: substring 0 0 a + b;
604   /* Cut a string with a separator and produces a list of strings which
605      were separated by this separator.
607      Example:
608        splitString "." "foo.bar.baz"
609        => [ "foo" "bar" "baz" ]
610        splitString "/" "/usr/local/bin"
611        => [ "" "usr" "local" "bin" ]
612   */
613   splitString = sep: s:
614     let
615       splits = builtins.filter builtins.isString (builtins.split (escapeRegex (toString sep)) (toString s));
616     in
617       map (addContextFrom s) splits;
619   /* Return a string without the specified prefix, if the prefix matches.
621      Type: string -> string -> string
623      Example:
624        removePrefix "foo." "foo.bar.baz"
625        => "bar.baz"
626        removePrefix "xxx" "foo.bar.baz"
627        => "foo.bar.baz"
628   */
629   removePrefix =
630     # Prefix to remove if it matches
631     prefix:
632     # Input string
633     str:
634     # Before 23.05, paths would be copied to the store before converting them
635     # to strings and comparing. This was surprising and confusing.
636     warnIf
637       (isPath prefix)
638       ''
639         lib.strings.removePrefix: The first argument (${toString prefix}) is a path value, but only strings are supported.
640             There is almost certainly a bug in the calling code, since this function never removes any prefix in such a case.
641             This function also copies the path to the Nix store, which may not be what you want.
642             This behavior is deprecated and will throw an error in the future.''
643     (let
644       preLen = stringLength prefix;
645     in
646       if substring 0 preLen str == prefix then
647         # -1 will take the string until the end
648         substring preLen (-1) str
649       else
650         str);
652   /* Return a string without the specified suffix, if the suffix matches.
654      Type: string -> string -> string
656      Example:
657        removeSuffix "front" "homefront"
658        => "home"
659        removeSuffix "xxx" "homefront"
660        => "homefront"
661   */
662   removeSuffix =
663     # Suffix to remove if it matches
664     suffix:
665     # Input string
666     str:
667     # Before 23.05, paths would be copied to the store before converting them
668     # to strings and comparing. This was surprising and confusing.
669     warnIf
670       (isPath suffix)
671       ''
672         lib.strings.removeSuffix: The first argument (${toString suffix}) is a path value, but only strings are supported.
673             There is almost certainly a bug in the calling code, since this function never removes any suffix in such a case.
674             This function also copies the path to the Nix store, which may not be what you want.
675             This behavior is deprecated and will throw an error in the future.''
676     (let
677       sufLen = stringLength suffix;
678       sLen = stringLength str;
679     in
680       if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
681         substring 0 (sLen - sufLen) str
682       else
683         str);
685   /* Return true if string v1 denotes a version older than v2.
687      Example:
688        versionOlder "1.1" "1.2"
689        => true
690        versionOlder "1.1" "1.1"
691        => false
692   */
693   versionOlder = v1: v2: compareVersions v2 v1 == 1;
695   /* Return true if string v1 denotes a version equal to or newer than v2.
697      Example:
698        versionAtLeast "1.1" "1.0"
699        => true
700        versionAtLeast "1.1" "1.1"
701        => true
702        versionAtLeast "1.1" "1.2"
703        => false
704   */
705   versionAtLeast = v1: v2: !versionOlder v1 v2;
707   /* This function takes an argument that's either a derivation or a
708      derivation's "name" attribute and extracts the name part from that
709      argument.
711      Example:
712        getName "youtube-dl-2016.01.01"
713        => "youtube-dl"
714        getName pkgs.youtube-dl
715        => "youtube-dl"
716   */
717   getName = let
718     parse = drv: (parseDrvName drv).name;
719   in x:
720     if isString x
721     then parse x
722     else x.pname or (parse x.name);
724   /* This function takes an argument that's either a derivation or a
725      derivation's "name" attribute and extracts the version part from that
726      argument.
728      Example:
729        getVersion "youtube-dl-2016.01.01"
730        => "2016.01.01"
731        getVersion pkgs.youtube-dl
732        => "2016.01.01"
733   */
734   getVersion = let
735     parse = drv: (parseDrvName drv).version;
736   in x:
737     if isString x
738     then parse x
739     else x.version or (parse x.name);
741   /* Extract name with version from URL. Ask for separator which is
742      supposed to start extension.
744      Example:
745        nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
746        => "nix"
747        nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
748        => "nix-1.7-x86"
749   */
750   nameFromURL = url: sep:
751     let
752       components = splitString "/" url;
753       filename = lib.last components;
754       name = head (splitString sep filename);
755     in assert name != filename; name;
757   /* Create a "-D<feature>:<type>=<value>" string that can be passed to typical
758      CMake invocations.
760     Type: cmakeOptionType :: string -> string -> string -> string
762      @param feature The feature to be set
763      @param type The type of the feature to be set, as described in
764                  https://cmake.org/cmake/help/latest/command/set.html
765                  the possible values (case insensitive) are:
766                  BOOL FILEPATH PATH STRING INTERNAL
767      @param value The desired value
769      Example:
770        cmakeOptionType "string" "ENGINE" "sdl2"
771        => "-DENGINE:STRING=sdl2"
772   */
773   cmakeOptionType = let
774     types = [ "BOOL" "FILEPATH" "PATH" "STRING" "INTERNAL" ];
775   in type: feature: value:
776     assert (elem (toUpper type) types);
777     assert (isString feature);
778     assert (isString value);
779     "-D${feature}:${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 = let
981     types = [ "null" "int" "float" "bool" ];
982   in x:
983     isStringLike x ||
984     elem (typeOf x) types ||
985     (isList x && lib.all isConvertibleWithToString x);
987   /* Check whether a value can be coerced to a string.
988      The value must be a string, path, or attribute set.
990      String-like values can be used without explicit conversion in
991      string interpolations and in most functions that expect a string.
992    */
993   isStringLike = x:
994     isString x ||
995     isPath x ||
996     x ? outPath ||
997     x ? __toString;
999   /* Check whether a value is a store path.
1001      Example:
1002        isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
1003        => false
1004        isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
1005        => true
1006        isStorePath pkgs.python
1007        => true
1008        isStorePath [] || isStorePath 42 || isStorePath {} || â€¦
1009        => false
1010   */
1011   isStorePath = x:
1012     if isStringLike x then
1013       let str = toString x; in
1014       substring 0 1 str == "/"
1015       && dirOf str == storeDir
1016     else
1017       false;
1019   /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
1020   ambiguity between zero-padded and octal numbers. See toIntBase10.
1022      Type: string -> int
1024      Example:
1026        toInt "1337"
1027        => 1337
1029        toInt "-4"
1030        => -4
1032        toInt " 123 "
1033        => 123
1035        toInt "00024"
1036        => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
1038        toInt "3.14"
1039        => error: floating point JSON numbers are not supported
1040   */
1041   toInt =
1042     let
1043       matchStripInput = match "[[:space:]]*(-?[[:digit:]]+)[[:space:]]*";
1044       matchLeadingZero = match "0[[:digit:]]+";
1045     in
1046     str:
1047     let
1048       # RegEx: Match any leading whitespace, possibly a '-', one or more digits,
1049       # and finally match any trailing whitespace.
1050       strippedInput = matchStripInput str;
1052       # RegEx: Match a leading '0' then one or more digits.
1053       isLeadingZero = matchLeadingZero (head strippedInput) == [];
1055       # Attempt to parse input
1056       parsedInput = fromJSON (head strippedInput);
1058       generalError = "toInt: Could not convert ${escapeNixString str} to int.";
1060     in
1061       # Error on presence of non digit characters.
1062       if strippedInput == null
1063       then throw generalError
1064       # Error on presence of leading zero/octal ambiguity.
1065       else if isLeadingZero
1066       then throw "toInt: Ambiguity in interpretation of ${escapeNixString str} between octal and zero padded integer."
1067       # Error if parse function fails.
1068       else if !isInt parsedInput
1069       then throw generalError
1070       # Return result.
1071       else parsedInput;
1074   /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
1076      Type: string -> int
1078      Example:
1079        toIntBase10 "1337"
1080        => 1337
1082        toIntBase10 "-4"
1083        => -4
1085        toIntBase10 " 123 "
1086        => 123
1088        toIntBase10 "00024"
1089        => 24
1091        toIntBase10 "3.14"
1092        => error: floating point JSON numbers are not supported
1093   */
1094   toIntBase10 =
1095     let
1096       matchStripInput = match "[[:space:]]*0*(-?[[:digit:]]+)[[:space:]]*";
1097       matchZero = match "0+";
1098     in
1099     str:
1100     let
1101       # RegEx: Match any leading whitespace, then match any zero padding,
1102       # capture possibly a '-' followed by one or more digits,
1103       # and finally match any trailing whitespace.
1104       strippedInput = matchStripInput str;
1106       # RegEx: Match at least one '0'.
1107       isZero = matchZero (head strippedInput) == [];
1109       # Attempt to parse input
1110       parsedInput = fromJSON (head strippedInput);
1112       generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
1114     in
1115       # Error on presence of non digit characters.
1116       if strippedInput == null
1117       then throw generalError
1118       # In the special case zero-padded zero (00000), return early.
1119       else if isZero
1120       then 0
1121       # Error if parse function fails.
1122       else if !isInt parsedInput
1123       then throw generalError
1124       # Return result.
1125       else parsedInput;
1127   /* Read a list of paths from `file`, relative to the `rootPath`.
1128      Lines beginning with `#` are treated as comments and ignored.
1129      Whitespace is significant.
1131      NOTE: This function is not performant and should be avoided.
1133      Example:
1134        readPathsFromFile /prefix
1135          ./pkgs/development/libraries/qt-5/5.4/qtbase/series
1136        => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
1137             "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
1138             "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
1139             "/prefix/nix-profiles-library-paths.patch"
1140             "/prefix/compose-search-path.patch" ]
1141   */
1142   readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead."
1143     (rootPath: file:
1144       let
1145         lines = lib.splitString "\n" (readFile file);
1146         removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
1147         relativePaths = removeComments lines;
1148         absolutePaths = map (path: rootPath + "/${path}") relativePaths;
1149       in
1150         absolutePaths);
1152   /* Read the contents of a file removing the trailing \n
1154      Type: fileContents :: path -> string
1156      Example:
1157        $ echo "1.0" > ./version
1159        fileContents ./version
1160        => "1.0"
1161   */
1162   fileContents = file: removeSuffix "\n" (readFile file);
1165   /* Creates a valid derivation name from a potentially invalid one.
1167      Type: sanitizeDerivationName :: String -> String
1169      Example:
1170        sanitizeDerivationName "../hello.bar # foo"
1171        => "-hello.bar-foo"
1172        sanitizeDerivationName ""
1173        => "unknown"
1174        sanitizeDerivationName pkgs.hello
1175        => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
1176   */
1177   sanitizeDerivationName =
1178   let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
1179   in
1180   string:
1181   # First detect the common case of already valid strings, to speed those up
1182   if stringLength string <= 207 && okRegex string != null
1183   then unsafeDiscardStringContext string
1184   else lib.pipe string [
1185     # Get rid of string context. This is safe under the assumption that the
1186     # resulting string is only used as a derivation name
1187     unsafeDiscardStringContext
1188     # Strip all leading "."
1189     (x: elemAt (match "\\.*(.*)" x) 0)
1190     # Split out all invalid characters
1191     # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
1192     # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
1193     (split "[^[:alnum:]+._?=-]+")
1194     # Replace invalid character ranges with a "-"
1195     (concatMapStrings (s: if lib.isList s then "-" else s))
1196     # Limit to 211 characters (minus 4 chars for ".drv")
1197     (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
1198     # If the result is empty, replace it with "unknown"
1199     (x: if stringLength x == 0 then "unknown" else x)
1200   ];
1202   /* Computes the Levenshtein distance between two strings.
1203      Complexity O(n*m) where n and m are the lengths of the strings.
1204      Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
1206      Type: levenshtein :: string -> string -> int
1208      Example:
1209        levenshtein "foo" "foo"
1210        => 0
1211        levenshtein "book" "hook"
1212        => 1
1213        levenshtein "hello" "Heyo"
1214        => 3
1215   */
1216   levenshtein = a: b: let
1217     # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
1218     arr = lib.genList (i:
1219       lib.genList (j:
1220         dist i j
1221       ) (stringLength b + 1)
1222     ) (stringLength a + 1);
1223     d = x: y: lib.elemAt (lib.elemAt arr x) y;
1224     dist = i: j:
1225       let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
1226         then 0 else 1;
1227       in
1228       if j == 0 then i
1229       else if i == 0 then j
1230       else lib.min
1231         ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1))
1232         ( d (i - 1) (j - 1) + c );
1233   in d (stringLength a) (stringLength b);
1235   /* Returns the length of the prefix common to both strings.
1236   */
1237   commonPrefixLength = a: b:
1238     let
1239       m = lib.min (stringLength a) (stringLength b);
1240       go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
1241     in go 0;
1243   /* Returns the length of the suffix common to both strings.
1244   */
1245   commonSuffixLength = a: b:
1246     let
1247       m = lib.min (stringLength a) (stringLength b);
1248       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;
1249     in go 0;
1251   /* Returns whether the levenshtein distance between two strings is at most some value
1252      Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
1254      Type: levenshteinAtMost :: int -> string -> string -> bool
1256      Example:
1257        levenshteinAtMost 0 "foo" "foo"
1258        => true
1259        levenshteinAtMost 1 "foo" "boa"
1260        => false
1261        levenshteinAtMost 2 "foo" "boa"
1262        => true
1263        levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1264        => false
1265        levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1266        => true
1268   */
1269   levenshteinAtMost = let
1270     infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1272     # This function takes two strings stripped by their common pre and suffix,
1273     # and returns whether they differ by at most two by Levenshtein distance.
1274     # Because of this stripping, if they do indeed differ by at most two edits,
1275     # we know that those edits were (if at all) done at the start or the end,
1276     # while the middle has to have stayed the same. This fact is used in the
1277     # implementation.
1278     infixDifferAtMost2 = x: y:
1279       let
1280         xlen = stringLength x;
1281         ylen = stringLength y;
1282         # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
1283         # diff is one of 0, 1 or 2
1284         diff = xlen - ylen;
1286         # Infix of x and y, stripped by the left and right most character
1287         xinfix = substring 1 (xlen - 2) x;
1288         yinfix = substring 1 (ylen - 2) y;
1290         # x and y but a character deleted at the left or right
1291         xdelr = substring 0 (xlen - 1) x;
1292         xdell = substring 1 (xlen - 1) x;
1293         ydelr = substring 0 (ylen - 1) y;
1294         ydell = substring 1 (ylen - 1) y;
1295       in
1296         # A length difference of 2 can only be gotten with 2 delete edits,
1297         # which have to have happened at the start and end of x
1298         # Example: "abcdef" -> "bcde"
1299         if diff == 2 then xinfix == y
1300         # A length difference of 1 can only be gotten with a deletion on the
1301         # right and a replacement on the left or vice versa.
1302         # Example: "abcdef" -> "bcdez" or "zbcde"
1303         else if diff == 1 then xinfix == ydelr || xinfix == ydell
1304         # No length difference can either happen through replacements on both
1305         # sides, or a deletion on the left and an insertion on the right or
1306         # vice versa
1307         # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1308         else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1310     in k: if k <= 0 then a: b: a == b else
1311       let f = a: b:
1312         let
1313           alen = stringLength a;
1314           blen = stringLength b;
1315           prelen = commonPrefixLength a b;
1316           suflen = commonSuffixLength a b;
1317           presuflen = prelen + suflen;
1318           ainfix = substring prelen (alen - presuflen) a;
1319           binfix = substring prelen (blen - presuflen) b;
1320         in
1321         # Make a be the bigger string
1322         if alen < blen then f b a
1323         # If a has over k more characters than b, even with k deletes on a, b can't be reached
1324         else if alen - blen > k then false
1325         else if k == 1 then infixDifferAtMost1 ainfix binfix
1326         else if k == 2 then infixDifferAtMost2 ainfix binfix
1327         else levenshtein ainfix binfix <= k;
1328       in f;