1 # snap: run programs from Bend 2 with argv as a list of strings, no shell. 2 # 3 # `run` (and `exec`) run a program and answer its exit status on its own first 4 # line, then stdout and stderr together; `code`, `text` and `ok` read that 5 # answer. `start` leaves a program running and answers its pid. `par` runs 6 # several at once. Every argument reaches the program whole; an argv with no 7 # program name, or with NUL inside an argument, is refused and nothing runs. 8 # `cmd` and `line` join argv on newlines. This file is the interface: 9 # everything under src/ is internal. 10 import Base 11 import ./src/effect.bend as Effect 12 import ./src/argv.bend as Argv 13 import ./src/answer.bend as Answer 14 import ./src/par.bend as Par 15 16 # a program run with its arguments: the exit status on its own first line, 17 # then stdout and stderr together. A refused argv answers 127 and runs nothing. 18 def run(argv: List<&2, String>) -> IO(String): # noqa: L001 IO entry point 19 Argv.run.with(Effect.snaprun.exec, Argv.run.plan(argv)) 20 21 # a program run with its arguments; the same as `run` 22 def exec(argv: List<&2, String>) -> IO(String): # noqa: L001 IO entry point 23 run(argv) 24 25 # a program started with its arguments and left running; the answer is its 26 # pid, or 0 when it could not be started or its argv was refused 27 def start(argv: List<&2, String>) -> IO(String): # noqa: L001 IO entry point 28 Argv.start.with(Effect.snaprun.start, Argv.run.plan(argv)) 29 30 # several programs run at once, each answered exactly as `run` answers one, in 31 # the order the jobs were given; a job `run` would refuse answers 127 in its 32 # place. `at` is a directory the caller owns and this fills: job n writes to 33 # `at/n`. A width of "" leaves the count to the effect, which reads the cores 34 # and divides them by `gb`, the memory cap each job runs under. `by` is the 35 # second every job must be finished by, or "0" for none. 36 def par( # noqa: L001 IO entry point 37 js: List<&2, List<&2, String>>, 38 +width: String, 39 +gb: String, 40 +at: String, 41 +by: String 42 ) -> IO(List<&2, String>): 43 do IO>: 44 +out : String <- Effect.snaprun.par(Par.par.plan(js, width, gb, at, by)) 45 +cs : List<&2, String> <- IO.pure(List<&2, String>, Par.par.nonblank(String.lines(out))) 46 bodies : List<&2, Maybe<&2, String>> <- Par.par.bodies(cs, at, 0n) 47 return Par.par.answers(cs, bodies) 48 49 # the exit status an answer holds: everything before its first newline 50 def code(out: String) -> String: 51 Answer.code(out) 52 53 # what the program printed, stdout and stderr together: everything after the 54 # answer's first newline 55 def text(out: String) -> String: 56 Answer.text(out) 57 58 # whether an answer's status is 0 59 def ok(+out: String) -> Bool: 60 Answer.ok(out) 61 62 # the arguments joined on newlines, the wire ez's `pass` effect reads 63 def cmd(argv: List<&2, String>) -> String: 64 Argv.cmd(argv) 65 66 # the arguments joined on newlines; the same as `cmd` 67 def line(argv: List<&2, String>) -> String: 68 Argv.line(argv)