use std::env;
use std::error::Error;
use std::io::{Read, Write};
use std::process;
use std::{thread, time};
use trello::Renderable;
use trello::{Card, CardContents, TrelloClient, TrelloError, TrelloObject};
pub fn multiselect_trello_object<T: TrelloObject + Renderable + PartialEq>(
objects: &[T],
selected: &[T],
) -> Result<Vec<usize>, std::io::Error> {
let result = dialoguer::MultiSelect::new()
.items_checked(
&objects
.iter()
.map(|o| (o.simple_render(), selected.contains(o)))
.collect::<Vec<(String, bool)>>(),
)
.with_prompt(format!("Select {}s using space key", T::get_type()))
.interact()?;
Ok(result)
}
pub fn select_trello_object<T: TrelloObject + Renderable>(
objects: &[T],
) -> Result<Option<usize>, std::io::Error> {
let result = dialoguer::Select::new()
.items(
&objects
.iter()
.map(|o| o.simple_render())
.collect::<Vec<String>>(),
)
.with_prompt(format!("Select {}", T::get_type()))
.interact_opt()?;
Ok(result)
}
pub fn get_input(text: &str) -> Result<String, rustyline::error::ReadlineError> {
let mut rl = rustyline::Editor::<()>::new();
rl.bind_sequence(
rustyline::KeyPress::ControlLeft,
rustyline::Cmd::Move(rustyline::Movement::BackwardWord(1, rustyline::Word::Big)),
);
rl.bind_sequence(
rustyline::KeyPress::ControlRight,
rustyline::Cmd::Move(rustyline::Movement::ForwardWord(
1,
rustyline::At::Start,
rustyline::Word::Big,
)),
);
rl.readline(text)
}
pub fn edit_card(client: &TrelloClient, card: &Card) -> Result<(), Box<dyn Error>> {
let mut file = tempfile::Builder::new().suffix(".md").tempfile()?;
let editor_env = env::var("EDITOR").unwrap_or_else(|_| String::from("vi"));
debug!("Using editor: {}", editor_env);
debug!("Editing card: {:?}", card);
writeln!(file, "{}", card.render(true))?;
let mut new_card = card.clone();
loop {
let mut editor = process::Command::new(&editor_env)
.arg(file.path())
.spawn()?;
let mut result: Option<Result<Card, TrelloError>> = None;
loop {
const SLEEP_TIME: u64 = 500;
debug!("Sleeping for {}ms", SLEEP_TIME);
thread::sleep(time::Duration::from_millis(SLEEP_TIME));
let mut buf = String::new();
file.reopen()?.read_to_string(&mut buf)?;
let contents: CardContents = match buf.trim_end().parse() {
Ok(c) => c,
Err(e) => {
debug!("Unable to parse Card Contents: {}", e);
if let Some(ecode) = editor.try_wait()? {
debug!("Editor closed (code {}), exiting watch loop", ecode);
result = Some(Err(e));
break;
} else {
continue;
}
}
};
if result.is_none()
|| matches!(result, Some(Err(_)))
|| new_card.name != contents.name
|| new_card.desc != contents.desc
{
new_card.name = contents.name;
new_card.desc = contents.desc;
debug!("Updating card: {:?}", new_card);
result = Some(Card::update(client, &new_card));
match &result {
Some(Ok(_)) => debug!("Updated card"),
Some(Err(e)) => debug!("Error updating card {:?}", e),
None => unreachable!(),
};
}
if let Some(ecode) = editor.try_wait()? {
debug!("Exiting editor loop with code: {}", ecode);
break;
}
}
match &result {
None => {
debug!("Exiting retry loop due to no result being ever retrieved");
break;
}
Some(Ok(_)) => {
debug!("Exiting retry loop due to successful last update");
break;
}
Some(Err(e)) => {
eprintln!("An error occurred while trying to update the card.");
eprintln!("{}", e);
eprintln!();
get_input("Press 'enter' to go back to your editor")?;
}
}
}
Ok(())
}