A small shell-free process EDSL for MoonBit
Dependencies
moon runwasm --experimental-policy examples/moonrun-policy.json cmd/smokemoon add bobzhang/myshell///|
import {
"bobzhang/myshell",
"moonbitlang/async",
}///|
Cmd(
program, // executable name or path
arguments, // Array[String], each passed literally
cwd? : String, // default: inherited working directory
env? : Map[String, String], // default: {}
inherit_env? : Bool, // default: true
stdin? : Stdin, // default: closed
stdout? : Redirect, // default: Capture
stderr? : Redirect, // default: Capture
cancel? : Cancel, // default: Kill
no_console_window? : Bool, // default: false, Windows only
) -> Cmd
Pipeline(commands : Array[Cmd]) -> Pipeline///|
enum Stdin {
Text(String)
Binary(Bytes)
FromFile(String)
} // `< path`
///|
enum Redirect {
Capture
Inherit
ToFile(String)
AppendToFile(String)
} // `> path`, `>> path`///|
#cfg(not(platform="windows"))
async test {
let output = @myshell.Cmd("printf", ["hello"]).output()
assert_eq(output.exit_code, 0)
assert_eq(output.stdout, "hello")
}///|
#cfg(not(platform="windows"))
async test {
let output = @myshell.Cmd("printf", ["%s", "$(echo no) | *"]).output()
assert_eq(output.stdout, "$(echo no) | *")
}///|
test {
let command = @myshell.Cmd("moon", ["check"], cwd="workspace", env={
"NO_COLOR": "1",
})
debug_inspect(command.cwd(), content="Some(\"workspace\")")
assert_eq(command.env()["NO_COLOR"], "1")
}///|
test {
let command = @myshell.Cmd("rm", ["-rf", "/"])
assert_eq(command.program(), "rm")
assert_eq(command.arguments().length(), 2)
}///|
test {
let arguments = ["check"]
if true {
arguments.push("--target")
arguments.push("wasm")
}
let command = @myshell.Cmd("moon", arguments)
assert_eq(command.arguments(), ["check", "--target", "wasm"])
}///|
#cfg(not(platform="windows"))
async test {
let output = @myshell.Pipeline([
Cmd("printf", ["alpha\nbeta\n"]),
Cmd("grep", ["beta"]),
Cmd("tr", ["a-z", "A-Z"]),
]).output()
assert_eq(output.stdout, "BETA\n")
assert_eq(output.stage_exit_codes, [0, 0, 0])
}///|
#cfg(not(platform="windows"))
async test {
let output = @myshell.Pipeline([
Cmd("cat", [], stdin=Text("hello")),
Cmd("tr", ["a-z", "A-Z"]),
]).output()
assert_eq(output.stdout, "HELLO")
}///|
#cfg(not(platform="windows"))
async test {
let output = @myshell.Cmd("cat", [], stdin=Binary(b"\x00\xff")).output()
assert_eq(output.stdout_bytes, b"\x00\xff")
}///|
#cfg(not(platform="windows"))
async test {
let path = "/tmp/myshell-readme.log"
@myshell.Cmd("printf", ["one\n"], stdout=ToFile(path)).status() |> ignore
@myshell.Cmd("printf", ["two\n"], stdout=AppendToFile(path)).status()
|> ignore
let back = @myshell.Cmd("cat", [], stdin=FromFile(path)).output()
assert_eq(back.stdout, "one\ntwo\n")
@myshell.Cmd("rm", ["-f", path]).status() |> ignore
}///|
#cfg(not(platform="windows"))
async test {
let seen = []
let code = @myshell.Cmd("printf", ["alpha\nbeta\n"]).each_line(line => {
seen.push(line)
})
assert_eq(code, 0)
assert_eq(seen, ["alpha", "beta"])
}///|
#cfg(not(platform="windows"))
async test {
assert_eq(@myshell.Cmd("false", []).status(), 1)
}///|
#cfg(not(platform="windows"))
async test {
let output = @myshell.Cmd("false", []).output()
if !output.success() {
assert_eq(output.exit_code, 1)
}
}///|
#cfg(not(platform="windows"))
async test {
let output = @myshell.Pipeline([Cmd("false", []), Cmd("cat", [])]).output()
assert_eq(output.exit_code, 1)
assert_eq(output.stage_exit_codes, [1, 0])
}///|
#cfg(not(platform="windows"))
async test {
let output = @myshell.Cmd("/usr/bin/grep", [
"needle", "/definitely/not/a/real/myshell-file",
]).output()
assert_true(!output.stderr.is_empty())
assert_eq(output.stage_stderr.length(), 1)
}///|
#cfg(not(platform="windows"))
async test {
try @myshell.Cmd("sleep", ["1"]).output(timeout_ms=20) catch {
@async.TimeoutError => ()
_ => fail("expected TimeoutError")
} noraise {
_ => fail("expected TimeoutError")
}
}///|
let output = command.output(max_output_bytes=32 * 1024 * 1024)///|
test {
let command = @myshell.Cmd("moon", ["check"], cancel=Graceful(grace_ms=2000))
debug_inspect(command.cancel(), content="Graceful(grace_ms=2000)")
}moon run -e 'import {
"bobzhang/myshell",
"moonbitlang/async",
}
async fn main {
let output = @myshell.Pipeline([
@myshell.Cmd("/usr/bin/printf", ["hello\nworld\n"], inherit_env=false),
@myshell.Cmd("/usr/bin/grep", ["world"], inherit_env=false),
]).output()
print(output.stdout)
}'pub(all) suberror ProcessError {
EmptyProgram
EmptyPipeline
InvalidEnvironmentName(String)
InvalidOutputLimit(Int)
InvalidGracePeriod(Int)
OutputLimitExceeded(stream~ : String, limit~ : Int)
NulByte(String)
StdinOnNonFirstStage(Int)
RedirectOnNonFinalStage(Int)
StdoutNotCaptured
CommandFailed(Output)
} derive(Debug)test {
let cmd = @myshell.Cmd("git", ["status", "--short"], cwd="workspace")
inspect(cmd.program(), content="git")
debug_inspect(cmd.cwd(), content="Some(\"workspace\")")
}#cfg(not(platform="windows"))
async test {
let seen = []
let code = @myshell.Cmd("printf", ["a\nb\n"]).each_line(line => {
seen.push(line)
})
assert_eq(code, 0)
assert_eq(seen, ["a", "b"])
}#cfg(not(platform="windows"))
async test {
let output = @myshell.Cmd("printf", ["hello"]).output()
assert_eq(output.stdout, "hello")
}#cfg(not(platform="windows"))
async test {
assert_eq(@myshell.Cmd("false", []).status(), 1)
}#cfg(not(platform="windows"))
async test {
try @myshell.Cmd("false", []).output().check() catch {
@myshell.ProcessError::CommandFailed(failed) =>
inspect(failed.exit_code, content="1")
_ => fail("unexpected error")
} noraise {
_ => fail("expected CommandFailed")
}
}test {
let plan = @myshell.Pipeline([
Cmd("printf", ["alpha\nbeta\n"]),
Cmd("grep", ["beta"]),
])
inspect(plan.commands().length(), content="2")
}#cfg(not(platform="windows"))
async test {
let seen = []
@myshell.Pipeline([Cmd("printf", ["alpha\nbeta\n"]), Cmd("grep", ["beta"])]).each_line(line => {
seen.push(line)
},
)
|> ignore
assert_eq(seen, ["beta"])
}#cfg(not(platform="windows"))
async test {
let output = @myshell.Pipeline([
Cmd("printf", ["alpha\nbeta\n"]),
Cmd("grep", ["beta"]),
]).output()
assert_eq(output.stdout, "beta\n")
}let default_max_output_bytes : IntA small shell-free process EDSL for MoonBit
Dependencies