use super::*;
pub mod add;
pub mod daemon;
pub mod init;
pub mod remove;
pub mod search;
use chrono::{DateTime, Utc};
use clap::{arg, Args};
use dialoguer::{Confirm, Input};
use interaction::*;
use learner::database::{Add, Query};
pub use self::{add::add, daemon::daemon, init::init, remove::remove, search::search};
#[derive(Subcommand, Clone)]
pub enum Commands {
#[cfg(feature = "tui")]
#[clap(hide = true)]
Tui,
Init,
Add {
identifier: String,
#[arg(long, group = "pdf_behavior")]
pdf: bool,
#[arg(long, group = "pdf_behavior")]
no_pdf: bool,
},
Remove {
query: String,
#[command(flatten)]
filter: SearchFilter,
#[arg(long)]
dry_run: bool,
#[arg(long)]
force: bool,
#[arg(long, group = "pdf_behavior")]
remove_pdf: bool,
#[arg(long, group = "pdf_behavior")]
keep_pdf: bool,
},
Search {
query: String,
#[arg(long)]
detailed: bool,
#[command(flatten)]
filter: SearchFilter,
},
Daemon {
#[command(subcommand)]
cmd: DaemonCommands,
},
}
#[derive(Args, Clone)]
pub struct SearchFilter {
#[arg(long)]
author: Option<String>,
#[arg(long)]
source: Option<String>,
#[arg(long)]
before: Option<String>,
}
impl UserInteraction for Cli {
fn confirm(&self, message: &str) -> Result<bool> {
println!("\n{} {}", style(PROMPT_PREFIX).yellow(), style(message).yellow().bold());
if self.accept_defaults {
return Ok(false);
}
let theme = dialoguer::theme::ColorfulTheme::default();
Ok(Confirm::with_theme(&theme).with_prompt("").default(false).interact()?)
}
fn prompt(&self, message: &str) -> Result<String> {
let theme = dialoguer::theme::ColorfulTheme::default();
Ok(Input::with_theme(&theme).with_prompt(message).interact_text()?)
}
fn reply(&self, content: ResponseContent) -> Result<()> {
match content {
ResponseContent::Papers(papers) => {
if papers.is_empty() {
println!("{} No papers found", style(ERROR_PREFIX).red());
return Ok(());
}
println!(
"{} Found {} papers:",
style(SUCCESS_PREFIX).green(),
style(papers.len()).yellow()
);
for (i, paper) in papers.iter().enumerate() {
let is_last = i == papers.len() - 1;
let prefix = if is_last { TREE_LEAF } else { TREE_BRANCH };
println!("{} {}", style(prefix).cyan(), style(&paper.title).white().bold());
let continuation = if is_last { " " } else { CONTINUE_PREFIX };
println!(
"{}Authors: {}",
style(continuation).cyan(),
style(&paper.authors.iter().map(|a| a.name.as_str()).collect::<Vec<_>>().join(", "))
.white()
);
}
if papers.len() > 1 {
println!("\nTip: Use --author, --source, or --before together to further refine results");
}
},
ResponseContent::Paper(paper) => {
println!("{} Paper details:", style(TREE_VERT).cyan());
println!("{} {}", style(TREE_BRANCH).cyan(), style(&paper.title).white().bold());
println!(
"{} Authors: {}",
style(TREE_BRANCH).cyan(),
style(&paper.authors.iter().map(|a| a.name.as_str()).collect::<Vec<_>>().join(", "))
.white()
);
println!("{} Abstract:", style(TREE_BRANCH).cyan());
let width = 80;
let mut current_line = String::new();
for word in paper.abstract_text.split_whitespace() {
if current_line.len() + word.len() + 1 > width {
println!("{} {}", style(TREE_VERT).cyan(), style(¤t_line).white());
current_line.clear();
}
if !current_line.is_empty() {
current_line.push(' ');
}
current_line.push_str(word);
}
if !current_line.is_empty() {
println!("{} {}", style(TREE_VERT).cyan(), style(¤t_line).white());
}
println!(
"{} Published: {}",
style(TREE_BRANCH).cyan(), style(&paper.publication_date).white()
);
if let Some(url) = &paper.pdf_url {
println!("{} PDF URL: {}", style(TREE_BRANCH).cyan(), style(url).blue().underlined());
let pdf_path = Database::default_storage_path().join(paper.filename());
if pdf_path.exists() {
println!(
"{} {} PDF available at:",
style(TREE_LEAF).cyan(),
style(SUCCESS_PREFIX).green()
);
println!(" {}", style(pdf_path.display()).white());
} else {
println!(
"{} {} PDF not downloaded",
style(TREE_LEAF).cyan(),
style(ERROR_PREFIX).yellow()
);
}
}
},
ResponseContent::Success(message) => {
println!("{} {}", style(SUCCESS_PREFIX).green(), style(message).white());
},
ResponseContent::Info(message) => {
println!("{} {}", style(INFO_PREFIX).green(), style(message).white());
},
ResponseContent::Error(error) => {
println!("{} {}", style(ERROR_PREFIX).red(), style(error).red());
},
}
Ok(())
}
}
fn parse_date(date_str: &str) -> Result<DateTime<Utc>> {
let parsed = if date_str.len() == 4 {
DateTime::parse_from_str(&format!("{}-01-01 00:00:00 +0000", date_str), "%Y-%m-%d %H:%M:%S %z")
} else if date_str.len() == 7 {
DateTime::parse_from_str(&format!("{}-01 00:00:00 +0000", date_str), "%Y-%m-%d %H:%M:%S %z")
} else {
DateTime::parse_from_str(&format!("{} 00:00:00 +0000", date_str), "%Y-%m-%d %H:%M:%S %z")
}?;
Ok(parsed.with_timezone(&Utc))
}