main.bend

ezsys@0.1.0.0 · 140 lines · 25 definitions

1# ezsys: system helpers for Bend 2: whole-file reads and atomic saves,
2# environment variables with a default, the wall clock as ISO 8601 UTC, text
3# templates, Python's str helpers (strip, replace, split, find, case,
4# width, digit order), a directory's names, and a failure written as
5# Python does.
6# This file is the whole public interface; everything under src/ is
7# internal.
8import Base
9import ezsys@0.1.0.0/src/sys.bend as Sys
10import ezsys@0.1.0.0/src/clock.bend as Clock
11import ezsys@0.1.0.0/src/text.bend as Text
12import ezsys@0.1.0.0/src/errno.bend as Err
13import ezsys@0.1.0.0/src/str.bend as Str
14
15# a whole file's text; a failure to open or read it is a Fail, never an exit
16def file.read(path: String) -> IO(Result<&1, &1, U32 & String, String>):
17  Sys.sys.read(path)
18
19# replace a file's whole text, creating it when it is missing
20def file.write(
21  path: String,
22  text: String
23) -> IO(Result<&1, &1, U32 & String, Unit>):
24  Sys.sys.write(path, text)
25
26# write `<path>.tmp`, flush it, and rename it over path, so a reader sees
27# the old text or the new, never part of either
28def file.save(
29  path: String,
30  text: String
31) -> IO(Result<&1, &1, U32 & String, Unit>):
32  Sys.sys.save(path, text)
33
34# an environment variable's value (even an empty one), or fallback when it
35# is unset
36def env.or(name: String, fallback: String) -> IO(String):
37  Sys.sys.env(name, fallback)
38
39# the wall clock: whole seconds since 1970-01-01T00:00:00Z, and the
40# milliseconds past them
41def time.now() -> IO(U32 & U32):
42  Sys.ezsys.clock()
43
44# ISO 8601 UTC, `YYYY-MM-DDTHH:MM:SS.mmmZ`, of seconds since the epoch and a
45# millisecond
46def time.iso(secs: U32, ms: U32) -> String:
47  Clock.time.iso(secs, ms)
48
49# the civil fields of seconds since the epoch, in UTC: year, month, day,
50# hour, minute, second and ISO weekday (1 Monday to 7 Sunday)
51def time.civil(secs: U32) -> U32 & U32 & U32 & U32 & U32 & U32 & U32:
52  Clock.time.fields(secs)
53
54# the ISO 8601 week-numbering year and week of seconds since the epoch
55def time.iso_week(secs: U32) -> U32 & U32:
56  Clock.time.iso_week(secs)
57
58# the ISO 8601 week as `date -u +%G-W%V` writes it, e.g. `2026-W43`
59def time.iso_week_text(secs: U32) -> String:
60  Clock.time.iso_week_text(secs)
61
62# the wall clock as ISO 8601 UTC
63def time.now_iso() -> IO(String):
64  Sys.sys.now_iso()
65
66# every `{{key}}` in a template replaced by its value; unknown and unclosed
67# markers are left as written
68def text.fill(template: String, pairs: List<(String & String)>) -> String:
69  Text.text.fill(template, Text.text.own(pairs))
70
71# the same with other delimiters, e.g. `<!--` and `-->`
72def text.fill_with(
73  open: String,
74  close: String,
75  template: String,
76  pairs: List<(String & String)>
77) -> String:
78  Text.text.fill_with(open, close, template, Text.text.own(pairs))
79
80
81# the text with Python's whitespace removed from both ends (str.strip())
82def text.strip(txt: String) -> String:
83  Str.text.strip(txt)
84
85# the text with Python's whitespace removed from its start (str.lstrip())
86def text.lstrip(txt: String) -> String:
87  Str.text.lstrip(txt)
88
89# the text with Python's whitespace removed from its end (str.rstrip())
90def text.rstrip(txt: String) -> String:
91  Str.text.rstrip(txt)
92
93# every `old` replaced by `new`, left to right, never overlapping
94# (str.replace); an empty `old` puts `new` around every character
95def text.replace(txt: String, old: String, new: String) -> String:
96  Str.text.replace(txt, old, new)
97
98# the pieces between every `sep` (str.split(sep)); an empty `sep` answers
99# the text as its one piece
100def text.split(txt: String, sep: String) -> List<&2, String>:
101  Str.text.split(txt, sep)
102
103# the index in code points where `needle` first starts, or None
104# (str.find)
105def text.find(txt: String, needle: String) -> Maybe<&2, U32>:
106  Str.text.find(txt, needle)
107
108# the text in lowercase (str.lower) for ASCII, Latin-1, Latin Extended-A,
109# Greek and Cyrillic; other code points are kept
110def text.lower(txt: String) -> String:
111  Str.text.lower(txt)
112
113# the text in uppercase (str.upper) for ASCII, Latin-1, Latin Extended-A,
114# Greek and Cyrillic; other code points are kept
115def text.upper(txt: String) -> String:
116  Str.text.upper(txt)
117
118# the text with spaces after it up to `width` code points (str.ljust)
119def text.pad_right(txt: String, width: Nat) -> String:
120  Str.text.pad_right(txt, width)
121
122# the first `width` code points of the text (txt[:width])
123def text.truncate(txt: String, width: Nat) -> String:
124  Str.text.truncate(txt, width)
125
126# two strings of ASCII digits in numeric order, any length
127def digits.cmp(lhs: String, rhs: String) -> Cmp:
128  Str.text.digits.cmp(lhs, rhs)
129
130# a directory's names, sorted by code point, with no "." or ".."; a failure
131# to read it is a Fail, never an exit
132def dir.list(
133  path: String
134) -> IO(Result<&1, &1, U32 & String, List<&2, String>>):
135  Sys.sys.list(path)
136
137# a failed file operation as Python's OSError writes it:
138# `[Errno 2] No such file or directory: '/tmp/x'`
139def err.os(code: U32, why: String, path: String) -> String:
140  Err.err.os(code, why, path)