diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..935968b --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,79 @@ +//! This module defines the command line interface (CLI) for the application. + +use clap::{Parser, ValueEnum}; +use std::path::PathBuf; +use crate::solver; +use crate::nfa; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +pub enum OutputFormat { + Plain, + Tex, + Csv, +} + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct Args { + #[arg(value_name = "AUTOMATON_FILE", help = "Path to the input")] + pub filename: String, + + #[arg( + short = 'f', + long = "from", + value_enum, + default_value = "tikz", + help = "The input format" + )] + pub input_format: nfa::InputFormat, + + #[arg( + short = 'v', + long = "verbose", + action = clap::ArgAction::Count, + help = "Increase verbosity level" + )] + pub verbosity: u8, + + #[arg( + long, + short = 'l', + value_name = "LOG_FILE", + help = "Optional path to the log file. Defaults to stdout if not specified." + )] + pub log_output: Option, + + #[arg( + value_enum, + short = 't', + long = "to", + default_value = "plain", + help = "The output format" + )] + pub output_format: OutputFormat, + + #[arg( + short = 'o', + long = "output", + value_name = "OUTPUT_FILE", + help = "Where to write the strategy; defaults to stdout." + )] + pub output_path: Option, + + #[arg( + short, + long, + value_enum, + default_value = "input", + help = "The state reordering type." + )] + pub state_ordering: nfa::StateOrdering, + + #[arg( + long, + value_enum, + default_value = "strategy", + help = "Solver output specification." + )] + pub solver_output: solver::SolverOutput, +} diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..5bae5c0 --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,38 @@ + +//! This module provides functionality for setting up logging + +use env_logger::Builder; +use log::LevelFilter; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +/// Sets up the logger based on verbosity and optional log file path. +pub fn setup_logger(verbosity: u8, log_output: Option) { + let mut builder = Builder::from_default_env(); + builder.format_timestamp(None); + + builder.filter_level(match verbosity { + 0 => LevelFilter::Warn, + 1 => LevelFilter::Info, + 2 => LevelFilter::Debug, + _ => LevelFilter::Trace, + }); + + if let Some(log_path) = log_output { + match File::create(&log_path) { + Ok(file) => { + let writer = Box::new(file) as Box; + builder.target(env_logger::Target::Pipe(writer)); + } + Err(_) => { + eprintln!("Could not create log file at {}. Defaulting to stderr.", log_path.display()); + builder.target(env_logger::Target::Stderr); + } + } + } else { + builder.target(env_logger::Target::Stderr); + } + + builder.init(); +} diff --git a/src/main.rs b/src/main.rs index 810993d..4baddfa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ -use clap::{Parser, ValueEnum}; +use clap::Parser; use std::fs::File; use std::io; use std::io::Write; -use std::path::PathBuf; mod coef; mod downset; mod flow; @@ -15,85 +14,24 @@ mod semigroup; mod solution; mod solver; mod strategy; -use log::LevelFilter; +use log::info; +mod logging; +mod cli; -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] -enum OutputFormat { - Plain, - Tex, - Csv, -} - -#[derive(clap::Parser, Debug)] -#[command(version, about, long_about = None)] -struct Args { - #[arg(value_name = "AUTOMATON_FILE", help = "path to the input")] - filename: String, - - #[arg( - short = 'f', - long = "from", - value_enum, - default_value = "tikz", - help = "The input format" - )] - input_format: nfa::InputFormat, - - #[arg( - value_enum, - short = 't', - long = "to", - default_value = "plain", - help = "The output format" - )] - output_format: OutputFormat, - - /// path to write the strategy - #[arg( - short = 'o', - long = "output", - value_name = "OUTPUT_FILE", - help = "where to write the strategy; defaults to stdout." - )] - output_path: Option, - - #[arg( - short, - long, - value_enum, - default_value = "input", - help = format!("The state reordering type: preserves input order, sorts alphabetically or topologically.") - )] - state_ordering: nfa::StateOrdering, - - #[arg( - long, - value_enum, - default_value = "strategy", - help = format!("The solver output. Either yes/no and a winning strategy (the faster). Or the full maximal winning strategy.") - )] - solver_output: solver::SolverOutput, -} fn main() { - #[cfg(debug_assertions)] - env_logger::Builder::new() - .filter_level(LevelFilter::Debug) - .init(); - - #[cfg(not(debug_assertions))] - env_logger::Builder::new() - .filter_level(LevelFilter::Info) - .init(); // parse CLI arguments - let args = Args::parse(); + let args = cli::Args::parse(); + + // set up logging + logging::setup_logger(args.verbosity, args.log_output); // parse the input file let nfa = nfa::Nfa::load_from_file(&args.filename, &args.input_format, &args.state_ordering); // print the input automaton - println!("{}", nfa); + info!("{}", nfa); // compute the solution let solution = solver::solve(&nfa, &args.solver_output); @@ -135,20 +73,20 @@ fn main() { // prepare output string let output = match args.output_format { - OutputFormat::Tex => { + cli::OutputFormat::Tex => { let is_tikz = args.input_format == nfa::InputFormat::Tikz; let latex_content = solution.as_latex(if is_tikz { Some(&args.filename) } else { None }); latex_content.to_string() } - OutputFormat::Plain => { + cli::OutputFormat::Plain => { format!( "States: {}\n {}", nfa.states_str(), solution.winning_strategy ) } - OutputFormat::Csv => { + cli::OutputFormat::Csv => { format!( "Σ, {}\n{}\n", nfa.states().join(","), diff --git a/src/semigroup.rs b/src/semigroup.rs index 6f38cdd..ca4b2f7 100644 --- a/src/semigroup.rs +++ b/src/semigroup.rs @@ -8,7 +8,6 @@ use rayon::prelude::*; use std::collections::HashSet; // for distinct method use std::collections::VecDeque; use std::fmt; -use std::io::{self, Write}; pub struct FlowSemigroup { //invariant: all flows have the same dimension @@ -194,8 +193,8 @@ impl FlowSemigroup { let mut changed = false; while !to_process_mult.is_empty() { let flow = to_process_mult.pop_front().unwrap(); - print!("."); - io::stdout().flush().unwrap(); + //print!("."); + //io::stdout().flush().unwrap(); debug!("\nClose by product processing flow\n{}\n", flow); /*if Self::is_covered(&flow, &processed) { //debug!("Skipped inqueue\n{}", flow); @@ -250,7 +249,7 @@ impl FlowSemigroup { while !to_process_iter.is_empty() { let flow = to_process_iter.pop_front().unwrap(); debug_assert!(flow.is_idempotent()); - print!("."); + //print!("."); debug!("\nClose by product processing flow\n{}\n", flow); let iteration = flow.iteration(); if !Self::is_covered(&iteration, &self.flows) { diff --git a/src/strategy.rs b/src/strategy.rs index f92b63c..59bc503 100644 --- a/src/strategy.rs +++ b/src/strategy.rs @@ -3,7 +3,6 @@ use crate::downset::DownSet; use crate::graph::Graph; use crate::ideal::Ideal; use crate::nfa; -use std::io::{self, Write}; use std::collections::HashMap; use std::fmt; @@ -38,8 +37,8 @@ impl Strategy { ) -> bool { let mut result = false; for (a, downset) in self.0.iter_mut() { - print!("."); - io::stdout().flush().unwrap(); + // print!("."); + //io::stdout().flush().unwrap(); let edges = edges_per_letter.get(a).unwrap(); let safe_pre_image = safe.safe_pre_image(edges, maximal_finite_value); result |= downset.restrict_to(&safe_pre_image);