Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at release-16.03 257 lines 8.3 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 # Construct a binary search path (such as $PATH) containing the 56 # binaries for a set of packages, e.g. "${pkg1}/bin:${pkg2}/bin:...". 57 makeBinPath = makeSearchPath "bin"; 58 59 60 # Idem for Perl search paths. 61 makePerlPath = makeSearchPath "lib/perl5/site_perl"; 62 63 64 # Dependening on the boolean `cond', return either the given string 65 # or the empty string. 66 optionalString = cond: string: if cond then string else ""; 67 68 69 # Determine whether a string has given prefix/suffix. 70 hasPrefix = pref: str: 71 substring 0 (stringLength pref) str == pref; 72 hasSuffix = suff: str: 73 let 74 lenStr = stringLength str; 75 lenSuff = stringLength suff; 76 in lenStr >= lenSuff && 77 substring (lenStr - lenSuff) lenStr str == suff; 78 79 80 # Convert a string to a list of characters (i.e. singleton strings). 81 # For instance, "abc" becomes ["a" "b" "c"]. This allows you to, 82 # e.g., map a function over each character. However, note that this 83 # will likely be horribly inefficient; Nix is not a general purpose 84 # programming language. Complex string manipulations should, if 85 # appropriate, be done in a derivation. 86 stringToCharacters = s: 87 map (p: substring p 1 s) (lib.range 0 (stringLength s - 1)); 88 89 90 # Manipulate a string charactter by character and replace them by 91 # strings before concatenating the results. 92 stringAsChars = f: s: 93 concatStrings ( 94 map f (stringToCharacters s) 95 ); 96 97 98 # Escape occurrence of the elements of ‘list’ in ‘string’ by 99 # prefixing it with a backslash. For example, ‘escape ["(" ")"] 100 # "(foo)"’ returns the string ‘\(foo\)’. 101 escape = list: replaceChars list (map (c: "\\${c}") list); 102 103 104 # Escape all characters that have special meaning in the Bourne shell. 105 escapeShellArg = lib.escape (stringToCharacters "\\ ';$`()|<>\t*[]"); 106 107 108 # Obsolete - use replaceStrings instead. 109 replaceChars = builtins.replaceStrings or ( 110 del: new: s: 111 let 112 substList = lib.zipLists del new; 113 subst = c: 114 let found = lib.findFirst (sub: sub.fst == c) null substList; in 115 if found == null then 116 c 117 else 118 found.snd; 119 in 120 stringAsChars subst s); 121 122 123 # Case conversion utilities. 124 lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz"; 125 upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 126 toLower = replaceChars upperChars lowerChars; 127 toUpper = replaceChars lowerChars upperChars; 128 129 130 # Appends string context from another string. 131 addContextFrom = a: b: substring 0 0 a + b; 132 133 134 # Cut a string with a separator and produces a list of strings which 135 # were separated by this separator; e.g., `splitString "." 136 # "foo.bar.baz"' returns ["foo" "bar" "baz"]. 137 splitString = _sep: _s: 138 let 139 sep = addContextFrom _s _sep; 140 s = addContextFrom _sep _s; 141 sepLen = stringLength sep; 142 sLen = stringLength s; 143 lastSearch = sLen - sepLen; 144 startWithSep = startAt: 145 substring startAt sepLen s == sep; 146 147 recurse = index: startAt: 148 let cutUntil = i: [(substring startAt (i - startAt) s)]; in 149 if index < lastSearch then 150 if startWithSep index then 151 let restartAt = index + sepLen; in 152 cutUntil index ++ recurse restartAt restartAt 153 else 154 recurse (index + 1) startAt 155 else 156 cutUntil sLen; 157 in 158 recurse 0 0; 159 160 161 # return the suffix of the second argument if the first argument match its 162 # prefix. e.g., 163 # `removePrefix "foo." "foo.bar.baz"' returns "bar.baz". 164 removePrefix = pre: s: 165 let 166 preLen = stringLength pre; 167 sLen = stringLength s; 168 in 169 if hasPrefix pre s then 170 substring preLen (sLen - preLen) s 171 else 172 s; 173 174 removeSuffix = suf: s: 175 let 176 sufLen = stringLength suf; 177 sLen = stringLength s; 178 in 179 if sufLen <= sLen && suf == substring (sLen - sufLen) sufLen s then 180 substring 0 (sLen - sufLen) s 181 else 182 s; 183 184 # Return true iff string v1 denotes a version older than v2. 185 versionOlder = v1: v2: builtins.compareVersions v2 v1 == 1; 186 187 188 # Return true iff string v1 denotes a version equal to or newer than v2. 189 versionAtLeast = v1: v2: !versionOlder v1 v2; 190 191 192 # This function takes an argument that's either a derivation or a 193 # derivation's "name" attribute and extracts the version part from that 194 # argument. For example: 195 # 196 # lib.getVersion "youtube-dl-2016.01.01" ==> "2016.01.01" 197 # lib.getVersion pkgs.youtube-dl ==> "2016.01.01" 198 getVersion = x: (builtins.parseDrvName (x.name or x)).version; 199 200 201 # Extract name with version from URL. Ask for separator which is 202 # supposed to start extension. 203 nameFromURL = url: sep: 204 let 205 components = splitString "/" url; 206 filename = lib.last components; 207 name = builtins.head (splitString sep filename); 208 in assert name != filename; name; 209 210 211 # Create an --{enable,disable}-<feat> string that can be passed to 212 # standard GNU Autoconf scripts. 213 enableFeature = enable: feat: "--${if enable then "enable" else "disable"}-${feat}"; 214 215 216 # Create a fixed width string with additional prefix to match 217 # required width. 218 fixedWidthString = width: filler: str: 219 let 220 strw = lib.stringLength str; 221 reqWidth = width - (lib.stringLength filler); 222 in 223 assert strw <= width; 224 if strw == width then str else filler + fixedWidthString reqWidth filler str; 225 226 227 # Format a number adding leading zeroes up to fixed width. 228 fixedWidthNumber = width: n: fixedWidthString width "0" (toString n); 229 230 231 # Check whether a value is a store path. 232 isStorePath = x: builtins.substring 0 1 (toString x) == "/" && dirOf (builtins.toPath x) == builtins.storeDir; 233 234 # Convert string to int 235 # Obviously, it is a bit hacky to use fromJSON that way. 236 toInt = str: 237 let may_be_int = builtins.fromJSON str; in 238 if builtins.isInt may_be_int 239 then may_be_int 240 else throw "Could not convert ${str} to int."; 241 242 # Read a list of paths from `file', relative to the `rootPath'. Lines 243 # beginning with `#' are treated as comments and ignored. Whitespace 244 # is significant. 245 readPathsFromFile = rootPath: file: 246 let 247 root = toString rootPath; 248 lines = 249 builtins.map (lib.removeSuffix "\n") 250 (lib.splitString "\n" (builtins.readFile file)); 251 removeComments = lib.filter (line: !(lib.hasPrefix "#" line)); 252 relativePaths = removeComments lines; 253 absolutePaths = builtins.map (path: builtins.toPath (root + "/" + path)) relativePaths; 254 in 255 absolutePaths; 256 257}