4d545297d8
* lib: introduce imap0, imap1
For historical reasons, imap starts counting at 1 and it's not
consistent with the rest of the lib.
So for now we split imap into imap0 that starts counting at zero and
imap1 that starts counting at 1. And imap is marked as deprecated.
See c71e2d4235 (commitcomment-21873221)
* replace uses of lib.imap
* lib: move imap to deprecated.nix
501 lines
14 KiB
Nix
501 lines
14 KiB
Nix
/* String manipulation functions. */
|
||
|
||
let lib = import ./default.nix;
|
||
|
||
inherit (builtins) length;
|
||
|
||
in
|
||
|
||
rec {
|
||
|
||
inherit (builtins) stringLength substring head tail isString replaceStrings;
|
||
|
||
/* Concatenate a list of strings.
|
||
|
||
Example:
|
||
concatStrings ["foo" "bar"]
|
||
=> "foobar"
|
||
*/
|
||
concatStrings = builtins.concatStringsSep "";
|
||
|
||
/* Map a function over a list and concatenate the resulting strings.
|
||
|
||
Example:
|
||
concatMapStrings (x: "a" + x) ["foo" "bar"]
|
||
=> "afooabar"
|
||
*/
|
||
concatMapStrings = f: list: concatStrings (map f list);
|
||
|
||
/* Like `concatMapStrings' except that the f functions also gets the
|
||
position as a parameter.
|
||
|
||
Example:
|
||
concatImapStrings (pos: x: "${toString pos}-${x}") ["foo" "bar"]
|
||
=> "1-foo2-bar"
|
||
*/
|
||
concatImapStrings = f: list: concatStrings (lib.imap1 f list);
|
||
|
||
/* Place an element between each element of a list
|
||
|
||
Example:
|
||
intersperse "/" ["usr" "local" "bin"]
|
||
=> ["usr" "/" "local" "/" "bin"].
|
||
*/
|
||
intersperse = separator: list:
|
||
if list == [] || length list == 1
|
||
then list
|
||
else tail (lib.concatMap (x: [separator x]) list);
|
||
|
||
/* Concatenate a list of strings with a separator between each element
|
||
|
||
Example:
|
||
concatStringsSep "/" ["usr" "local" "bin"]
|
||
=> "usr/local/bin"
|
||
*/
|
||
concatStringsSep = builtins.concatStringsSep or (separator: list:
|
||
concatStrings (intersperse separator list));
|
||
|
||
/* First maps over the list and then concatenates it.
|
||
|
||
Example:
|
||
concatMapStringsSep "-" (x: toUpper x) ["foo" "bar" "baz"]
|
||
=> "FOO-BAR-BAZ"
|
||
*/
|
||
concatMapStringsSep = sep: f: list: concatStringsSep sep (map f list);
|
||
|
||
/* First imaps over the list and then concatenates it.
|
||
|
||
Example:
|
||
|
||
concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
|
||
=> "6-3-2"
|
||
*/
|
||
concatImapStringsSep = sep: f: list: concatStringsSep sep (lib.imap1 f list);
|
||
|
||
/* Construct a Unix-style search path consisting of each `subDir"
|
||
directory of the given list of packages.
|
||
|
||
Example:
|
||
makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
|
||
=> "/root/bin:/usr/bin:/usr/local/bin"
|
||
makeSearchPath "bin" ["/"]
|
||
=> "//bin"
|
||
*/
|
||
makeSearchPath = subDir: packages:
|
||
concatStringsSep ":" (map (path: path + "/" + subDir) packages);
|
||
|
||
/* Construct a Unix-style search path, using given package output.
|
||
If no output is found, fallback to `.out` and then to the default.
|
||
|
||
Example:
|
||
makeSearchPathOutput "dev" "bin" [ pkgs.openssl pkgs.zlib ]
|
||
=> "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r-dev/bin:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/bin"
|
||
*/
|
||
makeSearchPathOutput = output: subDir: pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
|
||
|
||
/* Construct a library search path (such as RPATH) containing the
|
||
libraries for a set of packages
|
||
|
||
Example:
|
||
makeLibraryPath [ "/usr" "/usr/local" ]
|
||
=> "/usr/lib:/usr/local/lib"
|
||
pkgs = import <nixpkgs> { }
|
||
makeLibraryPath [ pkgs.openssl pkgs.zlib ]
|
||
=> "/nix/store/9rz8gxhzf8sw4kf2j2f1grr49w8zx5vj-openssl-1.0.1r/lib:/nix/store/wwh7mhwh269sfjkm6k5665b5kgp7jrk2-zlib-1.2.8/lib"
|
||
*/
|
||
makeLibraryPath = makeSearchPathOutput "lib" "lib";
|
||
|
||
/* Construct a binary search path (such as $PATH) containing the
|
||
binaries for a set of packages.
|
||
|
||
Example:
|
||
makeBinPath ["/root" "/usr" "/usr/local"]
|
||
=> "/root/bin:/usr/bin:/usr/local/bin"
|
||
*/
|
||
makeBinPath = makeSearchPathOutput "bin" "bin";
|
||
|
||
|
||
/* Construct a perl search path (such as $PERL5LIB)
|
||
|
||
FIXME(zimbatm): this should be moved in perl-specific code
|
||
|
||
Example:
|
||
pkgs = import <nixpkgs> { }
|
||
makePerlPath [ pkgs.perlPackages.NetSMTP ]
|
||
=> "/nix/store/n0m1fk9c960d8wlrs62sncnadygqqc6y-perl-Net-SMTP-1.25/lib/perl5/site_perl"
|
||
*/
|
||
makePerlPath = makeSearchPathOutput "lib" "lib/perl5/site_perl";
|
||
|
||
/* Depending on the boolean `cond', return either the given string
|
||
or the empty string. Useful to concatenate against a bigger string.
|
||
|
||
Example:
|
||
optionalString true "some-string"
|
||
=> "some-string"
|
||
optionalString false "some-string"
|
||
=> ""
|
||
*/
|
||
optionalString = cond: string: if cond then string else "";
|
||
|
||
/* Determine whether a string has given prefix.
|
||
|
||
Example:
|
||
hasPrefix "foo" "foobar"
|
||
=> true
|
||
hasPrefix "foo" "barfoo"
|
||
=> false
|
||
*/
|
||
hasPrefix = pref: str:
|
||
substring 0 (stringLength pref) str == pref;
|
||
|
||
/* Determine whether a string has given suffix.
|
||
|
||
Example:
|
||
hasSuffix "foo" "foobar"
|
||
=> false
|
||
hasSuffix "foo" "barfoo"
|
||
=> true
|
||
*/
|
||
hasSuffix = suffix: content:
|
||
let
|
||
lenContent = stringLength content;
|
||
lenSuffix = stringLength suffix;
|
||
in lenContent >= lenSuffix &&
|
||
substring (lenContent - lenSuffix) lenContent content == suffix;
|
||
|
||
/* Convert a string to a list of characters (i.e. singleton strings).
|
||
This allows you to, e.g., map a function over each character. However,
|
||
note that this will likely be horribly inefficient; Nix is not a
|
||
general purpose programming language. Complex string manipulations
|
||
should, if appropriate, be done in a derivation.
|
||
Also note that Nix treats strings as a list of bytes and thus doesn't
|
||
handle unicode.
|
||
|
||
Example:
|
||
stringToCharacters ""
|
||
=> [ ]
|
||
stringToCharacters "abc"
|
||
=> [ "a" "b" "c" ]
|
||
stringToCharacters "💩"
|
||
=> [ "<EFBFBD>" "<EFBFBD>" "<EFBFBD>" "<EFBFBD>" ]
|
||
*/
|
||
stringToCharacters = s:
|
||
map (p: substring p 1 s) (lib.range 0 (stringLength s - 1));
|
||
|
||
/* Manipulate a string character by character and replace them by
|
||
strings before concatenating the results.
|
||
|
||
Example:
|
||
stringAsChars (x: if x == "a" then "i" else x) "nax"
|
||
=> "nix"
|
||
*/
|
||
stringAsChars = f: s:
|
||
concatStrings (
|
||
map f (stringToCharacters s)
|
||
);
|
||
|
||
/* Escape occurrence of the elements of ‘list’ in ‘string’ by
|
||
prefixing it with a backslash.
|
||
|
||
Example:
|
||
escape ["(" ")"] "(foo)"
|
||
=> "\\(foo\\)"
|
||
*/
|
||
escape = list: replaceChars list (map (c: "\\${c}") list);
|
||
|
||
/* Quote string to be used safely within the Bourne shell.
|
||
|
||
Example:
|
||
escapeShellArg "esc'ape\nme"
|
||
=> "'esc'\\''ape\nme'"
|
||
*/
|
||
escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
|
||
|
||
/* Quote all arguments to be safely passed to the Bourne shell.
|
||
|
||
Example:
|
||
escapeShellArgs ["one" "two three" "four'five"]
|
||
=> "'one' 'two three' 'four'\\''five'"
|
||
*/
|
||
escapeShellArgs = concatMapStringsSep " " escapeShellArg;
|
||
|
||
/* Obsolete - use replaceStrings instead. */
|
||
replaceChars = builtins.replaceStrings or (
|
||
del: new: s:
|
||
let
|
||
substList = lib.zipLists del new;
|
||
subst = c:
|
||
let found = lib.findFirst (sub: sub.fst == c) null substList; in
|
||
if found == null then
|
||
c
|
||
else
|
||
found.snd;
|
||
in
|
||
stringAsChars subst s);
|
||
|
||
# Case conversion utilities.
|
||
lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
|
||
upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||
|
||
/* Converts an ASCII string to lower-case.
|
||
|
||
Example:
|
||
toLower "HOME"
|
||
=> "home"
|
||
*/
|
||
toLower = replaceChars upperChars lowerChars;
|
||
|
||
/* Converts an ASCII string to upper-case.
|
||
|
||
Example:
|
||
toUpper "home"
|
||
=> "HOME"
|
||
*/
|
||
toUpper = replaceChars lowerChars upperChars;
|
||
|
||
/* Appends string context from another string. This is an implementation
|
||
detail of Nix.
|
||
|
||
Strings in Nix carry an invisible `context' which is a list of strings
|
||
representing store paths. If the string is later used in a derivation
|
||
attribute, the derivation will properly populate the inputDrvs and
|
||
inputSrcs.
|
||
|
||
Example:
|
||
pkgs = import <nixpkgs> { };
|
||
addContextFrom pkgs.coreutils "bar"
|
||
=> "bar"
|
||
*/
|
||
addContextFrom = a: b: substring 0 0 a + b;
|
||
|
||
/* Cut a string with a separator and produces a list of strings which
|
||
were separated by this separator.
|
||
|
||
NOTE: this function is not performant and should never be used.
|
||
|
||
Example:
|
||
splitString "." "foo.bar.baz"
|
||
=> [ "foo" "bar" "baz" ]
|
||
splitString "/" "/usr/local/bin"
|
||
=> [ "" "usr" "local" "bin" ]
|
||
*/
|
||
splitString = _sep: _s:
|
||
let
|
||
sep = addContextFrom _s _sep;
|
||
s = addContextFrom _sep _s;
|
||
sepLen = stringLength sep;
|
||
sLen = stringLength s;
|
||
lastSearch = sLen - sepLen;
|
||
startWithSep = startAt:
|
||
substring startAt sepLen s == sep;
|
||
|
||
recurse = index: startAt:
|
||
let cutUntil = i: [(substring startAt (i - startAt) s)]; in
|
||
if index <= lastSearch then
|
||
if startWithSep index then
|
||
let restartAt = index + sepLen; in
|
||
cutUntil index ++ recurse restartAt restartAt
|
||
else
|
||
recurse (index + 1) startAt
|
||
else
|
||
cutUntil sLen;
|
||
in
|
||
recurse 0 0;
|
||
|
||
/* Return the suffix of the second argument if the first argument matches
|
||
its prefix.
|
||
|
||
Example:
|
||
removePrefix "foo." "foo.bar.baz"
|
||
=> "bar.baz"
|
||
removePrefix "xxx" "foo.bar.baz"
|
||
=> "foo.bar.baz"
|
||
*/
|
||
removePrefix = pre: s:
|
||
let
|
||
preLen = stringLength pre;
|
||
sLen = stringLength s;
|
||
in
|
||
if hasPrefix pre s then
|
||
substring preLen (sLen - preLen) s
|
||
else
|
||
s;
|
||
|
||
/* Return the prefix of the second argument if the first argument matches
|
||
its suffix.
|
||
|
||
Example:
|
||
removeSuffix "front" "homefront"
|
||
=> "home"
|
||
removeSuffix "xxx" "homefront"
|
||
=> "homefront"
|
||
*/
|
||
removeSuffix = suf: s:
|
||
let
|
||
sufLen = stringLength suf;
|
||
sLen = stringLength s;
|
||
in
|
||
if sufLen <= sLen && suf == substring (sLen - sufLen) sufLen s then
|
||
substring 0 (sLen - sufLen) s
|
||
else
|
||
s;
|
||
|
||
/* Return true iff string v1 denotes a version older than v2.
|
||
|
||
Example:
|
||
versionOlder "1.1" "1.2"
|
||
=> true
|
||
versionOlder "1.1" "1.1"
|
||
=> false
|
||
*/
|
||
versionOlder = v1: v2: builtins.compareVersions v2 v1 == 1;
|
||
|
||
/* Return true iff string v1 denotes a version equal to or newer than v2.
|
||
|
||
Example:
|
||
versionAtLeast "1.1" "1.0"
|
||
=> true
|
||
versionAtLeast "1.1" "1.1"
|
||
=> true
|
||
versionAtLeast "1.1" "1.2"
|
||
=> false
|
||
*/
|
||
versionAtLeast = v1: v2: !versionOlder v1 v2;
|
||
|
||
/* This function takes an argument that's either a derivation or a
|
||
derivation's "name" attribute and extracts the version part from that
|
||
argument.
|
||
|
||
Example:
|
||
getVersion "youtube-dl-2016.01.01"
|
||
=> "2016.01.01"
|
||
getVersion pkgs.youtube-dl
|
||
=> "2016.01.01"
|
||
*/
|
||
getVersion = x:
|
||
let
|
||
parse = drv: (builtins.parseDrvName drv).version;
|
||
in if isString x
|
||
then parse x
|
||
else x.version or (parse x.name);
|
||
|
||
/* Extract name with version from URL. Ask for separator which is
|
||
supposed to start extension.
|
||
|
||
Example:
|
||
nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "-"
|
||
=> "nix"
|
||
nameFromURL "https://nixos.org/releases/nix/nix-1.7/nix-1.7-x86_64-linux.tar.bz2" "_"
|
||
=> "nix-1.7-x86"
|
||
*/
|
||
nameFromURL = url: sep:
|
||
let
|
||
components = splitString "/" url;
|
||
filename = lib.last components;
|
||
name = builtins.head (splitString sep filename);
|
||
in assert name != filename; name;
|
||
|
||
/* Create an --{enable,disable}-<feat> string that can be passed to
|
||
standard GNU Autoconf scripts.
|
||
|
||
Example:
|
||
enableFeature true "shared"
|
||
=> "--enable-shared"
|
||
enableFeature false "shared"
|
||
=> "--disable-shared"
|
||
*/
|
||
enableFeature = enable: feat: "--${if enable then "enable" else "disable"}-${feat}";
|
||
|
||
/* Create a fixed width string with additional prefix to match
|
||
required width.
|
||
|
||
Example:
|
||
fixedWidthString 5 "0" (toString 15)
|
||
=> "00015"
|
||
*/
|
||
fixedWidthString = width: filler: str:
|
||
let
|
||
strw = lib.stringLength str;
|
||
reqWidth = width - (lib.stringLength filler);
|
||
in
|
||
assert strw <= width;
|
||
if strw == width then str else filler + fixedWidthString reqWidth filler str;
|
||
|
||
/* Format a number adding leading zeroes up to fixed width.
|
||
|
||
Example:
|
||
fixedWidthNumber 5 15
|
||
=> "00015"
|
||
*/
|
||
fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
|
||
|
||
/* Check whether a value is a store path.
|
||
|
||
Example:
|
||
isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/bin/python"
|
||
=> false
|
||
isStorePath "/nix/store/d945ibfx9x185xf04b890y4f9g3cbb63-python-2.7.11/"
|
||
=> true
|
||
isStorePath pkgs.python
|
||
=> true
|
||
isStorePath [] || isStorePath 42 || isStorePath {} || …
|
||
=> false
|
||
*/
|
||
isStorePath = x:
|
||
builtins.isString x
|
||
&& builtins.substring 0 1 (toString x) == "/"
|
||
&& dirOf (builtins.toPath x) == builtins.storeDir;
|
||
|
||
/* Convert string to int
|
||
Obviously, it is a bit hacky to use fromJSON that way.
|
||
|
||
Example:
|
||
toInt "1337"
|
||
=> 1337
|
||
toInt "-4"
|
||
=> -4
|
||
toInt "3.14"
|
||
=> error: floating point JSON numbers are not supported
|
||
*/
|
||
toInt = str:
|
||
let may_be_int = builtins.fromJSON str; in
|
||
if builtins.isInt may_be_int
|
||
then may_be_int
|
||
else throw "Could not convert ${str} to int.";
|
||
|
||
/* Read a list of paths from `file', relative to the `rootPath'. Lines
|
||
beginning with `#' are treated as comments and ignored. Whitespace
|
||
is significant.
|
||
|
||
NOTE: this function is not performant and should be avoided
|
||
|
||
Example:
|
||
readPathsFromFile /prefix
|
||
./pkgs/development/libraries/qt-5/5.4/qtbase/series
|
||
=> [ "/prefix/dlopen-resolv.patch" "/prefix/tzdir.patch"
|
||
"/prefix/dlopen-libXcursor.patch" "/prefix/dlopen-openssl.patch"
|
||
"/prefix/dlopen-dbus.patch" "/prefix/xdg-config-dirs.patch"
|
||
"/prefix/nix-profiles-library-paths.patch"
|
||
"/prefix/compose-search-path.patch" ]
|
||
*/
|
||
readPathsFromFile = rootPath: file:
|
||
let
|
||
root = toString rootPath;
|
||
lines = lib.splitString "\n" (builtins.readFile file);
|
||
removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
|
||
relativePaths = removeComments lines;
|
||
absolutePaths = builtins.map (path: builtins.toPath (root + "/" + path)) relativePaths;
|
||
in
|
||
absolutePaths;
|
||
|
||
/* Read the contents of a file removing the trailing \n
|
||
|
||
Example:
|
||
$ echo "1.0" > ./version
|
||
|
||
fileContents ./version
|
||
=> "1.0"
|
||
*/
|
||
fileContents = file: removeSuffix "\n" (builtins.readFile file);
|
||
}
|