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::<Vec<_>>()
);
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<JobId, (usize, String)>,
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<dyn std::io::Write>,
}
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 {
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<usize> {
let size = self.out.write(buf)?;
self.flush()?;
Ok(size)
}
fn flush(&mut self) -> std::io::Result<()> {
self.out.flush()
}
}