From 415dea6fd29ef0d2fc2e52fe9d934b8c373ee625 Mon Sep 17 00:00:00 2001 From: Jooris Hadeler Date: Tue, 10 Mar 2026 18:49:15 +0100 Subject: [PATCH] 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 --- fluxc/src/cli.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++++ fluxc/src/main.rs | 29 ++++++++++------ 2 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 fluxc/src/cli.rs diff --git a/fluxc/src/cli.rs b/fluxc/src/cli.rs new file mode 100644 index 0000000..a98d00b --- /dev/null +++ b/fluxc/src/cli.rs @@ -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] [ ...]\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", + "".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, +} + +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 } +} diff --git a/fluxc/src/main.rs b/fluxc/src/main.rs index 93fbb85..aa9355c 100644 --- a/fluxc/src/main.rs +++ b/fluxc/src/main.rs @@ -1,26 +1,33 @@ -use std::{env::args, fs}; +use std::{fs, process}; use crate::parser::Parser; pub mod ast; +pub mod cli; pub mod diagnostics; pub mod lexer; pub mod parser; pub mod token; fn main() { - let path = args().nth(1).expect("usage: fluxc "); - let content = fs::read_to_string(&path).unwrap_or_else(|e| { - eprintln!("error: {e}"); - std::process::exit(1) - }); + let opts = cli::parse_args(); + let mut had_errors = false; - let mut parser = Parser::new(&content); - let program = parser.parse_program(); + for path in &opts.files { + let content = fs::read_to_string(path).unwrap_or_else(|e| cli::io_error(path, e)); - for diag in &parser.errors { - eprint!("{}", diag.render(&content, &path)); + let mut parser = Parser::new(&content); + let program = parser.parse_program(); + + for diag in &parser.errors { + eprint!("{}", diag.render(&content, path)); + had_errors = true; + } + + println!("{program:#?}"); } - println!("{program:#?}"); + if had_errors { + process::exit(1); + } }