Feat: improve CLI with help menu and multi-file support
Extracts all argument-parsing and help logic into cli.rs: - -h / --help: colored usage and options summary - -V / --version: print version from Cargo.toml - Unknown flags produce a styled error with a hint - At least one file required; missing-file error is styled - Multiple files accepted; exit code 1 if any file has parse errors
This commit is contained in:
87
fluxc/src/cli.rs
Normal file
87
fluxc/src/cli.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use std::process;
|
||||||
|
|
||||||
|
use yansi::Paint as _;
|
||||||
|
|
||||||
|
// ── Help ───────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
pub fn print_help() {
|
||||||
|
println!(
|
||||||
|
"{} {} — the Flux language compiler\n",
|
||||||
|
"fluxc".bold(),
|
||||||
|
env!("CARGO_PKG_VERSION").dim(),
|
||||||
|
);
|
||||||
|
println!("{}", "USAGE:".bold().yellow());
|
||||||
|
println!(" fluxc [OPTIONS] <file> [<file> ...]\n");
|
||||||
|
println!("{}", "OPTIONS:".bold().yellow());
|
||||||
|
println!(
|
||||||
|
" {}, {} Print this help message",
|
||||||
|
"-h".bold(),
|
||||||
|
"--help".bold(),
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" {}, {} Print version information",
|
||||||
|
"-V".bold(),
|
||||||
|
"--version".bold(),
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
println!("{}", "ARGS:".bold().yellow());
|
||||||
|
println!(
|
||||||
|
" {} One or more Flux source files to compile",
|
||||||
|
"<file>".bold(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_version() {
|
||||||
|
println!("fluxc {}", env!("CARGO_PKG_VERSION"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Error helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
pub fn fatal(msg: &str) -> ! {
|
||||||
|
eprintln!("{}: {}", "error".bold().red(), msg.bold());
|
||||||
|
eprintln!(" {} fluxc --help", "hint:".bold().cyan());
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn io_error(path: &str, err: std::io::Error) -> ! {
|
||||||
|
eprintln!(
|
||||||
|
"{}: cannot read {}: {}",
|
||||||
|
"error".bold().red(),
|
||||||
|
path.bold(),
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Argument parsing ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
pub struct Opts {
|
||||||
|
pub files: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_args() -> Opts {
|
||||||
|
let mut files = Vec::new();
|
||||||
|
|
||||||
|
for arg in std::env::args().skip(1) {
|
||||||
|
match arg.as_str() {
|
||||||
|
"-h" | "--help" => {
|
||||||
|
print_help();
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
"-V" | "--version" => {
|
||||||
|
print_version();
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
flag if flag.starts_with('-') => {
|
||||||
|
fatal(&format!("unknown option `{flag}`"));
|
||||||
|
}
|
||||||
|
path => files.push(path.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if files.is_empty() {
|
||||||
|
fatal("no input files — at least one source file is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
Opts { files }
|
||||||
|
}
|
||||||
@@ -1,26 +1,33 @@
|
|||||||
use std::{env::args, fs};
|
use std::{fs, process};
|
||||||
|
|
||||||
use crate::parser::Parser;
|
use crate::parser::Parser;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
|
pub mod cli;
|
||||||
pub mod diagnostics;
|
pub mod diagnostics;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let path = args().nth(1).expect("usage: fluxc <file>");
|
let opts = cli::parse_args();
|
||||||
let content = fs::read_to_string(&path).unwrap_or_else(|e| {
|
let mut had_errors = false;
|
||||||
eprintln!("error: {e}");
|
|
||||||
std::process::exit(1)
|
for path in &opts.files {
|
||||||
});
|
let content = fs::read_to_string(path).unwrap_or_else(|e| cli::io_error(path, e));
|
||||||
|
|
||||||
let mut parser = Parser::new(&content);
|
let mut parser = Parser::new(&content);
|
||||||
let program = parser.parse_program();
|
let program = parser.parse_program();
|
||||||
|
|
||||||
for diag in &parser.errors {
|
for diag in &parser.errors {
|
||||||
eprint!("{}", diag.render(&content, &path));
|
eprint!("{}", diag.render(&content, path));
|
||||||
|
had_errors = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{program:#?}");
|
println!("{program:#?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if had_errors {
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user