at 15.09-beta 7.1 kB view raw
1/* String manipulation functions. */ 2 3let lib = import ./default.nix; 4 5inherit (builtins) length; 6 7in 8 9rec { 10 11 inherit (builtins) stringLength substring head tail isString replaceStrings; 12 13 14 # Concatenate a list of strings. 15 concatStrings = 16 if builtins ? concatStringsSep then 17 builtins.concatStringsSep "" 18 else 19 lib.foldl' (x: y: x + y) ""; 20 21 22 # Map a function over a list and concatenate the resulting strings. 23 concatMapStrings = f: list: concatStrings (map f list); 24 concatImapStrings = f: list: concatStrings (lib.imap f list); 25 26 27 # Place an element between each element of a list, e.g., 28 # `intersperse "," ["a" "b" "c"]' returns ["a" "," "b" "," "c"]. 29 intersperse = separator: list: 30 if list == [] || length list == 1 31 then list 32 else tail (lib.concatMap (x: [separator x]) list); 33 34 35 # Concatenate a list of strings with a separator between each element, e.g. 36 # concatStringsSep " " ["foo" "bar" "xyzzy"] == "foo bar xyzzy" 37 concatStringsSep = builtins.concatStringsSep or (separator: list: 38 concatStrings (intersperse separator list)); 39 40 concatMapStringsSep = sep: f: list: concatStringsSep sep (map f list); 41 concatImapStringsSep = sep: f: list: concatStringsSep sep (lib.imap f list); 42 43 44 # Construct a Unix-style search path consisting of each `subDir" 45 # directory of the given list of packages. For example, 46 # `makeSearchPath "bin" ["x" "y" "z"]' returns "x/bin:y/bin:z/bin". 47 makeSearchPath = subDir: packages: 48 concatStringsSep ":" (map (path: path + "/" + subDir) packages); 49 50 51 # Construct a library search path (such as RPATH) containing the 52 # libraries for a set of packages, e.g. "${pkg1}/lib:${pkg2}/lib:...". 53 makeLibraryPath = makeSearchPath "lib"; 54 55 56 # Idem for Perl search paths. 57 makePerlPath = makeSearchPath "lib/perl5/site_perl"; 58 59 60 # Dependening on the boolean `cond', return either the given string 61 # or the empty string. 62 optionalString = cond: string: if cond then string else ""; 63 64 65 # Determine whether a string has given prefix/suffix. 66 hasPrefix = pref: str: 67 substring 0 (stringLength pref) str == pref; 68 hasSuffix = suff: str: 69 let 70 lenStr = stringLength str; 71 lenSuff = stringLength suff; 72 in lenStr >= lenSuff && 73 substring (lenStr - lenSuff) lenStr str == suff; 74 75 76 # Convert a string to a list of characters (i.e. singleton strings). 77 # For instance, "abc" becomes ["a" "b" "c"]. This allows you to, 78 # e.g., map a function over each character. However, note that this 79 # will likely be horribly inefficient; Nix is not a general purpose 80 # programming language. Complex string manipulations should, if 81 # appropriate, be done in a derivation. 82 stringToCharacters = s: 83 map (p: substring p 1 s) (lib.range 0 (stringLength s - 1)); 84 85 86 # Manipulate a string charactter by character and replace them by 87 # strings before concatenating the results. 88 stringAsChars = f: s: 89 concatStrings ( 90 map f (stringToCharacters s) 91 ); 92 93 94 # Escape occurrence of the elements of ‘list’ in ‘string’ by 95 # prefixing it with a backslash. For example, ‘escape ["(" ")"] 96 # "(foo)"’ returns the string ‘\(foo\)’. 97 escape = list: replaceChars list (map (c: "\\${c}") list); 98 99 100 # Escape all characters that have special meaning in the Bourne shell. 101 escapeShellArg = lib.escape (stringToCharacters "\\ ';$`()|<>\t*[]"); 102 103 104 # Obsolete - use replaceStrings instead. 105 replaceChars = builtins.replaceStrings or ( 106 del: new: s: 107 let 108 substList = lib.zipLists del new; 109 subst = c: 110 let found = lib.findFirst (sub: sub.fst == c) null substList; in 111 if found == null then 112 c 113 else 114 found.snd; 115 in 116 stringAsChars subst s); 117 118 119 # Case conversion utilities. 120 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz"; 121 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 122 toLower = replaceChars upperChars lowerChars; 123 toUpper = replaceChars lowerChars upperChars; 124 125 126 # Appends string context from another string. 127 addContextFrom = a: b: substring 0 0 a + b; 128 129 130 # Cut a string with a separator and produces a list of strings which 131 # were separated by this separator; e.g., `splitString "." 132 # "foo.bar.baz"' returns ["foo" "bar" "baz"]. 133 splitString = _sep: _s: 134 let 135 sep = addContextFrom _s _sep; 136 s = addContextFrom _sep _s; 137 sepLen = stringLength sep; 138 sLen = stringLength s; 139 lastSearch = sLen - sepLen; 140 startWithSep = startAt: 141 substring startAt sepLen s == sep; 142 143 recurse = index: startAt: 144 let cutUntil = i: [(substring startAt (i - startAt) s)]; in 145 if index < lastSearch then 146 if startWithSep index then 147 let restartAt = index + sepLen; in 148 cutUntil index ++ recurse restartAt restartAt 149 else 150 recurse (index + 1) startAt 151 else 152 cutUntil sLen; 153 in 154 recurse 0 0; 155 156 157 # return the suffix of the second argument if the first argument match its 158 # prefix. e.g., 159 # `removePrefix "foo." "foo.bar.baz"' returns "bar.baz". 160 removePrefix = pre: s: 161 let 162 preLen = stringLength pre; 163 sLen = stringLength s; 164 in 165 if hasPrefix pre s then 166 substring preLen (sLen - preLen) s 167 else 168 s; 169 170 removeSuffix = suf: s: 171 let 172 sufLen = stringLength suf; 173 sLen = stringLength s; 174 in 175 if sufLen <= sLen && suf == substring (sLen - sufLen) sufLen s then 176 substring 0 (sLen - sufLen) s 177 else 178 s; 179 180 # Return true iff string v1 denotes a version older than v2. 181 versionOlder = v1: v2: builtins.compareVersions v2 v1 == 1; 182 183 184 # Return true iff string v1 denotes a version equal to or newer than v2. 185 versionAtLeast = v1: v2: !versionOlder v1 v2; 186 187 188 # Get the version of the specified derivation, as specified in its 189 # ‘name’ attribute. 190 getVersion = drv: (builtins.parseDrvName drv.name).version; 191 192 193 # Extract name with version from URL. Ask for separator which is 194 # supposed to start extension. 195 nameFromURL = url: sep: 196 let 197 components = splitString "/" url; 198 filename = lib.last components; 199 name = builtins.head (splitString sep filename); 200 in assert name != filename; name; 201 202 203 # Create an --{enable,disable}-<feat> string that can be passed to 204 # standard GNU Autoconf scripts. 205 enableFeature = enable: feat: "--${if enable then "enable" else "disable"}-${feat}"; 206 207 208 # Create a fixed width string with additional prefix to match 209 # required width. 210 fixedWidthString = width: filler: str: 211 let 212 strw = lib.stringLength str; 213 reqWidth = width - (lib.stringLength filler); 214 in 215 assert strw <= width; 216 if strw == width then str else filler + fixedWidthString reqWidth filler str; 217 218 219 # Format a number adding leading zeroes up to fixed width. 220 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n); 221 222 223 # Check whether a value is a store path. 224 isStorePath = x: builtins.substring 0 1 (toString x) == "/" && dirOf (builtins.toPath x) == builtins.storeDir; 225 226}