use clap::{Args, Parser, Subcommand};
use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub cmd: Commands,
#[arg(short, long, global = true)]
pub dir: Option<String>,
#[arg(short, long, default_value = "false", global = true)]
pub verbose: bool,
}
impl Cli {
pub fn get_args() -> Self {
Self::parse()
}
pub fn dbg_args() {
let args = Self::parse();
dbg!(args);
}
}
#[derive(Args, Clone, Debug)]
pub struct CommandActivateArgs {
#[arg(index = 1)]
pub dir: Option<String>,
}
#[derive(Args, Clone, Debug)]
pub struct CommandAddArgs {
#[arg(index = 1)]
pub id: String,
#[arg(short = 'f', long)]
pub file: Option<String>,
#[arg(short = 't', long)]
pub title: Option<String>,
#[arg(short = 'a', long = "author")]
pub authors: Option<Vec<String>>,
#[arg(short = 'y', long)]
pub year: Option<u32>,
#[arg(long)]
pub force: bool,
}
#[derive(Args, Clone, Debug)]
pub struct CommandConfigArgs {
#[arg(short = 'n', long = "owner.name", name = "NAME", num_args = 0..=1, default_missing_value = Self::_JUST_TO_PRINT_THIS_FIELD)]
pub owner_name: Option<String>,
#[arg(short = 'e', long = "owner.email", name = "EMAIL", num_args = 0..=1, default_missing_value = Self::_JUST_TO_PRINT_THIS_FIELD)]
pub owner_email: Option<String>,
#[arg(short = 'a', long = "owner.affiliation", name = "AFFILIATION", num_args = 0..=1, default_missing_value = Self::_JUST_TO_PRINT_THIS_FIELD)]
pub owner_affiliation: Option<String>,
#[arg(short = 'l', long = "owner.link", name = "LINK", num_args = 0..=1, default_missing_value = Self::_JUST_TO_PRINT_THIS_FIELD)]
pub owner_link: Option<String>,
#[arg(long = "show-config-path", default_value = "false", hide = true)]
pub show_config_path: bool,
}
impl CommandConfigArgs {
pub const _JUST_TO_PRINT_THIS_FIELD: &'static str = "JUST_TO_PRINT_THIS_FIELD";
}
#[derive(Args, Clone, Debug)]
pub struct CommandEditArgs {
#[arg(index = 1)]
pub id: String,
#[arg(short = 'f', long)]
pub file: Option<String>,
#[arg(short = 't', long)]
pub title: Option<String>,
#[arg(short = 'a', long = "author")]
pub authors: Option<Vec<String>>,
#[arg(short = 'y', long)]
pub year: Option<u32>,
}
#[derive(Args, Clone, Debug)]
pub struct CommandInfoArgs {}
#[derive(Args, Clone, Debug)]
pub struct CommandInitArgs {
#[arg(index = 1)]
pub dir: Option<String>,
}
#[derive(Args, Clone, Debug)]
pub struct CommandListArgs {}
#[derive(Args, Clone, Debug)]
pub struct CommandOpenArgs {
#[arg(index = 1)]
pub id: String,
}
#[derive(Args, Clone, Debug)]
pub struct CommandSearchArgs {}
#[derive(Args, Clone, Debug)]
pub struct CommandShowArgs {}
#[derive(Args, Clone, Debug)]
pub struct CommandRemoveArgs {
#[arg(index = 1)]
pub id: String,
}
#[derive(Subcommand, Debug, Clone)]
pub enum Commands {
Activate(CommandActivateArgs),
Add(CommandAddArgs),
Config(CommandConfigArgs),
Edit(CommandEditArgs),
List(CommandListArgs),
Remove(CommandRemoveArgs),
Open(CommandOpenArgs),
Info(CommandInfoArgs),
Init(CommandInitArgs),
Search(CommandSearchArgs),
Show(CommandShowArgs),
}
pub trait PaperDir {
fn _project_dirs() -> ProjectDirs {
if let Some(project_dirs) = ProjectDirs::from("org", "wqzhao", "termipaper") {
project_dirs
} else {
panic!("Cannot find the termipaper project directories");
}
}
fn _config_dir() -> PathBuf {
Self::_project_dirs().config_dir().to_path_buf()
}
fn _config_dir_str() -> String {
Self::_config_dir().to_str().unwrap().to_string()
}
fn _config_parent_dir() -> PathBuf {
Self::_config_dir().parent().unwrap().to_path_buf()
}
fn _data_dir() -> PathBuf {
Self::_project_dirs().data_dir().to_path_buf()
}
fn _data_dir_str() -> String {
Self::_data_dir().to_str().unwrap().to_string()
}
fn _default_dir() -> PathBuf {
Self::_data_dir().join("papers").to_path_buf()
}
fn _default_dir_str() -> String {
Self::_default_dir().to_str().unwrap().to_string()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ConfigDatabase {
pub date_created: String,
}
impl ConfigDatabase {
pub fn new() -> Self {
Self {
date_created: chrono::Local::now().format("%Y-%m-%d").to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ConfigOwner {
pub name: Option<String>,
pub email: Option<String>,
pub affiliation: Option<String>,
pub link: Option<String>,
}
impl ConfigOwner {
pub fn new() -> Self {
Self {
name: None,
email: None,
affiliation: None,
link: None,
}
}
}
pub type ConfigDatabases = HashMap<String, ConfigDatabase>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Config {
pub databases: Option<ConfigDatabases>,
pub owner: Option<ConfigOwner>,
pub activated: Option<String>,
}
impl PaperDir for Config {}
impl Config {
fn _config_file() -> PathBuf {
let config_dir = Self::_config_dir();
config_dir.join("config.yml")
}
pub fn _config_file_str() -> String {
Self::_config_file().to_str().unwrap().to_string()
}
pub fn new() -> Self {
Self {
databases: None,
owner: None,
activated: None,
}
}
pub fn from_file() -> Self {
let config_file = Self::_config_file();
if config_file.exists() {
let config_str = std::fs::read_to_string(config_file).unwrap();
match serde_yaml::from_str(&config_str) {
Ok(config) => config,
Err(_) => {
eprintln!(
"Error: Cannot parse the config file at '{}'.",
Self::_config_dir_str()
);
Self::new()
}
}
} else {
Self::new()
}
}
pub fn to_file(&self) {
let config_file = Self::_config_file();
let config_str = serde_yaml::to_string(&self).unwrap();
std::fs::create_dir_all(Self::_config_dir()).unwrap();
std::fs::write(config_file, config_str).unwrap();
}
}