use core::time::Duration; use futures_lite::StreamExt; use std::collections::BTreeMap; use std::io::{stdout, IsTerminal}; use willdo::{ execution::progress::{Observation, Progress}, job::JobId, }; fn main() { let mut show = Show::default(); let code = futures_lite::future::block_on(async move { let graph = willdo::cli::build()?; let mut desired_goals = willdo::cli::goals(); let mut actual_goals = vec![]; println!("------- Schedule --------"); for (indent, job, info) in graph.tree(&desired_goals) { print!("{}", " ".repeat(indent)); println!("{job} ({info})"); desired_goals.retain(|g| g.as_ref() != job.to_string()); if indent == 0 { actual_goals.push(job.clone()); } } if !desired_goals.is_empty() { println!("------- Summary --------"); println!("Some goals were not available: {desired_goals:?}"); return Ok(desired_goals.len() as i32); } println!("------- Execution --------"); let mut events = willdo::cli::run(graph); while let Some((j, p)) = events.next().await { if let Progress::Observation(Observation::Completed(0)) = &p { actual_goals.retain(|g| g != &j) } show.update(j, p); std::thread::sleep(Duration::from_millis(100)); } println!("------- Summary --------"); if actual_goals.is_empty() { println!("All good!"); Ok(0) } else { println!( "Some goals were not reached: {:?}", actual_goals .iter() .map(|j| j.to_string()) .collect::>() ); Ok(actual_goals.len() as i32) } }) .unwrap_or_else(|e: willdo::cli::Error| { eprintln!("WillDo: {e}"); 1 }); std::process::exit(code); } #[derive(Debug, Default)] pub struct Show { jobs: BTreeMap, printer: Printer, } impl Show { fn update(&mut self, job: JobId, progress: Progress) { let line: String = format!("{job}: {progress:?}"); let (offset, entry) = self.entry(job); *entry = line.clone(); self.print(offset, &line) } fn entry(&mut self, job: JobId) -> (usize, &mut String) { let count = self.jobs.len(); let (pos, entry) = self.jobs.entry(job).or_insert((count, "".into())); let offset = count - *pos; (offset, entry) } fn print(&mut self, offset: usize, entry: &str) { if offset != 0 { self.printer.line_up(offset); } self.printer.line_wipe(); use std::io::Write; writeln!(self.printer, "{entry}").expect("print"); if offset != 0 { self.printer.line_down(offset.saturating_sub(1)); } } } struct Printer { pub ansi: bool, pub out: Box, } impl core::fmt::Debug for Printer { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Printer") .field("ansi", &self.ansi) .field("out", &"...") .finish() } } impl Default for Printer { fn default() -> Self { Self::new() } } impl Printer { pub fn ansi(mut self) -> Self { self.ansi = true; self } pub fn plain(mut self) -> Self { self.ansi = false; self } pub fn new() -> Self { //consider https://docs.rs/raw_tty/latest/raw_tty/ match std::fs::OpenOptions::new() .read(true) .write(true) .open("/dev/tty") { Ok(out) => Self { ansi: true, out: Box::new(out), }, Err(_) => { let out = stdout(); Self { ansi: out.is_terminal(), out: Box::new(out.lock()), } } } } pub fn line_up(&mut self, n: usize) { if self.ansi { use std::io::Write; let _silence = write!(self, "\x1b[{n}F"); } } pub fn line_down(&mut self, n: usize) { if self.ansi { use std::io::Write; let _silence = write!(self, "\x1b[{n}E"); } } pub fn line_wipe(&mut self) { if self.ansi { use std::io::Write; let _silence = write!(self, "\x1b[2K"); } } } impl std::io::Write for Printer { fn write(&mut self, buf: &[u8]) -> std::io::Result { let size = self.out.write(buf)?; self.flush()?; Ok(size) } fn flush(&mut self) -> std::io::Result<()> { self.out.flush() } }