summary refs log tree commit diff
path: root/lib/strings.nix
blob: 39112407c5708cf27b6be60b9c885bf7e98c0bb8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/* String manipulation functions. */

let lib = import ./default.nix;

inherit (builtins) length;

in

rec {

  inherit (builtins) stringLength substring head tail isString;


  # Concatenate a list of strings.
  concatStrings = lib.fold (x: y: x + y) "";


  # Map a function over a list and concatenate the resulting strings.
  concatMapStrings = f: list: concatStrings (map f list);
  concatImapStrings = f: list: concatStrings (lib.imap f list);


  # Place an element between each element of a list, e.g.,
  # `intersperse "," ["a" "b" "c"]' returns ["a" "," "b" "," "c"].
  intersperse = separator: list:
    if list == [] || length list == 1
    then list
    else [(head list) separator]
         ++ (intersperse separator (tail list));


  # Concatenate a list of strings with a separator between each element, e.g.
  # concatStringsSep " " ["foo" "bar" "xyzzy"] == "foo bar xyzzy"
  concatStringsSep = separator: list:
    concatStrings (intersperse separator list);

  concatMapStringsSep = sep: f: list: concatStringsSep sep (map f list);
  concatImapStringsSep = sep: f: list: concatStringsSep sep (lib.imap f list);


  # Construct a Unix-style search path consisting of each `subDir"
  # directory of the given list of packages.  For example,
  # `makeSearchPath "bin" ["x" "y" "z"]' returns "x/bin:y/bin:z/bin".
  makeSearchPath = subDir: packages:
    concatStringsSep ":" (map (path: path + "/" + subDir) packages);


  # Construct a library search path (such as RPATH) containing the
  # libraries for a set of packages, e.g. "${pkg1}/lib:${pkg2}/lib:...".
  makeLibraryPath = makeSearchPath "lib";


  # Idem for Perl search paths.
  makePerlPath = makeSearchPath "lib/perl5/site_perl";


  # Dependening on the boolean `cond', return either the given string
  # or the empty string.
  optionalString = cond: string: if cond then string else "";


  # Determine whether a string has given prefix/suffix.
  hasPrefix = pref: str:
    eqStrings (substring 0 (stringLength pref) str) pref;
  hasSuffix = suff: str:
    let
      lenStr = stringLength str;
      lenSuff = stringLength suff;
    in lenStr >= lenSuff &&
       eqStrings (substring (lenStr - lenSuff) lenStr str) suff;


  # Convert a string to a list of characters (i.e. singleton strings).
  # For instance, "abc" becomes ["a" "b" "c"].  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.
  stringToCharacters = s: let l = stringLength s; in
    if l == 0
    then []
    else map (p: substring p 1 s) (lib.range 0 (l - 1));


  # Manipulate a string charcater by character and replace them by strings
  # before concatenating the results.
  stringAsChars = f: s:
    concatStrings (
      map f (stringToCharacters s)
    );


  # same as vim escape function.
  # Each character contained in list is prefixed by "\"
  escape = list : string :
    stringAsChars (c: if lib.elem c list then "\\${c}" else c) string;


  # still ugly slow. But more correct now
  # [] for zsh
  escapeShellArg = lib.escape (stringToCharacters "\\ ';$`()|<>\t*[]");


  # replace characters by their substitutes.  This function is equivalent to
  # the `tr' command except that one character can be replace by multiple
  # ones.  e.g.,
  # replaceChars ["<" ">"] ["&lt;" "&gt;"] "<foo>" returns "&lt;foo&gt;".
  replaceChars = del: new: s:
    let
      subst = c:
        (lib.fold
          (sub: res: if sub.fst == c then sub else res)
          {fst = c; snd = c;} (lib.zipLists del new)
        ).snd;
    in
      stringAsChars subst s;


  # Case conversion utilities
  lowerChars = stringToCharacters "abcdefghijklmnopqrstuvwxyz";
  upperChars = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  toLower = replaceChars upperChars lowerChars;
  toUpper = replaceChars lowerChars upperChars;

  # Appends string context from another string
  addContextFrom = a: b: substring 0 0 a + b;

  # Compares strings not requiring context equality
  # Obviously, a workaround but works on all Nix versions
  eqStrings = a: b: addContextFrom b a == addContextFrom a b;


  # Cut a string with a separator and produces a list of strings which were
  # separated by this separator. e.g.,
  # `splitString "." "foo.bar.baz"' returns ["foo" "bar" "baz"].
  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 match its
  # prefix. e.g.,
  # `removePrefix "foo." "foo.bar.baz"' returns "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;

  removeSuffix = suf: s:
    let
      sufLen = stringLength suf;
      sLen = stringLength s;
    in
      if sufLen <= sLen && eqStrings 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.
  versionOlder = v1: v2: builtins.compareVersions v2 v1 == 1;


  # Return true iff string v1 denotes a version equal to or newer than v2.
  versionAtLeast = v1: v2: !versionOlder v1 v2;


  # Get the version of the specified derivation, as specified in its
  # ‘name’ attribute.
  getVersion = drv: (builtins.parseDrvName drv.name).version;


  # Extract name with version from URL. Ask for separator which is
  # supposed to start extension
  nameFromURL = url: sep: let
    components = splitString "/" url;
    filename = lib.last components;
    name = builtins.head (splitString sep filename);
  in
  assert ! eqStrings name filename;
  name;


  # Create an --{enable,disable}-<feat> string that can be passed to
  # standard GNU Autoconf scripts.
  enableFeature = enable: feat: "--${if enable then "enable" else "disable"}-${feat}";

  # Create a fixed width string with additional prefix to match required width
  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
  fixedWidthNumber = width: n: fixedWidthString width "0" (toString n);
}