use crate::{cli, find};
use clap::ArgMatches;
use colored::*;
use std::error::Error;
use trello::{
Attachment, Board, Card, ClientConfig, Label, List, Member, Renderable, SearchOptions,
TrelloClient, search,
};
type Result<T> = std::result::Result<T, Box<dyn Error>>;
pub fn setup_subcommand(matches: &ArgMatches) -> Result<()> {
debug!("Running setup subcommand with {:?}", matches);
println!("{}", "Welcome to tro!".green().bold());
println!();
println!(
"Please generate a Developer {} and {} from https://trello.com/app-key/",
"key".green(),
"token".green()
);
println!("and enter them below");
println!();
let key = cli::get_input("Enter Developer API Key: ")?;
let token = cli::get_input("Enter Token: ")?;
let config = ClientConfig {
host: ClientConfig::default_host(),
key,
token,
};
let client = TrelloClient::new(config);
println!();
match Member::me(&client) {
Ok(member) => {
client.config.save_config()?;
println!(
"Successfully logged in as {} with tro!",
member.username.green()
);
}
Err(_) => {
println!(
"{}",
"Unable to validate credentials. Please re-check and try again".red()
);
}
};
Ok(())
}
pub fn me_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running me subcommand with {:?}", matches);
let detailed = matches.is_present("detailed");
let member = Member::me(client)?;
if detailed {
println!("username: {}", member.username);
println!("full name: {}", member.full_name);
println!("id: {}", member.id);
} else {
println!("{}", member.username);
}
Ok(())
}
pub fn show_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running show subcommand with {:?}", matches);
let label_filter = matches.value_of("label_filter");
let interactive = matches.is_present("interactive");
let headers = !matches.is_present("no_headers");
let params = find::get_trello_params(matches);
debug!("Trello Params: {:?}", params);
let result = find::get_trello_object(client, ¶ms)?;
trace!("result: {:?}", result);
if interactive {
if result.card.is_some() {
eprintln!("Cannot use interactive code if a card pattern is specified");
} else if let Some(list) = result.list {
let cards = Card::get_all(client, &list.id)?;
if let Some(index) = cli::select_trello_object(&cards)? {
cli::edit_card(client, &cards[index])?;
}
} else if let Some(board) = result.board {
let lists = List::get_all(client, &board.id, true)?;
if let Some(index) = cli::select_trello_object(&lists)? {
println!("{}", &lists[index].render(headers));
}
} else {
let mut boards = Board::get_all(client)?;
if let Some(index) = cli::select_trello_object(&boards)? {
boards[index].retrieve_nested(client)?;
println!("{}", &boards[index].render(headers));
}
}
} else if let Some(card) = result.card {
cli::edit_card(client, &card)?;
} else if let Some(list) = result.list {
let list = match label_filter {
Some(label_filter) => list.filter(label_filter),
None => list,
};
println!("{}", list.render(headers));
} else if let Some(board) = result.board {
debug!("Board pattern detected");
let board = match label_filter {
Some(label_filter) => board.filter(label_filter),
None => board,
};
println!("{}", board.render(headers));
} else {
if headers {
println!("Open Boards");
println!("===========");
println!();
}
let boards = Board::get_all(client)?;
for b in boards {
println!("* {}", b.name);
}
}
Ok(())
}
pub fn move_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running move subcommand with {:?}", matches);
let params = find::get_trello_params(matches);
let result = find::get_trello_object(client, ¶ms)?;
let new_list_name = matches
.value_of("new_list_name")
.ok_or("Missing new list name")?;
let board = result.board.ok_or("Unable to retrieve board")?;
let card = result.card.ok_or("Unable to retrieve card")?;
let list = result
.list
.ok_or("Unable to retrieve list. Wildcards are currently unsupported with move")?;
let board_lists = board.lists.as_ref().ok_or("Missing target board lists")?;
let new_list = find::get_object_by_name(board_lists, new_list_name, true)?;
Card::change_list(client, &card.id, &new_list.id)?;
println!(
"Moved '{}' from '{}' to '{}'",
card.name.green(),
list.name.green(),
new_list.name.green()
);
Ok(())
}
pub fn open_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running open subcommand with {:?}", matches);
let id = matches.value_of("id").ok_or("Id not provided")?;
let object_type = matches.value_of("type").ok_or("type not provided")?;
if object_type == "board" {
debug!("Re-opening board with id {}", &id);
let board = Board::open(client, id)?;
eprintln!("Opened board: {}", &board.name.green());
eprintln!("id: {}", &board.id);
} else if object_type == "list" {
debug!("Re-opening list with id {}", &id);
let list = List::open(client, id)?;
eprintln!("Opened list: {}", &list.name.green());
eprintln!("id: {}", &list.id);
} else if object_type == "card" {
debug!("Re-openning card with id {}", &id);
let card = Card::open(client, id)?;
eprintln!("Opened card: {}", &card.name.green());
eprintln!("id: {}", &card.id);
} else {
unreachable!("Unknown object_type '{}' (this is a clap bug)", object_type);
}
Ok(())
}
fn close_board(client: &TrelloClient, board: &mut Board) -> Result<()> {
board.closed = true;
Board::update(client, board)?;
eprintln!("Closed board: '{}'", &board.name.green());
eprintln!("id: {}", &board.id);
Ok(())
}
fn close_list(client: &TrelloClient, list: &mut List) -> Result<()> {
list.closed = true;
List::update(client, list)?;
eprintln!("Closed list: '{}'", &list.name.green());
eprintln!("id: {}", &list.id);
Ok(())
}
fn close_card(client: &TrelloClient, card: &mut Card) -> Result<()> {
card.closed = true;
Card::update(client, card)?;
eprintln!("Closed card: '{}'", &card.name.green());
eprintln!("id: {}", &card.id);
Ok(())
}
pub fn close_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running close subcommand with {:?}", matches);
let params = find::get_trello_params(matches);
let result = find::get_trello_object(client, ¶ms)?;
let interactive = matches.is_present("interactive");
trace!("result: {:?}", result);
if interactive {
if result.card.is_some() {
eprintln!("Cannot run interactive mode if you specify a card pattern");
} else if let Some(list) = result.list {
let mut cards = Card::get_all(client, &list.id)?;
for index in cli::multiselect_trello_object(&cards, &[])? {
close_card(client, &mut cards[index])?;
}
} else if let Some(board) = result.board {
let mut lists = List::get_all(client, &board.id, false)?;
for index in cli::multiselect_trello_object(&lists, &[])? {
close_list(client, &mut lists[index])?;
}
} else {
let mut boards = Board::get_all(client)?;
for index in cli::multiselect_trello_object(&boards, &[])? {
close_board(client, &mut boards[index])?;
}
}
} else if let Some(mut card) = result.card {
close_card(client, &mut card)?;
} else if let Some(mut list) = result.list {
close_list(client, &mut list)?;
} else if let Some(mut board) = result.board {
close_board(client, &mut board)?;
}
Ok(())
}
pub fn create_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running create subcommand with {:?}", matches);
let params = find::get_trello_params(matches);
let result = find::get_trello_object(client, ¶ms)?;
let show = matches.is_present("show");
trace!("result: {:?}", result);
if let Some(list) = result.list {
let labels_to_apply = if let Some(label_names) = matches.values_of("label") {
let mut target_labels = vec![];
let labels =
Label::get_all(client, &result.board.ok_or("Unable to retrieve board")?.id)?;
for name in label_names {
match find::get_object_by_name(&labels, name, true) {
Ok(l) => target_labels.push(l.clone()),
Err(e) => {
eprintln!("{}", e);
return Ok(());
}
};
}
target_labels
} else {
vec![]
};
let name = match matches.value_of("name") {
Some(n) => String::from(n),
None => cli::get_input("Card name: ")?,
};
let card = Card::create(client, &list.id, &Card::new("", &name, "", None, "", None))?;
for label in labels_to_apply {
match Label::apply(client, &card.id, &label.id) {
Ok(_) => eprintln!("Applied {} label", &label.simple_render(),),
Err(e) => eprintln!("Unable to apply {} label: {}", &label.simple_render(), e),
};
}
if show {
cli::edit_card(client, &card)?;
}
} else if let Some(board) = result.board {
let name = match matches.value_of("name") {
Some(n) => String::from(n),
None => cli::get_input("List name: ")?,
};
List::create(client, &board.id, &name)?;
} else {
let name = match matches.value_of("name") {
Some(n) => String::from(n),
None => cli::get_input("Board name: ")?,
};
Board::create(client, &name)?;
}
Ok(())
}
pub fn attachments_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running attachments subcommand with {:?}", matches);
let params = find::get_trello_params(matches);
let result = find::get_trello_object(client, ¶ms)?;
let card = result.card.ok_or("Unable to find card")?;
let attachments = Attachment::get_all(client, &card.id)?;
for att in attachments {
println!("{}", &att.url);
}
Ok(())
}
pub fn attach_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running attach subcommand with {:?}", matches);
let params = find::get_trello_params(matches);
let result = find::get_trello_object(client, ¶ms)?;
let path = matches.value_of("path").ok_or("Missing path argument")?;
let card = result.card.ok_or("Unable to find card")?;
let attachment = Attachment::apply(client, &card.id, path)?;
println!("{}", attachment.render(true));
Ok(())
}
pub fn url_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running url subcommand with {:?}", matches);
let params = find::get_trello_params(matches);
let result = find::get_trello_object(client, ¶ms)?;
if let Some(card) = result.card {
println!("{}", card.url);
} else if result.list.is_some() {
println!("{}", result.board.ok_or("Unable to retrieve board")?.url);
} else if let Some(board) = result.board {
println!("{}", board.url);
}
Ok(())
}
fn replace_negative_prefix(query: &str) -> String {
if query.starts_with('~') {
query.replacen('~', "-", 1)
} else {
query.to_string()
}
}
pub fn search_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running search subcommand with {:?}", matches);
let query = matches
.values_of("query")
.ok_or("Missing query value")?
.map(replace_negative_prefix)
.collect::<Vec<String>>()
.join(" ");
let partial = matches.is_present("partial");
let interactive = matches.is_present("interactive");
let cards_limit = if let Some(v) = matches.value_of("limit") {
Some(v.parse()?)
} else {
None
};
let params = SearchOptions {
cards_limit,
boards_limit: Some(1),
partial,
};
let results = search(client, &query, ¶ms)?;
if interactive {
if let Some(index) = cli::select_trello_object(&results.cards)? {
cli::edit_card(client, &results.cards[index])?;
}
} else if !&results.cards.is_empty() {
for card in &results.cards {
println!(
"{} {}",
card.simple_render(),
format!("id: {}", card.id).green()
);
}
}
Ok(())
}
fn delete_label(client: &TrelloClient, card: &Card, label: &Label) -> Result<()> {
Label::remove(client, &card.id, &label.id)?;
eprintln!(
"Removed {} label from '{}'",
&label.simple_render(),
&card.name.green(),
);
Ok(())
}
fn apply_label(client: &TrelloClient, card: &Card, label: &Label) -> Result<()> {
Label::apply(client, &card.id, &label.id)?;
eprintln!(
"Applied {} label to '{}'",
&label.simple_render(),
&card.name.green()
);
Ok(())
}
pub fn label_subcommand(client: &TrelloClient, matches: &ArgMatches) -> Result<()> {
debug!("Running label subcommand with {:?}", matches);
let params = find::get_trello_params(matches);
let result = find::get_trello_object(client, ¶ms)?;
let interactive = matches.is_present("interactive");
let delete = matches.is_present("delete");
let label_names = matches.values_of("label_name");
let card = result.card.ok_or("Unable to find card")?;
let card_labels = card.labels.as_ref().ok_or("Unable to get card labels")?;
if delete {
let labels = card_labels;
let label_names = label_names.ok_or("Label names must be specified")?;
for name in label_names {
let label = match find::get_object_by_name(labels, name, true) {
Ok(l) => l,
Err(e) => {
eprintln!("{}", e);
continue;
}
};
delete_label(client, &card, label)?;
}
} else {
let board = result.board.ok_or("Unable to retrieve board")?;
let mut labels = Label::get_all(client, &board.id)?;
labels.sort_by_cached_key(|l| l.name.clone());
if interactive {
let selected_labels = cli::multiselect_trello_object(&labels, card_labels)?
.into_iter()
.map(|i| &labels[i])
.collect::<Vec<&Label>>();
for label in &selected_labels {
if !card_labels.contains(label) {
apply_label(client, &card, label)?;
}
}
for label in card_labels {
if !selected_labels.contains(&label) {
delete_label(client, &card, label)?;
}
}
} else {
let label_names = label_names.ok_or("Label names must be specified")?;
for name in label_names {
let label = match find::get_object_by_name(&labels, name, true) {
Ok(l) => l,
Err(e) => {
eprintln!(
"Label with pattern '{}' not found or is already assigned",
name
);
debug!("{}", e);
continue;
}
};
apply_label(client, &card, label)?;
}
}
}
Ok(())
}