tiempo-rs/src/interactive.rs

154 lines
4.0 KiB
Rust
Raw Normal View History

2021-08-25 14:30:27 -05:00
use std::io::{self, BufRead, Write};
2022-05-07 10:37:30 -05:00
use std::collections::{HashMap, hash_map};
2022-05-07 22:37:05 -05:00
use chrono::{DateTime, Utc};
2021-08-25 14:30:27 -05:00
use crate::io::Streams;
use crate::database::Database;
use crate::error::Result;
use crate::commands::Facts;
2022-05-07 22:37:05 -05:00
use crate::models::Entry;
2021-08-25 14:30:27 -05:00
fn read_line<I: BufRead>(mut r#in: I) -> io::Result<String> {
let mut pre_n = String::new();
2021-08-25 14:30:27 -05:00
r#in.read_line(&mut pre_n)?;
Ok(pre_n)
}
pub fn ask<D, I, O, E>(streams: &mut Streams<D, I, O, E>, question: &str) -> io::Result<bool>
where
D: Database,
I: BufRead,
O: Write,
E: Write,
{
2021-08-25 14:30:27 -05:00
write!(streams.out, "{} [y/N] ", question)?;
streams.out.flush()?;
2021-08-25 14:30:27 -05:00
Ok(read_line(&mut streams.r#in)?.to_lowercase().starts_with('y'))
}
enum Choice {
Number(usize),
Quit,
CtrlD,
Whatever,
}
fn to_choice(s: String) -> Choice {
let s = s.trim();
if let Ok(n) = s.parse::<usize>() {
if n == 0 {
Choice::Whatever
} else {
Choice::Number(n)
}
2022-05-07 10:37:30 -05:00
} else if s.is_empty() {
Choice::CtrlD
} else if s.to_lowercase() == "q" {
Choice::Quit
} else {
Choice::Whatever
}
}
/// Offers the last N entries (configurable) to the user and waits for a choice.
pub fn note_from_last_entries<D, I, O, E>(streams: &mut Streams<D, I, O, E>, facts: &Facts, current_sheet: &str) -> Result<Option<String>>
where
D: Database,
I: BufRead,
O: Write,
E: Write,
{
let entries = streams.db.entries_by_sheet(current_sheet, None, None)?;
let mut uniques = HashMap::new();
entries
.into_iter().rev()
.filter_map(|e| e.note.map(|n| (n, e.start)))
2022-05-07 10:37:30 -05:00
.map(|(n, s)| if let hash_map::Entry::Vacant(e) = uniques.entry(n) {
e.insert(s);
true
2022-05-07 10:37:30 -05:00
} else {
false
})
.filter(|&i| i)
.take(facts.config.interactive_entries)
.count();
let mut uniques: Vec<_> = uniques.into_iter().collect();
2022-05-07 10:37:30 -05:00
uniques.sort_unstable_by_key(|(_n, s)| *s);
writeln!(streams.out, "Latest entries of sheet '{current_sheet}':\n")?;
let formatter = timeago::Formatter::new();
for (i, (note, time)) in uniques.iter().enumerate() {
let i = i + 1;
let ago = formatter.convert_chrono(*time, facts.now);
writeln!(streams.out, " {i}) {note} ({ago})")?;
}
writeln!(streams.out, "\nenter number or q to cancel")?;
loop {
write!(streams.out, ">> ")?;
streams.out.flush()?;
let choice = to_choice(read_line(&mut streams.r#in)?);
match choice {
Choice::Number(i) => if let Some((n, _s)) = uniques.get(i - 1) {
return Ok(Some(n.clone()));
} else {
writeln!(streams.out, "Not an option")?;
}
Choice::Quit => return Ok(None),
Choice::CtrlD => {
writeln!(streams.out)?;
return Ok(None);
}
Choice::Whatever => writeln!(streams.out, "Not an option")?,
}
};
}
2022-05-07 22:37:05 -05:00
pub fn confirm_deletion<D, I, O, E>(streams: &mut Streams<D, I, O, E>, entry: Entry, now: DateTime<Utc>) -> Result<()>
where
D: Database,
I: BufRead,
O: Write,
E: Write,
{
let id = entry.id;
let note = entry.note.unwrap_or_else(|| "-empty note-".into());
let formatter = {
let mut formatter = timeago::Formatter::new();
formatter.ago("");
formatter
};
let duration = if let Some(end) = entry.end {
let span = formatter.convert_chrono(entry.start, end);
format!("finished with a timespan of {span}")
} else {
let span = formatter.convert_chrono(entry.start, now);
format!("unfinished and running for {span}")
};
if ask(streams, &format!("\
are you sure you want to delete entry {id} with note
\"{note}\"
{duration})"))? {
streams.db.delete_entry_by_id(entry.id)?;
writeln!(streams.out, "Gone")?;
} else {
writeln!(streams.out, "Don't worry, it's still there")?;
}
Ok(())
}