1 import Base 2 3 # V's I/O: running a program with its arguments (the effect below), and reading and writing files with 4 # Base's File API. 5 6 # ---- running a program ---- 7 # Temporary: bend-kit-process does this, but no Bend version runs both it (2.0.27 only) and bend-kit-files (2.0.28+). 8 def Program.run(cmd: String, args: String) -> IO(Result<&1, &1, U32 & String, String>): 9 import "./program_run.c" 10 import "./program_run.js" 11 12 def out_of(r: Result<&1, &1, U32 & String, String>) -> String: 13 match r: 14 case Done{out}: 15 out 16 case Fail{_}: 17 "" 18 19 def ok_of(r: Result<&1, &1, U32 & String, String>) -> Bool: 20 match r: 21 case Done{_}: 22 True{} 23 case Fail{_}: 24 False{} 25 26 # A program's standard output when it exits 0, else "". 27 def run(cmd: String, args: List<&2, String>) -> IO(String): 28 do IO: 29 r : Result<&1, &1, U32 & String, String> <- Program.run(cmd, String.join(args, "\u{1f}")) 30 return out_of(r) 31 32 # Whether a program exits 0. 33 def succeeds(cmd: String, args: List<&2, String>) -> IO(Bool): 34 do IO: 35 r : Result<&1, &1, U32 & String, String> <- Program.run(cmd, String.join(args, "\u{1f}")) 36 return ok_of(r) 37 38 def home() -> IO(String): 39 do IO: 40 r : Result<&1, &1, U32 & String, String> <- IO.get_env("HOME") 41 return Result.default(&1, &1, U32 & String, String, r, "") 42 43 # ---- reading a file: its text, or "" when it cannot be read ---- 44 def size_of(r: Result<&1, &1, U32 & String, U32>) -> U32: 45 match r: 46 case Done{n}: 47 n 48 case Fail{_}: 49 0 50 51 def text_of(r: Result<&1, &1, U32 & String, String>) -> String: 52 match r: 53 case Done{t}: 54 t 55 case Fail{_}: 56 "" 57 58 def read_done(r: File & Result<&1, &1, U32 & String, String>) -> IO(String): 59 match r: 60 case (f, got): 61 do IO: 62 File.close(f) 63 return text_of(got) 64 65 def read_sized(r: File & Result<&1, &1, U32 & String, U32>) -> IO(String): 66 match r: 67 case (f, n): 68 do IO: 69 got : File & Result<&1, &1, U32 & String, String> <- File.read(f, size_of(n)) 70 read_done(got) 71 72 def read_opened(r: Result<&1, &1, U32 & String, File>) -> IO(String): 73 match r: 74 case Done{f}: 75 do IO: 76 n : File & Result<&1, &1, U32 & String, U32> <- File.size(f) 77 read_sized(n) 78 case Fail{_}: 79 IO.pure(String, "") 80 81 def read_head_opened(r: Result<&1, &1, U32 & String, File>, max: U32) -> IO(String): 82 match r: 83 case Done{f}: 84 do IO: 85 got : File & Result<&1, &1, U32 & String, String> <- File.read(f, max) 86 read_done(got) 87 case Fail{_}: 88 IO.pure(String, "") 89 90 # The first `max` bytes of a file ("" if unreadable): enough for what sits at its top, like imports. 91 def read_head(path: String, max: U32) -> IO(String): 92 do IO: 93 r : Result<&1, &1, U32 & String, File> <- File.open(path, "r") 94 read_head_opened(r, max) 95 96 def read(path: String) -> IO(String): 97 do IO: 98 r : Result<&1, &1, U32 & String, File> <- File.open(path, "r") 99 read_opened(r) 100 101 # ---- writing a file ---- 102 def after_write(r: File & Result<&1, &1, U32 & String, Unit>) -> IO(Unit): 103 match r: 104 case (f, _): 105 File.close(f) 106 107 def write_opened(r: Result<&1, &1, U32 & String, File>, text: String) -> IO(Unit): 108 match r: 109 case Done{f}: 110 do IO: 111 w : File & Result<&1, &1, U32 & String, Unit> <- File.write(f, text) 112 after_write(w) 113 case Fail{_}: 114 IO.die(Unit, 1, "cannot write a file") 115 116 def write(path: String, text: String) -> IO(Unit): 117 do IO: 118 r : Result<&1, &1, U32 & String, File> <- File.open(path, "w") 119 write_opened(r, text) 120 121 def keep_filled(c: Bool, x: String, rest: List<&2, String>) -> List<&2, String>: 122 match c: 123 case True{}: 124 x <> rest 125 case False{}: 126 rest 127 128 # The trimmed, non-empty strings. 129 def nonempty(xs: List<&2, String>) -> List<&2, String>: 130 match xs: 131 case []: 132 [] 133 case +x <> rest: 134 keep_filled(Bool.not(String.is_empty(String.trim(x))), String.trim(x), nonempty(rest)) 135