///| A small native/WASI command line front-end. The library owns parsing and
///|
/// reporting; this package only handles paths, arguments, and exit codes.
#cfg(target="wasm")
fn exit_with(code : Int) -> Unit = "wasi_snapshot_preview1" "proc_exit"
///|
#cfg(target="wasm-gc")
fn exit_with(code : Int) -> Unit = "__moonbit_sys_unstable" "exit"
///|
#cfg(any(target="native", target="llvm"))
extern "c" fn exit_with(code : Int) = "exit"
///|
#cfg(target="js")
extern "js" fn exit_with(code : Int) =
#| function(code) {
#| if (typeof process !== "undefined") process.exit(code);
#| }
///|
fn usage() -> String {
"Usage:\n" +
" moon run cmd/main inspect [--json] \n" +
" moon run cmd/main validate [--json] \n" +
" moon run cmd/main features [--json] \n" +
" moon run cmd/main diff [--json] "
}
///|
fn read_module(path : String) -> Result[@inspect.Module, String] {
let raw = @fs.read_file_to_bytes(path) catch {
_ => return Err("cannot read '" + path + "'")
}
let bytes : Array[Int] = []
for byte in raw {
bytes.push(byte.to_int())
}
match @inspect.parse_module(bytes) {
Ok(binary) => Ok(binary)
Err(error) => Err("cannot parse '" + path + "': " + error.message())
}
}
///|
fn single_input(args : ArrayView[String]) -> Result[(Bool, String), String] {
if args.length() == 1 {
Ok((false, args[0]))
} else if args.length() == 2 && args[0] == "--json" {
Ok((true, args[1]))
} else {
Err("expected one module path, optionally preceded by --json")
}
}
///|
fn two_inputs(
args : ArrayView[String],
) -> Result[(Bool, String, String), String] {
if args.length() == 2 {
Ok((false, args[0], args[1]))
} else if args.length() == 3 && args[0] == "--json" {
Ok((true, args[1], args[2]))
} else {
Err("expected two module paths, optionally preceded by --json")
}
}
///|
fn render_features_json(features : Array[@inspect.WasmFeature]) -> String {
let mut text = "{\"features\":["
let mut first = true
for feature in features {
if !first {
text = text + ","
}
text = text + "\"" + feature.label() + "\""
first = false
}
text + "]}"
}
///|
fn size_json(value : Int?) -> String {
match value {
Some(size) => size.to_string()
None => "null"
}
}
///|
fn render_diff_json(left : @inspect.Module, right : @inspect.Module) -> String {
let differences = @inspect.diff_structure(left, right)
let mut text = "{\"differences\":["
let mut first = true
for difference in differences {
if !first {
text = text + ","
}
text = text +
"{\"section\":\"" +
difference.label +
"\",\"left\":" +
size_json(difference.left_size) +
",\"right\":" +
size_json(difference.right_size) +
"}"
first = false
}
text + "]}"
}
///|
fn inspect_command(json : Bool, path : String) -> Int {
let binary = match read_module(path) {
Ok(value) => value
Err(message) => {
println(message)
return 2
}
}
if json {
println(@inspect.render_structure_json(binary))
return 0
}
match @inspect.decode_module(binary) {
Ok(decoded) => {
println(@inspect.render_decoded(decoded))
0
}
Err(error) => {
println(error.message())
2
}
}
}
///|
fn validate_command(json : Bool, path : String) -> Int {
let binary = match read_module(path) {
Ok(value) => value
Err(message) => {
println(message)
return 2
}
}
let decoded = match @inspect.decode_module(binary) {
Ok(value) => value
Err(error) => {
println(error.message())
return 2
}
}
let report = @inspect.validate(decoded)
if json {
println(@inspect.render_validation_json(report))
} else {
println(report.render())
}
if report.has_errors() {
1
} else {
0
}
}
///|
fn features_command(json : Bool, path : String) -> Int {
let binary = match read_module(path) {
Ok(value) => value
Err(message) => {
println(message)
return 2
}
}
let decoded = match @inspect.decode_module(binary) {
Ok(value) => value
Err(error) => {
println(error.message())
return 2
}
}
let features = @inspect.detect_features(decoded)
if json {
println(render_features_json(features))
} else {
println(@inspect.render_features(features))
}
0
}
///|
fn diff_command(json : Bool, left_path : String, right_path : String) -> Int {
let left = match read_module(left_path) {
Ok(value) => value
Err(message) => {
println(message)
return 2
}
}
let right = match read_module(right_path) {
Ok(value) => value
Err(message) => {
println(message)
return 2
}
}
if json {
println(render_diff_json(left, right))
} else {
println(@inspect.render_structure_diff(left, right))
}
0
}
///|
fn main {
let argv = @env.args()
if argv.length() < 2 {
println(usage())
exit_with(2)
return
}
let exit_code = match argv[1] {
"inspect" =>
match single_input(argv[2:]) {
Ok((json, path)) => inspect_command(json, path)
Err(message) => {
println(message + "\n" + usage())
2
}
}
"validate" =>
match single_input(argv[2:]) {
Ok((json, path)) => validate_command(json, path)
Err(message) => {
println(message + "\n" + usage())
2
}
}
"features" =>
match single_input(argv[2:]) {
Ok((json, path)) => features_command(json, path)
Err(message) => {
println(message + "\n" + usage())
2
}
}
"diff" =>
match two_inputs(argv[2:]) {
Ok((json, left, right)) => diff_command(json, left, right)
Err(message) => {
println(message + "\n" + usage())
2
}
}
_ => {
println("unknown command '" + argv[1] + "'\n" + usage())
2
}
}
if exit_code != 0 {
exit_with(exit_code)
}
}