#![warn(missing_docs, clippy::missing_docs_in_private_items)]
use std::{path::PathBuf, str::FromStr};
use clap::{builder::ArgAction, Parser, Subcommand};
use console::style;
use error::LearnerdError;
use learner::{database::Database, paper::Paper, prelude::*, Config, Learner};
use tracing_subscriber::EnvFilter;
pub mod commands;
#[cfg(not(target_os = "windows"))] pub mod daemon;
pub mod error;
pub mod interaction;
#[cfg(feature = "tui")] pub mod tui;
#[cfg(not(target_os = "windows"))] use crate::daemon::*;
use crate::{commands::*, error::*};
static INFO_PREFIX: &str = "ℹ ";
static SUCCESS_PREFIX: &str = "✓ ";
#[cfg(not(target_os = "windows"))]
static WARNING_PREFIX: &str = "⚠️ ";
static ERROR_PREFIX: &str = "✗ ";
static PROMPT_PREFIX: &str = "❯ ";
static CONTINUE_PREFIX: &str = "│ ";
static TREE_VERT: &str = "│";
static TREE_BRANCH: &str = "├";
static TREE_LEAF: &str = "└";
#[derive(Parser)]
#[command(name = "learner")]
#[command(bin_name = "learner")]
#[command(author, version, about = "Daemon and CLI for the learner paper management system")]
pub struct CliArgs {
#[arg(
short,
long,
action = ArgAction::Count,
global = true,
help = "Increase logging verbosity"
)]
verbose: u8,
#[arg(long, short, global = true)]
path: Option<PathBuf>,
#[command(subcommand)]
command: Option<Commands>,
#[arg(long, hide = true, global = true)]
accept_defaults: bool,
#[arg(short = 'g', long = "generate", value_enum)]
generator: Option<clap_complete::Shell>,
}
pub struct Cli {
args: CliArgs,
learner: Option<Learner>,
}
fn setup_logging(verbosity: u8) {
let filter = match verbosity {
0 => "error",
1 => "warn",
2 => "info",
3 => "debug",
_ => "trace",
};
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(filter));
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_file(true)
.with_line_number(true)
.with_thread_ids(true)
.with_target(true)
.init();
}
#[tokio::main]
async fn main() -> Result<()> {
let args = CliArgs::parse();
let cmd = &mut <CliArgs as clap::CommandFactory>::command();
if let Some(generator) = args.generator {
clap_complete::generate(generator, cmd, cmd.get_name().to_string(), &mut std::io::stdout());
return Ok(());
}
let command = args.command.clone().unwrap_or_else(|| {
#[cfg(feature = "tui")]
return Commands::Tui;
#[cfg(not(feature = "tui"))]
{
println!("Please specify a command. Use --help for usage information.");
std::process::exit(1);
}
});
#[cfg(not(target_os = "windows"))]
if let Commands::Daemon { .. } = command {
} else {
setup_logging(args.verbose);
}
#[cfg(target_os = "windows")]
setup_logging(args.verbose);
let mut cli = Cli { args, learner: None };
if !matches!(command, Commands::Init(_)) {
cli.learner = Some(Learner::from_path(Config::default_path()?).await?);
}
match command {
Commands::Init(init_options) => init(&mut cli, init_options).await,
Commands::Add(add_options) => {
add(&mut cli, add_options).await?;
Ok(())
},
Commands::Remove(remove_options) => remove(&mut cli, remove_options).await,
Commands::Search(search_options) => search(&mut cli, search_options).await,
#[cfg(not(target_os = "windows"))]
Commands::Daemon { cmd } => daemon(cmd).await,
#[cfg(feature = "tui")]
Commands::Tui =>
if let Some(learner) = cli.learner.take() {
tui::run(learner).await
} else {
Err(LearnerdError::from(LearnerError::Config("Failed to initialize learner".to_string())))
},
}
}