summary refs log tree commit diff
path: root/pkgs/lib/strings.nix
blob: 8ea4106709ab315718f00ef303f2dff99b9a2c9e (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
/* String manipulation functions. */

let lib = import ./default.nix;

inherit (builtins) add sub lessThan length;

in

rec {
  inherit (builtins) stringLength substring head tail;


  # 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);


  # 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 filename ends in the given suffix.
  hasSuffix = ext: fileName:
    let lenFileName = stringLength fileName;
        lenExt = stringLength ext;
    in !(lessThan lenFileName lenExt) &&
       substring (sub lenFileName lenExt) lenFileName fileName == ext;


  # 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 (sub 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;


  # Compares strings not requiring context equality
  # Obviously, a workaround but works on all Nix versions
  eqStrings = a: b: (a+(substring 0 0 b)) == ((substring 0 0 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
      sepLen = stringLength sep;
      sLen = stringLength s;
      lastSearch = sub sLen sepLen;
      startWithSep = startAt:
        substring startAt sepLen s == sep;

      recurse = index: startAt:
        let cutUntil = i: [(substring startAt (sub i startAt) s)]; in
        if lessThan index lastSearch then
          if startWithSep index then
            let restartAt = add index sepLen; in
            cutUntil index ++ recurse restartAt restartAt
          else
            recurse (add 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 pre == substring 0 preLen s then
        substring preLen (sub sLen preLen) s
      else
        s;

  # Return true iff string v1 denotes a version older than v2.
  versionOlder = v1: v2: builtins.compareVersions v2 v1 == 1;


  # 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}";

}