Clean up CLI, add arguments and some validation
This commit is contained in:
Generated
+7
@@ -16,6 +16,7 @@ dependencies = [
|
|||||||
"codepage-437",
|
"codepage-437",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"insta",
|
"insta",
|
||||||
|
"lexopt",
|
||||||
"nom",
|
"nom",
|
||||||
"peg_macro",
|
"peg_macro",
|
||||||
"pest",
|
"pest",
|
||||||
@@ -133,6 +134,12 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lexopt"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.171"
|
version = "0.2.171"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ nom = "7.1.3"
|
|||||||
pest = "2.7.15"
|
pest = "2.7.15"
|
||||||
compact_str = "0.9.0"
|
compact_str = "0.9.0"
|
||||||
rustc-hash = "2.1.1"
|
rustc-hash = "2.1.1"
|
||||||
|
lexopt = "0.3.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.39.0"
|
insta = "1.39.0"
|
||||||
|
|||||||
+81
-15
@@ -5,26 +5,68 @@ mod peg;
|
|||||||
mod preprocess;
|
mod preprocess;
|
||||||
mod world;
|
mod world;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::{Result, anyhow};
|
||||||
use error::Context as ErrContext;
|
use error::Context as ErrContext;
|
||||||
use labels::process_labels;
|
use labels::process_labels;
|
||||||
|
use lexopt::prelude::*;
|
||||||
use preprocess::eval::Context;
|
use preprocess::eval::Context;
|
||||||
use std::{env, error::Error, fs, path::PathBuf, process::exit};
|
use std::{
|
||||||
|
env, fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::exit,
|
||||||
|
};
|
||||||
use world::World;
|
use world::World;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<()> {
|
||||||
let args: Vec<String> = env::args().collect();
|
let mut input_file = None;
|
||||||
if args.len() != 2 {
|
let mut output_file = None;
|
||||||
eprintln!("Usage: {} WORLD_FILE", args[0]);
|
let mut parser = lexopt::Parser::from_env();
|
||||||
|
let mut has_args = false;
|
||||||
|
|
||||||
|
while let Some(arg) = parser.next()? {
|
||||||
|
has_args = true;
|
||||||
|
match arg {
|
||||||
|
Short('o') | Long("output") => {
|
||||||
|
output_file = Some(parser.value()?.string()?);
|
||||||
|
}
|
||||||
|
Value(val) => {
|
||||||
|
if input_file.is_none() {
|
||||||
|
input_file = Some(val.string()?);
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("Unknown arg: {}", val.to_string_lossy()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(arg.unexpected().into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has_args {
|
||||||
|
eprintln!("Usage: {} INPUT -o OUTPUT", env::args().next().unwrap());
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let world_filename = &args[1];
|
let input_filename = input_file.ok_or_else(|| anyhow!("No input file specified"))?;
|
||||||
let bytes = fs::read(world_filename)?;
|
let output_filename = output_file.ok_or_else(|| anyhow!("No output file specified"))?;
|
||||||
|
|
||||||
|
// Prevent overwriting input file
|
||||||
|
let input_path = Path::new(&input_filename).canonicalize()?;
|
||||||
|
let output_path = Path::new(&output_filename);
|
||||||
|
let output_dir = output_path
|
||||||
|
.parent()
|
||||||
|
.filter(|x| !x.as_os_str().is_empty())
|
||||||
|
.unwrap_or_else(|| Path::new("."))
|
||||||
|
.canonicalize()?;
|
||||||
|
let output_path = output_dir.join(output_path.file_name().unwrap());
|
||||||
|
if input_path == output_path {
|
||||||
|
eprintln!("Error: Output file cannot be the same as input file");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = fs::read(&input_path)?;
|
||||||
let mut world = World::from_bytes(&bytes)?;
|
let mut world = World::from_bytes(&bytes)?;
|
||||||
|
|
||||||
// Prepare to evaluate macros from the world file's directory
|
// Prepare to evaluate macros from the world file's directory
|
||||||
let world_pathbuf = PathBuf::from(&world_filename);
|
let world_pathbuf = PathBuf::from(&input_path);
|
||||||
let world_dir = world_pathbuf
|
let world_dir = world_pathbuf
|
||||||
.parent()
|
.parent()
|
||||||
.ok_or(anyhow!("Couldn't get world's directory"))?;
|
.ok_or(anyhow!("Couldn't get world's directory"))?;
|
||||||
@@ -39,7 +81,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
// Resolve labels to proper ZZT-OOP
|
// Resolve labels to proper ZZT-OOP
|
||||||
let base_ctx = ErrContext::new();
|
let base_ctx = ErrContext::new();
|
||||||
let ctx = base_ctx.with_file_path(&world_filename);
|
let ctx = base_ctx.with_file_path(&input_filename);
|
||||||
for (i, board) in world.boards.iter_mut().enumerate() {
|
for (i, board) in world.boards.iter_mut().enumerate() {
|
||||||
let ctx = ctx.with_board(i);
|
let ctx = ctx.with_board(i);
|
||||||
*board = process_labels(&board, &ctx);
|
*board = process_labels(&board, &ctx);
|
||||||
@@ -47,15 +89,39 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
// Print diagnostics
|
// Print diagnostics
|
||||||
let messages = base_ctx.into_messages();
|
let messages = base_ctx.into_messages();
|
||||||
for (i, message) in messages.iter().enumerate() {
|
for message in messages.iter() {
|
||||||
if i > 0 {
|
println!("{}\n", message.rich_format(&world));
|
||||||
println!();
|
}
|
||||||
|
if !messages.is_empty() {
|
||||||
|
let mut warnings = 0;
|
||||||
|
let mut errors = 0;
|
||||||
|
for message in messages {
|
||||||
|
match message.level {
|
||||||
|
error::Level::Error => errors += 1,
|
||||||
|
error::Level::Warning => warnings += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let plural = |x| if x == 1 { "" } else { "s" };
|
||||||
|
if errors == 0 {
|
||||||
|
println!(
|
||||||
|
"Compilation succeeded with {warnings} warning{}.",
|
||||||
|
plural(warnings)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"Compilation failed with {warnings} warning{} and {errors} error{}.",
|
||||||
|
plural(warnings),
|
||||||
|
plural(errors)
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
println!("{}", message.rich_format(&world));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to write a modified world file
|
// Try to write a modified world file
|
||||||
fs::write("tmp.zzt", world.to_bytes()?)?;
|
let world_bytes = world
|
||||||
|
.to_bytes()
|
||||||
|
.map_err(|e| anyhow!("Couldn't serialize world, {}", e))?;
|
||||||
|
fs::write(&output_path, world_bytes)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user