///|UUID(781bc9a3-8ba9-4dfc-90fc-a08894f78b78)
let default_graph_limit_text = "3000000"
///|UUID(64cf617a-ebad-4e41-ada4-c89891bd0d7c)
priv struct CliOptions {
old_path : String
new_path : String
graph_limit_text : String
use_color : Bool
ignore_comments : Bool
}
///|UUID(9d8d9872-ee94-460e-99e1-8590c4ea178f)
fn parse_graph_limit(text : String) -> UInt raise {
let limit = @string.parse_int(text)
if limit < 0 {
raise Failure::Failure("graph limit must be non-negative")
}
limit.reinterpret_as_uint()
}
///|UUID(a34f9833-2141-48c6-8927-c7a3203564ee)
fn should_use_structural_diff(old_path : String, new_path : String) -> Bool {
old_path.has_suffix(".mbt") && new_path.has_suffix(".mbt")
}
///|
#cfg(target="wasm")
fn proc_exit_ffi(rval : Int) -> Unit = "wasi_snapshot_preview1" "proc_exit"
///|
#cfg(target="native")
extern "c" fn proc_exit_ffi(rval : Int) -> Unit = "exit"
///|UUID(ab140a6a-3c91-4f23-a339-d48e58607db8)
#moongrep.skip
async fn[T] fatal(message : String) -> T {
@stdio.stderr.write("\{message}\n") catch {
_ => ()
}
proc_exit_ffi(1)
panic()
}
///|UUID(5fb97ce8-62bb-4966-b0a3-61d2905cbf95)
#moongrep.skip
async fn write_stdout(message : String) -> Unit {
@stdio.stdout.write(message) catch {
e => fatal("failed to write stdout: \{e}")
}
}
///|UUID(07090eb9-2c1a-4f1c-902b-014898f20578)
fn moondiff_command() -> @argparse.Command {
@argparse.Command(
"moondiff",
options=[
@argparse.OptionArg(
"graph-limit",
short='g',
about="Maximum syntax graph size before falling back to tokdiff",
default_values=[default_graph_limit_text],
),
],
flags=[
@argparse.FlagArg(
"no-color",
about="Disable ANSI colors",
action=@argparse.FlagAction::SetFalse,
),
@argparse.FlagArg(
"ignore-comments",
about="Ignore MoonBit comment-only and blank-line-only differences",
action=@argparse.FlagAction::SetTrue,
),
],
positionals=[
@argparse.PositionArg("old-file", num_args=@argparse.ValueRange::single()),
@argparse.PositionArg("new-file", num_args=@argparse.ValueRange::single()),
],
)
}
///|UUID(b1b5b427-9f37-43ac-afd8-b018ac69375b)
fn single_value(matches : @argparse.Matches, name : String) -> String {
match matches.values.get(name) {
Some([value]) => value
_ => panic()
}
}
///|UUID(83ae8311-feba-4621-af44-82221c52a060)
fn parse_cli_args(argv : ArrayView[String]) -> CliOptions raise {
let matches = moondiff_command().parse(argv~)
{
old_path: single_value(matches, "old-file"),
new_path: single_value(matches, "new-file"),
graph_limit_text: single_value(matches, "graph-limit"),
use_color: matches.flags.get("no-color").unwrap_or(true),
ignore_comments: matches.flags.get("ignore-comments").unwrap_or(false),
}
}
///|UUID(df9a5580-e3b6-4bda-b76f-f1c9dc4a95c0)
fn program_args() -> ArrayView[String] {
let args = @env.args()
if args.length() > 1 {
args[1:]
} else {
[]
}
}
///|UUID(b961590c-b024-4017-94ea-9bff5e338b80)
fn render_cli_diff(
options : CliOptions,
old_source : String,
new_source : String,
graph_limit : UInt,
) -> String {
if should_use_structural_diff(options.old_path, options.new_path) {
@tool.diff_text(
options.old_path,
old_source,
options.new_path,
new_source,
options=@tool.DiffOptions::new(
graph_limit~,
context_lines=3,
use_color=options.use_color,
ignore_comments=options.ignore_comments,
),
).rendered
} else {
@render.render_line_diff(
options.old_path,
old_source,
options.new_path,
new_source,
None,
None,
@render.RenderOptions::new(context_lines=3, use_color=options.use_color),
)
}
}
///|UUID(5da5cf7f-b7c4-474b-a87a-be6436f0b5f2)
#moongrep.skip
async fn main {
let options = parse_cli_args(program_args()) catch {
e => fatal(e.to_string())
}
let old_source = @fs.read_file(options.old_path).text() catch {
e => fatal("failed to read \{options.old_path}: \{e}")
}
let new_source = @fs.read_file(options.new_path).text() catch {
e => fatal("failed to read \{options.new_path}: \{e}")
}
let graph_limit = parse_graph_limit(options.graph_limit_text) catch {
e => fatal("invalid --graph-limit value \{options.graph_limit_text}: \{e}")
}
let rendered = render_cli_diff(options, old_source, new_source, graph_limit)
write_stdout("\{rendered}\n")
}