usage/norm.bend

cswap-bend-cli@0.1.0.0 · 209 lines · 34 definitions

1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/clock.bend as Clock
4import cswap-bend-cli@0.1.0.0/usage/text.bend as UText
5
6# ---- reset text and the normalised result ----------------------------------
7
8def months() -> String:
9  "JanFebMarAprMayJunJulAugSepOctNovDec"
10
11def hhmm(+local: U32) -> String:
12  Clock.pad2(((local % 86400) / 3600 : U32)) ++ ":" ++ Clock.pad2(((local % 3600) / 60 : U32))
13
14def clock.date2(md: U32 & U32, +local: U32) -> String:
15  (m, d) = md
16  String.take(String.drop(months(), U32.to_nat((m * 3 - 3 : U32))), 3n) ++ " " ++ U32.show(d) ++ " " ++ hhmm(local)
17
18def clock.date(ymd: U32 & (U32 & U32), +local: U32) -> String:
19  (y, md) = ymd
20  clock.date2(md, local)
21
22# "20:39" on the same local day as now, else "Jul 5 08:59". `off` is the local
23# UTC offset biased by 86400 (Clock.tz).
24def clock.text(+reset: U32, +now: U32, +off: U32) -> String:
25  Clock.sel.s(U32.is_eq(((reset + off - 86400) / 86400 : U32), ((now + off - 86400) / 86400 : U32)),
26    hhmm((reset + off - 86400 : U32)),
27    clock.date(Clock.civil(((reset + off - 86400) / 86400 : U32)), (reset + off - 86400 : U32)))
28
29def countdown(+rem: U32) -> String:
30  Clock.sel.s(U32.is_gt((rem / 86400 : U32), 0), U32.show((rem / 86400 : U32)) ++ "d " ++ U32.show(((rem % 86400) / 3600 : U32)) ++ "h",
31    Clock.sel.s(U32.is_gt((rem / 3600 : U32), 0), U32.show((rem / 3600 : U32)) ++ "h " ++ U32.show(((rem % 3600) / 60 : U32)) ++ "m",
32      U32.show((rem / 60 : U32)) ++ "m"))
33
34def remaining(+reset: U32, +now: U32) -> U32:
35  Clock.sel(U32.is_gt(reset, now), (reset - now : U32), 0)
36
37def nonempty(m: Maybe<&2, String>) -> Maybe<&2, String>:
38  match m:
39    case None{}:
40      None{}
41    case Some{+s}:
42      UText.maybe.none_if(String.is_empty(s), s)
43
44def resets_of(w: Json.Json) -> Maybe<&2, String>:
45  nonempty(Json.as_string(Maybe.default(&2, Json.Json, Json.get("resets_at", w), Json.JNull{})))
46
47# resets_at, then countdown and clock when the stamp parses.
48def with_reset.at(j: Json.Json, s: String, t: Maybe<&2, U32>, +now: U32, +off: U32) -> Json.Json:
49  match t:
50    case None{}:
51      Json.set("resets_at", Json.JStr{s}, j)
52    case Some{+u}:
53      Json.set("clock", Json.JStr{clock.text(u, now, off)},
54        Json.set("countdown", Json.JStr{countdown(remaining(u, now))},
55          Json.set("resets_at", Json.JStr{s}, j)))
56
57def with_reset(j: Json.Json, r: Maybe<&2, String>, +now: U32, +off: U32) -> Json.Json:
58  match r:
59    case None{}:
60      j
61    case Some{+s}:
62      with_reset.at(j, s, Clock.parse_iso8601(s), now, off)
63
64def put(k: String, m: Maybe<&2, Json.Json>, j: Json.Json) -> Json.Json:
65  match m:
66    case None{}:
67      j
68    case Some{v}:
69      Json.set(k, v, j)
70
71# five_hour / seven_day: {pct, resets_at, countdown, clock}. A window with no
72# utilization is left out.
73def window.pct(u: Maybe<&2, Json.Json>, r: Maybe<&2, String>, +now: U32, +off: U32) -> Maybe<&2, Json.Json>:
74  match u:
75    case None{}:
76      None{}
77    case Some{p}:
78      Some{with_reset(Json.of_pairs([("pct", p)]), r, now, off)}
79
80def window.of(+w: Json.Json, +now: U32, +off: U32) -> Maybe<&2, Json.Json>:
81  window.pct(Json.get("utilization", w), resets_of(w), now, off)
82
83def window.in(m: Maybe<&2, Json.Json>, +now: U32, +off: U32) -> Maybe<&2, Json.Json>:
84  match m:
85    case None{}:
86      None{}
87    case Some{w}:
88      window.of(w, now, off)
89
90def window(+key: String, +data: Json.Json, +now: U32, +off: U32) -> Maybe<&2, Json.Json>:
91  window.in(Json.get(key, data), now, off)
92
93def num.text(j: Json.Json) -> Maybe<&2, String>:
94  match j:
95    case Json.JNum{t}:
96      Some{t}
97    case _:
98      None{}
99
100def nonnull(m: Maybe<&2, Json.Json>) -> Maybe<&2, Json.Json>:
101  match m:
102    case None{}:
103      None{}
104    case Some{Json.JNull{}}:
105      None{}
106    case Some{j}:
107      Some{j}
108
109def num.field(+k: String, +j: Json.Json) -> Maybe<&2, String>:
110  do Maybe<&2, String>:
111    v : Json.Json <- nonnull(Json.get(k, j))
112    num.text(v)
113
114# float(field) as Python prints it.
115def float.field(+k: String, +j: Json.Json) -> Maybe<&2, String>:
116  do Maybe<&2, String>:
117    t : String <- num.field(k, j)
118    UText.float(t)
119
120def spend.mk(used: String, limit: String, pct: String, cur: Json.Json, eu: Json.Json, +now: U32, +off: U32) -> Json.Json:
121  with_reset(Json.of_pairs([("used", Json.JNum{used}), ("limit", Json.JNum{limit}), ("pct", Json.JNum{pct}), ("currency", cur)]),
122    resets_of(eu), now, off)
123
124def spend.fields(+eu: Json.Json, +now: U32, +off: U32) -> Maybe<&2, Json.Json>:
125  do Maybe<&2, Json.Json>:
126    u : String <- num.field("used_credits", eu)
127    used : String <- UText.div100(u)
128    l : String <- num.field("monthly_limit", eu)
129    limit : String <- UText.div100(l)
130    p : String <- num.field("utilization", eu)
131    pct : String <- UText.float(p)
132    return spend.mk(used, limit, pct, Maybe.default(&2, Json.Json, Json.get("currency", eu), Json.JStr{"USD"}), eu, now, off)
133
134def spend.on(enabled: Maybe<&2, Bool>, res: Json.Json, +eu: Json.Json, +now: U32, +off: U32) -> Json.Json:
135  match enabled:
136    case Some{True{}}:
137      put("spend", spend.fields(eu, now, off), res)
138    case _:
139      res
140
141def spend.add(res: Json.Json, m: Maybe<&2, Json.Json>, +now: U32, +off: U32) -> Json.Json:
142  match m:
143    case None{}:
144      res
145    case Some{+eu}:
146      spend.on(Json.as_bool(Maybe.default(&2, Json.Json, Json.get("is_enabled", eu), Json.JNull{})), res, eu, now, off)
147
148# One `limits[]` entry with scope.model.display_name and a numeric percent.
149def scoped.entry(name: String, pct: String, +lim: Json.Json, +now: U32, +off: U32) -> Json.Json:
150  with_reset(Json.of_pairs([("name", Json.JStr{name}), ("pct", Json.JNum{pct})]), resets_of(lim), now, off)
151
152def scoped.name(+lim: Json.Json) -> Maybe<&2, String>:
153  do Maybe<&2, String>:
154    scope : Json.Json <- Json.get("scope", lim)
155    model : Json.Json <- Json.get("model", scope)
156    n : Json.Json <- Json.get("display_name", model)
157    s : String <- Json.as_string(n)
158    nonempty(Some{s})
159
160def scoped.of(name: Maybe<&2, String>, pct: Maybe<&2, String>, +lim: Json.Json, +now: U32, +off: U32) -> Maybe<&2, Json.Json>:
161  match name pct:
162    case Some{n} Some{p}:
163      Some{scoped.entry(n, p, lim, now, off)}
164    case _ _:
165      None{}
166
167def scoped.one(+lim: Json.Json, +now: U32, +off: U32) -> Maybe<&2, Json.Json>:
168  scoped.of(scoped.name(lim), float.field("percent", lim), lim, now, off)
169
170def scoped.cons(m: Maybe<&2, Json.Json>, rest: List<&2, Json.Json>) -> List<&2, Json.Json>:
171  match m:
172    case None{}:
173      rest
174    case Some{x}:
175      x <> rest
176
177def scoped.go(ls: List<&2, Json.Json>, +now: U32, +off: U32) -> List<&2, Json.Json>:
178  match ls:
179    case Nil{}:
180      []
181    case Con{h, t}:
182      scoped.cons(scoped.one(h, now, off), scoped.go(t, now, off))
183
184def scoped.put(xs: List<&2, Json.Json>, res: Json.Json) -> Json.Json:
185  match xs:
186    case Nil{}:
187      res
188    case Con{h, t}:
189      Json.set("scoped", Json.arr(h <> t), res)
190
191def result.fin(ks: List<&2, String>, res: Json.Json) -> Json.Json:
192  match ks:
193    case Nil{}:
194      Json.JNull{}
195    case Con{h, t}:
196      res
197
198def result.keep(+r: Json.Json) -> Json.Json:
199  result.fin(Json.keys(r), r)
200
201# The normalised usage (five_hour, seven_day, spend, scoped), or JNull when
202# the data carries nothing: build_usage_result. `now` is unix seconds, `off`
203# the local UTC offset biased by 86400.
204def build_usage_result(+data: Json.Json, +now: U32, +off: U32) -> Json.Json:
205  result.keep(scoped.put(
206    scoped.go(Json.to_list(Maybe.default(&2, Json.Json, Json.get("limits", data), Json.JNull{})), now, off),
207    spend.add(
208      put("seven_day", window("seven_day", data, now, off), put("five_hour", window("five_hour", data, now, off), Json.of_pairs([]))),
209      Json.get("extra_usage", data), now, off)))