openvswitch: generalize builder
[NixPkgs.git] / lib / strings.nix
blobb5f5a4d9060ba59ce39b01865848164dfdac58d4
1 /* String manipulation functions. */
2 { lib }:
3 let
5 inherit (builtins) length;
7 in
9 rec {
11   inherit (builtins)
12     compareVersions
13     elem
14     elemAt
15     filter
16     fromJSON
17     head
18     isInt
19     isList
20     isAttrs
21     isString
22     match
23     parseDrvName
24     readFile
25     replaceStrings
26     split
27     storeDir
28     stringLength
29     substring
30     tail
31     toJSON
32     typeOf
33     unsafeDiscardStringContext
34     ;
36   /* Concatenate a list of strings.
38     Type: concatStrings :: [string] -> string
40      Example:
41        concatStrings ["foo" "bar"]
42        => "foobar"
43   */
44   concatStrings = builtins.concatStringsSep "";
46   /* Map a function over a list and concatenate the resulting strings.
48     Type: concatMapStrings :: (a -> string) -> [a] -> string
50      Example:
51        concatMapStrings (x: "a" + x) ["foo" "bar"]
52        => "afooabar"
53   */
54   concatMapStrings = f: list: concatStrings (map f list);
56   /* Like `concatMapStrings` except that the f functions also gets the
57      position as a parameter.
59      Type: concatImapStrings :: (int -> a -> string) -> [a] -> string
61      Example:
62        concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"]
63        => "1-foo2-bar"
64   */
65   concatImapStrings = f: list: concatStrings (lib.imap1 f list);
67   /* Place an element between each element of a list
69      Type: intersperse :: a -> [a] -> [a]
71      Example:
72        intersperse "/" ["usr" "local" "bin"]
73        => ["usr" "/" "local" "/" "bin"].
74   */
75   intersperse =
76     # Separator to add between elements
77     separator:
78     # Input list
79     list:
80     if list == [] || length list == 1
81     then list
82     else tail (lib.concatMap (x: [separator x]) list);
84   /* Concatenate a list of strings with a separator between each element
86      Type: concatStringsSep :: string -> [string] -> string
88      Example:
89         concatStringsSep "/" ["usr" "local" "bin"]
90         => "usr/local/bin"
91   */
92   concatStringsSep = builtins.concatStringsSep or (separator: list:
93     lib.foldl' (x: y: x + y) "" (intersperse separator list));
95   /* Maps a function over a list of strings and then concatenates the
96      result with the specified separator interspersed between
97      elements.
99      Type: concatMapStringsSep :: string -> (a -> string) -> [a] -> string
101      Example:
102         concatMapStringsSep "-" (x: toUpper x)  ["foo" "bar" "baz"]
103         => "FOO-BAR-BAZ"
104   */
105   concatMapStringsSep =
106     # Separator to add between elements
107     sep:
108     # Function to map over the list
109     f:
110     # List of input strings
111     list: concatStringsSep sep (map f list);
113   /* Same as `concatMapStringsSep`, but the mapping function
114      additionally receives the position of its argument.
116      Type: concatIMapStringsSep :: string -> (int -> a -> string) -> [a] -> string
118      Example:
119        concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
120        => "6-3-2"
121   */
122   concatImapStringsSep =
123     # Separator to add between elements
124     sep:
125     # Function that receives elements and their positions
126     f:
127     # List of input strings
128     list: concatStringsSep sep (lib.imap1 f list);
130   /* Construct a Unix-style, colon-separated search path consisting of
131      the given `subDir` appended to each of the given paths.
133      Type: makeSearchPath :: string -> [string] -> string
135      Example:
136        makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
137        => "/root/bin:/usr/bin:/usr/local/bin"
138        makeSearchPath "bin" [""]
139        => "/bin"
140   */
141   makeSearchPath =
142     # Directory name to append
143     subDir:
144     # List of base paths
145     paths:
146     concatStringsSep ":" (map (path: path + "/" + subDir) (filter (x: x != null) paths));
148   /* Construct a Unix-style search path by appending the given
149      `subDir` to the specified `output` of each of the packages. If no
150      output by the given name is found, fallback to `.out` and then to
151      the default.
153      Type: string -> string -> [package] -> string
155      Example:
156        makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
157        => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
158   */
159   makeSearchPathOutput =
160     # Package output to use
161     output:
162     # Directory name to append
163     subDir:
164     # List of packages
165     pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
167   /* Construct a library search path (such as RPATH) containing the
168      libraries for a set of packages
170      Example:
171        makeLibraryPath [ "/usr" "/usr/local" ]
172        => "/usr/lib:/usr/local/lib"
173        pkgs = import <nixpkgs> { }
174        makeLibraryPath [ pkgs.openssl pkgs.zlib ]
175        => "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
176   */
177   makeLibraryPath = makeSearchPathOutput "lib" "lib";
179   /* Construct a binary search path (such as $PATH) containing the
180      binaries for a set of packages.
182      Example:
183        makeBinPath ["/root" "/usr" "/usr/local"]
184        => "/root/bin:/usr/bin:/usr/local/bin"
185   */
186   makeBinPath = makeSearchPathOutput "bin" "bin";
188   /* Normalize path, removing extranous /s
190      Type: normalizePath :: string -> string
192      Example:
193        normalizePath "/a//b///c/"
194        => "/a/b/c/"
195   */
196   normalizePath = s: (builtins.foldl' (x: y: if y == "/" && hasSuffix "/" x then x else x+y) "" (stringToCharacters s));
198   /* Depending on the boolean `cond', return either the given string
199      or the empty string. Useful to concatenate against a bigger string.
201      Type: optionalString :: bool -> string -> string
203      Example:
204        optionalString true "some-string"
205        => "some-string"
206        optionalString false "some-string"
207        => ""
208   */
209   optionalString =
210     # Condition
211     cond:
212     # String to return if condition is true
213     string: if cond then string else "";
215   /* Determine whether a string has given prefix.
217      Type: hasPrefix :: string -> string -> bool
219      Example:
220        hasPrefix "foo" "foobar"
221        => true
222        hasPrefix "foo" "barfoo"
223        => false
224   */
225   hasPrefix =
226     # Prefix to check for
227     pref:
228     # Input string
229     str: substring 0 (stringLength pref) str == pref;
231   /* Determine whether a string has given suffix.
233      Type: hasSuffix :: string -> string -> bool
235      Example:
236        hasSuffix "foo" "foobar"
237        => false
238        hasSuffix "foo" "barfoo"
239        => true
240   */
241   hasSuffix =
242     # Suffix to check for
243     suffix:
244     # Input string
245     content:
246     let
247       lenContent = stringLength content;
248       lenSuffix = stringLength suffix;
249     in lenContent >= lenSuffix &&
250        substring (lenContent - lenSuffix) lenContent content == suffix;
252   /* Determine whether a string contains the given infix
254     Type: hasInfix :: string -> string -> bool
256     Example:
257       hasInfix "bc" "abcd"
258       => true
259       hasInfix "ab" "abcd"
260       => true
261       hasInfix "cd" "abcd"
262       => true
263       hasInfix "foo" "abcd"
264       => false
265   */
266   hasInfix = infix: content:
267     builtins.match ".*${escapeRegex infix}.*" "${content}" != null;
269   /* Convert a string to a list of characters (i.e. singleton strings).
270      This allows you to, e.g., map a function over each character.  However,
271      note that this will likely be horribly inefficient; Nix is not a
272      general purpose programming language. Complex string manipulations
273      should, if appropriate, be done in a derivation.
274      Also note that Nix treats strings as a list of bytes and thus doesn't
275      handle unicode.
277      Type: stringToCharacters :: string -> [string]
279      Example:
280        stringToCharacters ""
281        => [ ]
282        stringToCharacters "abc"
283        => [ "a" "b" "c" ]
284        stringToCharacters "💩"
285        => [ "�" "�" "�" "�" ]
286   */
287   stringToCharacters = s:
288     map (p: substring p 1 s) (lib.range 0 (stringLength s - 1));
290   /* Manipulate a string character by character and replace them by
291      strings before concatenating the results.
293      Type: stringAsChars :: (string -> string) -> string -> string
295      Example:
296        stringAsChars (x: if x == "a" then "i" else x) "nax"
297        => "nix"
298   */
299   stringAsChars =
300     # Function to map over each individual character
301     f:
302     # Input string
303     s: concatStrings (
304       map f (stringToCharacters s)
305     );
307   /* Convert char to ascii value, must be in printable range
309      Type: charToInt :: string -> int
311      Example:
312        charToInt "A"
313        => 65
314        charToInt "("
315        => 40
317   */
318   charToInt = let
319     table = import ./ascii-table.nix;
320   in c: builtins.getAttr c table;
322   /* Escape occurrence of the elements of `list` in `string` by
323      prefixing it with a backslash.
325      Type: escape :: [string] -> string -> string
327      Example:
328        escape ["(" ")"] "(foo)"
329        => "\\(foo\\)"
330   */
331   escape = list: replaceChars list (map (c: "\\${c}") list);
333   /* Escape occurence of the element of `list` in `string` by
334      converting to its ASCII value and prefixing it with \\x.
335      Only works for printable ascii characters.
337      Type: escapeC = [string] -> string -> string
339      Example:
340        escapeC [" "] "foo bar"
341        => "foo\\x20bar"
343   */
344   escapeC = list: replaceChars list (map (c: "\\x${ toLower (lib.toHexString (charToInt c))}") list);
346   /* Quote string to be used safely within the Bourne shell.
348      Type: escapeShellArg :: string -> string
350      Example:
351        escapeShellArg "esc'ape\nme"
352        => "'esc'\\''ape\nme'"
353   */
354   escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
356   /* Quote all arguments to be safely passed to the Bourne shell.
358      Type: escapeShellArgs :: [string] -> string
360      Example:
361        escapeShellArgs ["one" "two three" "four'five"]
362        => "'one' 'two three' 'four'\\''five'"
363   */
364   escapeShellArgs = concatMapStringsSep " " escapeShellArg;
366   /* Test whether the given name is a valid POSIX shell variable name.
368      Type: string -> bool
370      Example:
371        isValidPosixName "foo_bar000"
372        => true
373        isValidPosixName "0-bad.jpg"
374        => false
375   */
376   isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
378   /* Translate a Nix value into a shell variable declaration, with proper escaping.
380      The value can be a string (mapped to a regular variable), a list of strings
381      (mapped to a Bash-style array) or an attribute set of strings (mapped to a
382      Bash-style associative array). Note that "string" includes string-coercible
383      values like paths or derivations.
385      Strings are translated into POSIX sh-compatible code; lists and attribute sets
386      assume a shell that understands Bash syntax (e.g. Bash or ZSH).
388      Type: string -> (string | listOf string | attrsOf string) -> string
390      Example:
391        ''
392          ${toShellVar "foo" "some string"}
393          [[ "$foo" == "some string" ]]
394        ''
395   */
396   toShellVar = name: value:
397     lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" (
398     if isAttrs value && ! isCoercibleToString value then
399       "declare -A ${name}=(${
400         concatStringsSep " " (lib.mapAttrsToList (n: v:
401           "[${escapeShellArg n}]=${escapeShellArg v}"
402         ) value)
403       })"
404     else if isList value then
405       "declare -a ${name}=(${escapeShellArgs value})"
406     else
407       "${name}=${escapeShellArg value}"
408     );
410   /* Translate an attribute set into corresponding shell variable declarations
411      using `toShellVar`.
413      Type: attrsOf (string | listOf string | attrsOf string) -> string
415      Example:
416        let
417          foo = "value";
418          bar = foo;
419        in ''
420          ${toShellVars { inherit foo bar; }}
421          [[ "$foo" == "$bar" ]]
422        ''
423   */
424   toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars);
426   /* Turn a string into a Nix expression representing that string
428      Type: string -> string
430      Example:
431        escapeNixString "hello\${}\n"
432        => "\"hello\\\${}\\n\""
433   */
434   escapeNixString = s: escape ["$"] (toJSON s);
436   /* Turn a string into an exact regular expression
438      Type: string -> string
440      Example:
441        escapeRegex "[^a-z]*"
442        => "\\[\\^a-z]\\*"
443   */
444   escapeRegex = escape (stringToCharacters "\\[{()^$?*+|.");
446   /* Quotes a string if it can't be used as an identifier directly.
448      Type: string -> string
450      Example:
451        escapeNixIdentifier "hello"
452        => "hello"
453        escapeNixIdentifier "0abc"
454        => "\"0abc\""
455   */
456   escapeNixIdentifier = s:
457     # Regex from https://github.com/NixOS/nix/blob/d048577909e383439c2549e849c5c2f2016c997e/src/libexpr/lexer.l#L91
458     if match "[a-zA-Z_][a-zA-Z0-9_'-]*" s != null
459     then s else escapeNixString s;
461   /* Escapes a string such that it is safe to include verbatim in an XML
462      document.
464      Type: string -> string
466      Example:
467        escapeXML ''"test" 'test' < & >''
468        => "&quot;test&quot; &apos;test&apos; &lt; &amp; &gt;"
469   */
470   escapeXML = builtins.replaceStrings
471     ["\"" "'" "<" ">" "&"]
472     ["&quot;" "&apos;" "&lt;" "&gt;" "&amp;"];
474   # Obsolete - use replaceStrings instead.
475   replaceChars = builtins.replaceStrings or (
476     del: new: s:
477     let
478       substList = lib.zipLists del new;
479       subst = c:
480         let found = lib.findFirst (sub: sub.fst == c) null substList; in
481         if found == null then
482           c
483         else
484           found.snd;
485     in
486       stringAsChars subst s);
488   # Case conversion utilities.
489   lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
490   upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
492   /* Converts an ASCII string to lower-case.
494      Type: toLower :: string -> string
496      Example:
497        toLower "HOME"
498        => "home"
499   */
500   toLower = replaceChars upperChars lowerChars;
502   /* Converts an ASCII string to upper-case.
504      Type: toUpper :: string -> string
506      Example:
507        toUpper "home"
508        => "HOME"
509   */
510   toUpper = replaceChars lowerChars upperChars;
512   /* Appends string context from another string.  This is an implementation
513      detail of Nix.
515      Strings in Nix carry an invisible `context` which is a list of strings
516      representing store paths.  If the string is later used in a derivation
517      attribute, the derivation will properly populate the inputDrvs and
518      inputSrcs.
520      Example:
521        pkgs = import <nixpkgs> { };
522        addContextFrom pkgs.coreutils "bar"
523        => "bar"
524   */
525   addContextFrom = a: b: substring 0 0 a + b;
527   /* Cut a string with a separator and produces a list of strings which
528      were separated by this separator.
530      Example:
531        splitString "." "foo.bar.baz"
532        => [ "foo" "bar" "baz" ]
533        splitString "/" "/usr/local/bin"
534        => [ "" "usr" "local" "bin" ]
535   */
536   splitString = _sep: _s:
537     let
538       sep = builtins.unsafeDiscardStringContext _sep;
539       s = builtins.unsafeDiscardStringContext _s;
540       splits = builtins.filter builtins.isString (builtins.split (escapeRegex sep) s);
541     in
542       map (v: addContextFrom _sep (addContextFrom _s v)) splits;
544   /* Return a string without the specified prefix, if the prefix matches.
546      Type: string -> string -> string
548      Example:
549        removePrefix "foo." "foo.bar.baz"
550        => "bar.baz"
551        removePrefix "xxx" "foo.bar.baz"
552        => "foo.bar.baz"
553   */
554   removePrefix =
555     # Prefix to remove if it matches
556     prefix:
557     # Input string
558     str:
559     let
560       preLen = stringLength prefix;
561       sLen = stringLength str;
562     in
563       if hasPrefix prefix str then
564         substring preLen (sLen - preLen) str
565       else
566         str;
568   /* Return a string without the specified suffix, if the suffix matches.
570      Type: string -> string -> string
572      Example:
573        removeSuffix "front" "homefront"
574        => "home"
575        removeSuffix "xxx" "homefront"
576        => "homefront"
577   */
578   removeSuffix =
579     # Suffix to remove if it matches
580     suffix:
581     # Input string
582     str:
583     let
584       sufLen = stringLength suffix;
585       sLen = stringLength str;
586     in
587       if sufLen <= sLen && suffix == substring (sLen - sufLen) sufLen str then
588         substring 0 (sLen - sufLen) str
589       else
590         str;
592   /* Return true if string v1 denotes a version older than v2.
594      Example:
595        versionOlder "1.1" "1.2"
596        => true
597        versionOlder "1.1" "1.1"
598        => false
599   */
600   versionOlder = v1: v2: compareVersions v2 v1 == 1;
602   /* Return true if string v1 denotes a version equal to or newer than v2.
604      Example:
605        versionAtLeast "1.1" "1.0"
606        => true
607        versionAtLeast "1.1" "1.1"
608        => true
609        versionAtLeast "1.1" "1.2"
610        => false
611   */
612   versionAtLeast = v1: v2: !versionOlder v1 v2;
614   /* This function takes an argument that's either a derivation or a
615      derivation's "name" attribute and extracts the name part from that
616      argument.
618      Example:
619        getName "youtube-dl-2016.01.01"
620        => "youtube-dl"
621        getName pkgs.youtube-dl
622        => "youtube-dl"
623   */
624   getName = x:
625    let
626      parse = drv: (parseDrvName drv).name;
627    in if isString x
628       then parse x
629       else x.pname or (parse x.name);
631   /* This function takes an argument that's either a derivation or a
632      derivation's "name" attribute and extracts the version part from that
633      argument.
635      Example:
636        getVersion "youtube-dl-2016.01.01"
637        => "2016.01.01"
638        getVersion pkgs.youtube-dl
639        => "2016.01.01"
640   */
641   getVersion = x:
642    let
643      parse = drv: (parseDrvName drv).version;
644    in if isString x
645       then parse x
646       else x.version or (parse x.name);
648   /* Extract name with version from URL. Ask for separator which is
649      supposed to start extension.
651      Example:
652        nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
653        => "nix"
654        nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
655        => "nix-1.7-x86"
656   */
657   nameFromURL = url: sep:
658     let
659       components = splitString "/" url;
660       filename = lib.last components;
661       name = head (splitString sep filename);
662     in assert name != filename; name;
664   /* Create an --{enable,disable}-<feat> string that can be passed to
665      standard GNU Autoconf scripts.
667      Example:
668        enableFeature true "shared"
669        => "--enable-shared"
670        enableFeature false "shared"
671        => "--disable-shared"
672   */
673   enableFeature = enable: feat:
674     assert isString feat; # e.g. passing openssl instead of "openssl"
675     "--${if enable then "enable" else "disable"}-${feat}";
677   /* Create an --{enable-<feat>=<value>,disable-<feat>} string that can be passed to
678      standard GNU Autoconf scripts.
680      Example:
681        enableFeatureAs true "shared" "foo"
682        => "--enable-shared=foo"
683        enableFeatureAs false "shared" (throw "ignored")
684        => "--disable-shared"
685   */
686   enableFeatureAs = enable: feat: value: enableFeature enable feat + optionalString enable "=${value}";
688   /* Create an --{with,without}-<feat> string that can be passed to
689      standard GNU Autoconf scripts.
691      Example:
692        withFeature true "shared"
693        => "--with-shared"
694        withFeature false "shared"
695        => "--without-shared"
696   */
697   withFeature = with_: feat:
698     assert isString feat; # e.g. passing openssl instead of "openssl"
699     "--${if with_ then "with" else "without"}-${feat}";
701   /* Create an --{with-<feat>=<value>,without-<feat>} string that can be passed to
702      standard GNU Autoconf scripts.
704      Example:
705        withFeatureAs true "shared" "foo"
706        => "--with-shared=foo"
707        withFeatureAs false "shared" (throw "ignored")
708        => "--without-shared"
709   */
710   withFeatureAs = with_: feat: value: withFeature with_ feat + optionalString with_ "=${value}";
712   /* Create a fixed width string with additional prefix to match
713      required width.
715      This function will fail if the input string is longer than the
716      requested length.
718      Type: fixedWidthString :: int -> string -> string -> string
720      Example:
721        fixedWidthString 5 "0" (toString 15)
722        => "00015"
723   */
724   fixedWidthString = width: filler: str:
725     let
726       strw = lib.stringLength str;
727       reqWidth = width - (lib.stringLength filler);
728     in
729       assert lib.assertMsg (strw <= width)
730         "fixedWidthString: requested string length (${
731           toString width}) must not be shorter than actual length (${
732             toString strw})";
733       if strw == width then str else filler + fixedWidthString reqWidth filler str;
735   /* Format a number adding leading zeroes up to fixed width.
737      Example:
738        fixedWidthNumber 5 15
739        => "00015"
740   */
741   fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
743   /* Convert a float to a string, but emit a warning when precision is lost
744      during the conversion
746      Example:
747        floatToString 0.000001
748        => "0.000001"
749        floatToString 0.0000001
750        => trace: warning: Imprecise conversion from float to string 0.000000
751           "0.000000"
752   */
753   floatToString = float: let
754     result = toString float;
755     precise = float == fromJSON result;
756   in lib.warnIf (!precise) "Imprecise conversion from float to string ${result}"
757     result;
759   /* Check whether a value can be coerced to a string */
760   isCoercibleToString = x:
761     elem (typeOf x) [ "path" "string" "null" "int" "float" "bool" ] ||
762     (isList x && lib.all isCoercibleToString x) ||
763     x ? outPath ||
764     x ? __toString;
766   /* Check whether a value is a store path.
768      Example:
769        isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
770        => false
771        isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11"
772        => true
773        isStorePath pkgs.python
774        => true
775        isStorePath [] || isStorePath 42 || isStorePath {} || â€¦
776        => false
777   */
778   isStorePath = x:
779     if !(isList x) && isCoercibleToString x then
780       let str = toString x; in
781       substring 0 1 str == "/"
782       && dirOf str == storeDir
783     else
784       false;
786   /* Parse a string as an int. Does not support parsing of integers with preceding zero due to
787   ambiguity between zero-padded and octal numbers. See toIntBase10.
789      Type: string -> int
791      Example:
793        toInt "1337"
794        => 1337
796        toInt "-4"
797        => -4
799        toInt " 123 "
800        => 123
802        toInt "00024"
803        => error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
805        toInt "3.14"
806        => error: floating point JSON numbers are not supported
807   */
808   toInt = str:
809     let
810       # RegEx: Match any leading whitespace, then any digits, and finally match any trailing
811       # whitespace.
812       strippedInput = match "[[:space:]]*([[:digit:]]+)[[:space:]]*" str;
814       # RegEx: Match a leading '0' then one or more digits.
815       isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == [];
817       # Attempt to parse input
818       parsedInput = fromJSON (head strippedInput);
820       generalError = "toInt: Could not convert ${escapeNixString str} to int.";
822       octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}"
823       + " between octal and zero padded integer.";
825     in
826       # Error on presence of non digit characters.
827       if strippedInput == null
828       then throw generalError
829       # Error on presence of leading zero/octal ambiguity.
830       else if isLeadingZero
831       then throw octalAmbigError
832       # Error if parse function fails.
833       else if !isInt parsedInput
834       then throw generalError
835       # Return result.
836       else parsedInput;
839   /* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
841      Type: string -> int
843      Example:
844        toIntBase10 "1337"
845        => 1337
847        toIntBase10 "-4"
848        => -4
850        toIntBase10 " 123 "
851        => 123
853        toIntBase10 "00024"
854        => 24
856        toIntBase10 "3.14"
857        => error: floating point JSON numbers are not supported
858   */
859   toIntBase10 = str:
860     let
861       # RegEx: Match any leading whitespace, then match any zero padding, capture any remaining
862       # digits after that, and finally match any trailing whitespace.
863       strippedInput = match "[[:space:]]*0*([[:digit:]]+)[[:space:]]*" str;
865       # RegEx: Match at least one '0'.
866       isZero = match "0+" (head strippedInput) == [];
868       # Attempt to parse input
869       parsedInput = fromJSON (head strippedInput);
871       generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
873     in
874       # Error on presence of non digit characters.
875       if strippedInput == null
876       then throw generalError
877       # In the special case zero-padded zero (00000), return early.
878       else if isZero
879       then 0
880       # Error if parse function fails.
881       else if !isInt parsedInput
882       then throw generalError
883       # Return result.
884       else parsedInput;
886   /* Read a list of paths from `file`, relative to the `rootPath`.
887      Lines beginning with `#` are treated as comments and ignored.
888      Whitespace is significant.
890      NOTE: This function is not performant and should be avoided.
892      Example:
893        readPathsFromFile /prefix
894          ./pkgs/development/libraries/qt-5/5.4/qtbase/series
895        => [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
896             "/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
897             "/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
898             "/prefix/nix-profiles-library-paths.patch"
899             "/prefix/compose-search-path.patch" ]
900   */
901   readPathsFromFile = lib.warn "lib.readPathsFromFile is deprecated, use a list instead"
902     (rootPath: file:
903       let
904         lines = lib.splitString "\n" (readFile file);
905         removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
906         relativePaths = removeComments lines;
907         absolutePaths = map (path: rootPath + "/${path}") relativePaths;
908       in
909         absolutePaths);
911   /* Read the contents of a file removing the trailing \n
913      Type: fileContents :: path -> string
915      Example:
916        $ echo "1.0" > ./version
918        fileContents ./version
919        => "1.0"
920   */
921   fileContents = file: removeSuffix "\n" (readFile file);
924   /* Creates a valid derivation name from a potentially invalid one.
926      Type: sanitizeDerivationName :: String -> String
928      Example:
929        sanitizeDerivationName "../hello.bar # foo"
930        => "-hello.bar-foo"
931        sanitizeDerivationName ""
932        => "unknown"
933        sanitizeDerivationName pkgs.hello
934        => "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10"
935   */
936   sanitizeDerivationName =
937   let okRegex = match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
938   in
939   string:
940   # First detect the common case of already valid strings, to speed those up
941   if stringLength string <= 207 && okRegex string != null
942   then unsafeDiscardStringContext string
943   else lib.pipe string [
944     # Get rid of string context. This is safe under the assumption that the
945     # resulting string is only used as a derivation name
946     unsafeDiscardStringContext
947     # Strip all leading "."
948     (x: elemAt (match "\\.*(.*)" x) 0)
949     # Split out all invalid characters
950     # https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112
951     # https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125
952     (split "[^[:alnum:]+._?=-]+")
953     # Replace invalid character ranges with a "-"
954     (concatMapStrings (s: if lib.isList s then "-" else s))
955     # Limit to 211 characters (minus 4 chars for ".drv")
956     (x: substring (lib.max (stringLength x - 207) 0) (-1) x)
957     # If the result is empty, replace it with "unknown"
958     (x: if stringLength x == 0 then "unknown" else x)
959   ];
961   /* Computes the Levenshtein distance between two strings.
962      Complexity O(n*m) where n and m are the lengths of the strings.
963      Algorithm adjusted from https://stackoverflow.com/a/9750974/6605742
965      Type: levenshtein :: string -> string -> int
967      Example:
968        levenshtein "foo" "foo"
969        => 0
970        levenshtein "book" "hook"
971        => 1
972        levenshtein "hello" "Heyo"
973        => 3
974   */
975   levenshtein = a: b: let
976     # Two dimensional array with dimensions (stringLength a + 1, stringLength b + 1)
977     arr = lib.genList (i:
978       lib.genList (j:
979         dist i j
980       ) (stringLength b + 1)
981     ) (stringLength a + 1);
982     d = x: y: lib.elemAt (lib.elemAt arr x) y;
983     dist = i: j:
984       let c = if substring (i - 1) 1 a == substring (j - 1) 1 b
985         then 0 else 1;
986       in
987       if j == 0 then i
988       else if i == 0 then j
989       else lib.min
990         ( lib.min (d (i - 1) j + 1) (d i (j - 1) + 1))
991         ( d (i - 1) (j - 1) + c );
992   in d (stringLength a) (stringLength b);
994   /* Returns the length of the prefix common to both strings.
995   */
996   commonPrefixLength = a: b:
997     let
998       m = lib.min (stringLength a) (stringLength b);
999       go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
1000     in go 0;
1002   /* Returns the length of the suffix common to both strings.
1003   */
1004   commonSuffixLength = a: b:
1005     let
1006       m = lib.min (stringLength a) (stringLength b);
1007       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;
1008     in go 0;
1010   /* Returns whether the levenshtein distance between two strings is at most some value
1011      Complexity is O(min(n,m)) for k <= 2 and O(n*m) otherwise
1013      Type: levenshteinAtMost :: int -> string -> string -> bool
1015      Example:
1016        levenshteinAtMost 0 "foo" "foo"
1017        => true
1018        levenshteinAtMost 1 "foo" "boa"
1019        => false
1020        levenshteinAtMost 2 "foo" "boa"
1021        => true
1022        levenshteinAtMost 2 "This is a sentence" "this is a sentense."
1023        => false
1024        levenshteinAtMost 3 "This is a sentence" "this is a sentense."
1025        => true
1027   */
1028   levenshteinAtMost = let
1029     infixDifferAtMost1 = x: y: stringLength x <= 1 && stringLength y <= 1;
1031     # This function takes two strings stripped by their common pre and suffix,
1032     # and returns whether they differ by at most two by Levenshtein distance.
1033     # Because of this stripping, if they do indeed differ by at most two edits,
1034     # we know that those edits were (if at all) done at the start or the end,
1035     # while the middle has to have stayed the same. This fact is used in the
1036     # implementation.
1037     infixDifferAtMost2 = x: y:
1038       let
1039         xlen = stringLength x;
1040         ylen = stringLength y;
1041         # This function is only called with |x| >= |y| and |x| - |y| <= 2, so
1042         # diff is one of 0, 1 or 2
1043         diff = xlen - ylen;
1045         # Infix of x and y, stripped by the left and right most character
1046         xinfix = substring 1 (xlen - 2) x;
1047         yinfix = substring 1 (ylen - 2) y;
1049         # x and y but a character deleted at the left or right
1050         xdelr = substring 0 (xlen - 1) x;
1051         xdell = substring 1 (xlen - 1) x;
1052         ydelr = substring 0 (ylen - 1) y;
1053         ydell = substring 1 (ylen - 1) y;
1054       in
1055         # A length difference of 2 can only be gotten with 2 delete edits,
1056         # which have to have happened at the start and end of x
1057         # Example: "abcdef" -> "bcde"
1058         if diff == 2 then xinfix == y
1059         # A length difference of 1 can only be gotten with a deletion on the
1060         # right and a replacement on the left or vice versa.
1061         # Example: "abcdef" -> "bcdez" or "zbcde"
1062         else if diff == 1 then xinfix == ydelr || xinfix == ydell
1063         # No length difference can either happen through replacements on both
1064         # sides, or a deletion on the left and an insertion on the right or
1065         # vice versa
1066         # Example: "abcdef" -> "zbcdez" or "bcdefz" or "zabcde"
1067         else xinfix == yinfix || xdelr == ydell || xdell == ydelr;
1069     in k: if k <= 0 then a: b: a == b else
1070       let f = a: b:
1071         let
1072           alen = stringLength a;
1073           blen = stringLength b;
1074           prelen = commonPrefixLength a b;
1075           suflen = commonSuffixLength a b;
1076           presuflen = prelen + suflen;
1077           ainfix = substring prelen (alen - presuflen) a;
1078           binfix = substring prelen (blen - presuflen) b;
1079         in
1080         # Make a be the bigger string
1081         if alen < blen then f b a
1082         # If a has over k more characters than b, even with k deletes on a, b can't be reached
1083         else if alen - blen > k then false
1084         else if k == 1 then infixDifferAtMost1 ainfix binfix
1085         else if k == 2 then infixDifferAtMost2 ainfix binfix
1086         else levenshtein ainfix binfix <= k;
1087       in f;