Files
nixpkgs/lib/strings.nix
T

501 lines
14 KiB
Nix
Raw Normal View History

/* String manipulation functions. */
2017-07-28 20:05:35 -04:00
{ lib }:
let
2009-05-24 10:57:46 +00:00
inherit (builtins) length;
2009-05-24 10:57:46 +00:00
in
rec {
2013-11-12 13:48:19 +01:00
2015-07-24 15:45:41 +02:00
inherit (builtins) stringLength substring head tail isString replaceStrings;
2016-02-28 23:27:35 +00:00
/* Concatenate a list of strings.
2016-02-28 23:27:35 +00:00
Example:
concatStrings ["foo" "bar"]
=> "foobar"
*/
concatStrings = builtins.concatStringsSep "";
2016-02-28 23:27:35 +00:00
/* Map a function over a list and concatenate the resulting strings.
2016-02-28 23:27:35 +00:00
Example:
concatMapStrings (x: "a" + x) ["foo" "bar"]
=> "afooabar"
*/
concatMapStrings = f: list: concatStrings (map f list);
2016-02-28 23:27:35 +00:00
/* 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"
*/
2017-07-04 23:29:23 +01:00
concatImapStrings = f: list: concatStrings (lib.imap1 f list);
2012-03-28 15:43:39 +00:00
2016-02-28 23:27:35 +00:00
/* Place an element between each element of a list
2016-02-28 23:27:35 +00:00
Example:
intersperse "/" ["usr" "local" "bin"]
=> ["usr" "/" "local" "/" "bin"].
*/
intersperse = separator: list:
2012-08-13 14:19:31 -04:00
if list == [] || length list == 1
then list
2015-07-24 15:55:39 +02:00
else tail (lib.concatMap (x: [separator x]) list);
2016-02-28 23:27:35 +00:00
/* Concatenate a list of strings with a separator between each element
2016-02-28 23:27:35 +00:00
Example:
concatStringsSep "/" ["usr" "local" "bin"]
=> "usr/local/bin"
*/
2015-07-24 02:38:46 +02:00
concatStringsSep = builtins.concatStringsSep or (separator: list:
concatStrings (intersperse separator list));
2016-02-28 23:27:35 +00:00
/* 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);
2016-02-28 23:27:35 +00:00
/* First imaps over the list and then concatenates it.
Example:
concatImapStringsSep "-" (pos: x: toString (x / pos)) [ 6 6 6 ]
=> "6-3-2"
*/
2017-07-04 23:29:23 +01:00
concatImapStringsSep = sep: f: list: concatStringsSep sep (lib.imap1 f list);
2016-02-28 23:27:35 +00:00
/* Construct a Unix-style search path consisting of each `subDir"
directory of the given list of packages.
2016-02-28 23:27:35 +00:00
Example:
makeSearchPath "bin" ["/root" "/usr" "/usr/local"]
=> "/root/bin:/usr/bin:/usr/local/bin"
makeSearchPath "bin" ["/"]
=> "//bin"
*/
2012-03-28 15:43:39 +00:00
makeSearchPath = subDir: packages:
concatStringsSep ":" (map (path: path + "/" + subDir) packages);
/* Construct a Unix-style search path, using given package output.
2016-04-13 15:33:21 +03:00
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"
2016-04-13 15:33:21 +03:00
*/
makeSearchPathOutput = output: subDir: pkgs: makeSearchPath subDir (map (lib.getOutput output) pkgs);
2016-04-13 15:33:21 +03:00
2016-02-28 23:27:35 +00:00
/* Construct a library search path (such as RPATH) containing the
libraries for a set of packages
2016-02-28 23:27:35 +00:00
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";
2016-02-28 23:27:35 +00:00
/* 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";
2016-02-28 23:27:35 +00:00
/* 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";
2012-03-28 15:43:39 +00:00
2017-04-19 14:41:28 -05:00
/* Depending on the boolean `cond', return either the given string
or the empty string. Useful to concatenate against a bigger string.
2016-02-28 23:27:35 +00:00
Example:
optionalString true "some-string"
=> "some-string"
optionalString false "some-string"
=> ""
*/
optionalString = cond: string: if cond then string else "";
2016-02-28 23:27:35 +00:00
/* Determine whether a string has given prefix.
2012-03-28 15:43:39 +00:00
2016-02-28 23:27:35 +00:00
Example:
hasPrefix "foo" "foobar"
=> true
hasPrefix "foo" "barfoo"
=> false
*/
hasPrefix = pref: str:
2015-07-24 15:48:29 +02:00
substring 0 (stringLength pref) str == pref;
2016-02-28 23:27:35 +00:00
/* Determine whether a string has given suffix.
Example:
hasSuffix "foo" "foobar"
=> false
hasSuffix "foo" "barfoo"
=> true
*/
2016-08-10 20:06:28 +02:00
hasSuffix = suffix: content:
let
2016-08-10 20:06:28 +02:00
lenContent = stringLength content;
lenSuffix = stringLength suffix;
in lenContent >= lenSuffix &&
substring (lenContent - lenSuffix) lenContent content == suffix;
2016-02-28 23:27:35 +00:00
/* 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.
2016-02-28 23:27:35 +00:00
Example:
stringToCharacters ""
=> [ ]
stringToCharacters "abc"
=> [ "a" "b" "c" ]
stringToCharacters "💩"
=> [ "" "" "" "" ]
*/
2015-07-24 15:45:41 +02:00
stringToCharacters = s:
map (p: substring p 1 s) (lib.range 0 (stringLength s - 1));
2016-02-28 23:27:35 +00:00
/* Manipulate a string character by character and replace them by
strings before concatenating the results.
2012-03-28 15:43:39 +00:00
2016-02-28 23:27:35 +00:00
Example:
stringAsChars (x: if x == "a" then "i" else x) "nax"
=> "nix"
*/
stringAsChars = f: s:
concatStrings (
map f (stringToCharacters s)
);
2016-02-28 23:27:35 +00:00
/* Escape occurrence of the elements of list in string by
prefixing it with a backslash.
2012-03-28 15:43:39 +00:00
2016-02-28 23:27:35 +00:00
Example:
escape ["(" ")"] "(foo)"
=> "\\(foo\\)"
*/
2015-07-24 15:45:41 +02:00
escape = list: replaceChars list (map (c: "\\${c}") list);
2016-06-20 18:31:49 +02:00
/* Quote string to be used safely within the Bourne shell.
2012-03-28 15:43:39 +00:00
2016-02-28 23:27:35 +00:00
Example:
2016-06-20 18:31:49 +02:00
escapeShellArg "esc'ape\nme"
=> "'esc'\\''ape\nme'"
2016-02-28 23:27:35 +00:00
*/
2016-06-20 18:31:49 +02:00
escapeShellArg = arg: "'${replaceStrings ["'"] ["'\\''"] (toString arg)}'";
2016-06-12 18:11:37 +01:00
2016-06-20 18:31:49 +02:00
/* Quote all arguments to be safely passed to the Bourne shell.
2016-06-12 18:11:37 +01:00
Example:
2016-06-20 18:31:49 +02:00
escapeShellArgs ["one" "two three" "four'five"]
=> "'one' 'two three' 'four'\\''five'"
2016-06-12 18:11:37 +01:00
*/
escapeShellArgs = concatMapStringsSep " " escapeShellArg;
2016-02-28 23:27:35 +00:00
/* Obsolete - use replaceStrings instead. */
2015-07-24 15:45:41 +02:00
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
2015-07-24 15:45:41 +02:00
stringAsChars subst s);
2015-07-24 15:45:41 +02:00
# Case conversion utilities.
lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
2016-02-28 23:27:35 +00:00
/* Converts an ASCII string to lower-case.
Example:
toLower "HOME"
=> "home"
*/
toLower = replaceChars upperChars lowerChars;
2016-02-28 23:27:35 +00:00
/* Converts an ASCII string to upper-case.
Example:
toUpper "home"
2016-02-28 23:27:35 +00:00
=> "HOME"
*/
toUpper = replaceChars lowerChars upperChars;
2016-02-28 23:27:35 +00:00
/* Appends string context from another string. This is an implementation
detail of Nix.
2015-07-24 15:45:41 +02:00
2016-02-28 23:27:35 +00:00
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;
2016-02-28 23:27:35 +00:00
/* Cut a string with a separator and produces a list of strings which
were separated by this separator.
2009-09-28 18:22:37 +00:00
NOTE: this function is not performant and should never be used.
2016-02-28 23:27:35 +00:00
Example:
splitString "." "foo.bar.baz"
=> [ "foo" "bar" "baz" ]
splitString "/" "/usr/local/bin"
=> [ "" "usr" "local" "bin" ]
*/
splitString = _sep: _s:
2009-09-28 18:22:37 +00:00
let
sep = addContextFrom _s _sep;
s = addContextFrom _sep _s;
2009-09-28 18:22:37 +00:00
sepLen = stringLength sep;
sLen = stringLength s;
lastSearch = sLen - sepLen;
2009-09-28 18:22:37 +00:00
startWithSep = startAt:
substring startAt sepLen s == sep;
recurse = index: startAt:
let cutUntil = i: [(substring startAt (i - startAt) s)]; in
if index <= lastSearch then
2009-09-28 18:22:37 +00:00
if startWithSep index then
let restartAt = index + sepLen; in
2009-09-28 18:22:37 +00:00
cutUntil index ++ recurse restartAt restartAt
else
recurse (index + 1) startAt
2009-09-28 18:22:37 +00:00
else
cutUntil sLen;
in
recurse 0 0;
2009-10-06 09:21:39 +00:00
2016-02-28 23:27:35 +00:00
/* Return the suffix of the second argument if the first argument matches
its prefix.
2012-03-28 15:43:39 +00:00
2016-02-28 23:27:35 +00:00
Example:
removePrefix "foo." "foo.bar.baz"
=> "bar.baz"
removePrefix "xxx" "foo.bar.baz"
=> "foo.bar.baz"
*/
2009-10-06 09:21:39 +00:00
removePrefix = pre: s:
let
preLen = stringLength pre;
sLen = stringLength s;
in
if hasPrefix pre s then
substring preLen (sLen - preLen) s
2009-10-06 09:21:39 +00:00
else
s;
2016-02-28 23:27:35 +00:00
/* 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
2015-07-24 15:48:29 +02:00
if sufLen <= sLen && suf == substring (sLen - sufLen) sufLen s then
substring 0 (sLen - sufLen) s
else
s;
2016-02-28 23:27:35 +00:00
/* 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;
2016-05-19 01:26:04 +03:00
/* Return true iff string v1 denotes a version equal to or newer than v2.
2016-02-28 23:27:35 +00:00
Example:
versionAtLeast "1.1" "1.0"
=> true
versionAtLeast "1.1" "1.1"
=> true
versionAtLeast "1.1" "1.2"
=> false
*/
2013-07-17 11:14:26 +02:00
versionAtLeast = v1: v2: !versionOlder v1 v2;
2016-02-28 23:27:35 +00:00
/* This function takes an argument that's either a derivation or a
derivation's "name" attribute and extracts the version part from that
argument.
2013-07-17 11:14:26 +02:00
2016-02-28 23:27:35 +00:00
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);
2016-02-28 23:27:35 +00:00
/* Extract name with version from URL. Ask for separator which is
supposed to start extension.
2016-02-28 23:27:35 +00:00
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"
*/
2015-07-24 15:48:29 +02:00
nameFromURL = url: sep:
let
components = splitString "/" url;
filename = lib.last components;
name = builtins.head (splitString sep filename);
in assert name != filename; name;
2016-02-28 23:27:35 +00:00
/* Create an --{enable,disable}-<feat> string that can be passed to
standard GNU Autoconf scripts.
2016-02-28 23:27:35 +00:00
Example:
enableFeature true "shared"
=> "--enable-shared"
enableFeature false "shared"
=> "--disable-shared"
*/
enableFeature = enable: feat: "--${if enable then "enable" else "disable"}-${feat}";
2016-02-28 23:27:35 +00:00
/* Create a fixed width string with additional prefix to match
required width.
2015-07-23 17:41:35 +02:00
2016-02-28 23:27:35 +00:00
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;
2016-02-28 23:27:35 +00:00
/* Format a number adding leading zeroes up to fixed width.
2015-07-23 17:41:35 +02:00
2016-02-28 23:27:35 +00:00
Example:
fixedWidthNumber 5 15
=> "00015"
*/
fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
2016-02-28 23:27:35 +00:00
/* Check whether a value is a store path.
2016-02-28 23:27:35 +00:00
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
2017-05-30 01:20:37 +02:00
isStorePath [] || isStorePath 42 || isStorePath {} ||
=> false
2016-02-28 23:27:35 +00:00
*/
2017-05-30 01:20:37 +02:00
isStorePath = x:
builtins.isString x
&& builtins.substring 0 1 (toString x) == "/"
&& dirOf (builtins.toPath x) == builtins.storeDir;
2016-02-28 23:27:35 +00:00
/* 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.";
2016-02-28 23:27:35 +00:00
/* 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" ]
*/
2015-12-12 19:33:02 -06:00
readPathsFromFile = rootPath: file:
let
root = toString rootPath;
2017-03-19 14:51:04 +01:00
lines = lib.splitString "\n" (builtins.readFile file);
2017-03-19 10:16:44 +01:00
removeComments = lib.filter (line: line != "" && !(lib.hasPrefix "#" line));
2015-12-12 19:33:02 -06:00
relativePaths = removeComments lines;
absolutePaths = builtins.map (path: builtins.toPath (root + "/" + path)) relativePaths;
in
absolutePaths;
2016-07-29 17:15:37 +09:00
/* 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);
2009-02-24 16:19:08 +00:00
}