about summary refs log tree commit diff
path: root/users/sterni/nix/string/default.nix
blob: 381c8ddff748de9035b74b86bc9ee0b88859f9fd (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
{ depot, lib, ... }:

let

  inherit (depot.users.sterni.nix.char)
    chr
    ord
    ;

  inherit (depot.users.sterni.nix)
    int
    flow
    ;

  take = n: s:
    builtins.substring 0 n s;

  drop = n: s:
    builtins.substring n int.maxBound s;

  charAt = i: s:
    let
      r = builtins.substring i 1 s;
    in
    if r == "" then null else r;

  charIndex = char: s:
    let
      len = builtins.stringLength s;
      go = i:
        flow.cond [
          [ (i >= len) null ]
          [ (charAt i s == char) i ]
          [ true (go (i + 1)) ]
        ];
    in
    go 0;

  toChars = lib.stringToCharacters;
  fromChars = lib.concatStrings;

  toBytes = str:
    builtins.map ord (toChars str);

  fromBytes = is: lib.concatMapStrings chr is;

  pad = { left ? 0, right ? 0, char ? " " }: s:
    let
      leftS = fromChars (builtins.genList (_: char) left);
      rightS = fromChars (builtins.genList (_: char) right);
    in
    "${leftS}${s}${rightS}";

  fit = { char ? " ", width, side ? "left" }: s:
    let
      diff = width - builtins.stringLength s;
    in
    if diff <= 0
    then s
    else pad { inherit char; "${side}" = diff; } s;

  # pattern matching for strings only
  match = val: matcher: matcher."${val}";

  /* Bare-bones printf implementation. Supported format specifiers:

     * `%%` escapes `%`
     * `%s` is substituted by a string

     As expected, the first argument is a format string and the values
     for its format specifiers need to provided as the next arguments
     in order.

     Type: string -> (printfVal : either string (a -> printfVal))
  */
  printf = formatString:
    let
      specifierWithArg = token: builtins.elem token [
        "%s"
      ];
      isSpecifier = lib.hasPrefix "%";

      tokens = lib.flatten (builtins.split "(%.)" formatString);
      argsNeeded = builtins.length (builtins.filter specifierWithArg tokens);

      format = args: (builtins.foldl'
        ({ out ? "", argIndex ? 0 }: token: {
          argIndex = argIndex + (if specifierWithArg token then 1 else 0);
          out =
            if token == "%s" then out + builtins.elemAt args argIndex
            else if token == "%%" then out + "%"
            else if isSpecifier token then throw "Unsupported format specifier ${token}"
            else out + token;
        })
        { }
        tokens).out;

      accumulateArgs = argCount: args:
        if argCount > 0
        then arg: accumulateArgs (argCount - 1) (args ++ [ arg ])
        else format args;
    in
    accumulateArgs argsNeeded [ ];

in
{
  inherit
    take
    drop
    charAt
    charIndex
    toBytes
    fromBytes
    toChars
    fromChars
    pad
    fit
    match
    printf
    ;
}