use std::fmt::Display;
use baker::Bake;
use clap::{Args, Parser, Subcommand};
use derive_more::{From, FromStr, Into};
#[cfg(test)]
use fake::{Dummy, Fake};
use figment::{
map,
value::{Dict, Map, Value},
Error, Metadata, Profile, Provider,
};
use itertools::Itertools;
use tap::Pipe;
#[cfg(feature = "profiling")]
use tracing::instrument;
use crate::{config::LinkType, helpers, FILE_EXTENSIONS, PROJECT_DIRS};
#[derive(From, Debug, FromStr, Into, Clone)]
#[cfg_attr(test, derive(Dummy, PartialEq, Eq))]
pub struct PathBuf(pub(crate) std::path::PathBuf);
impl Display for PathBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.display())
}
}
#[derive(Parser, Debug, Bake)]
#[clap(version, about)]
#[cfg_attr(test, derive(Dummy, PartialEq, Eq))]
#[baked(name = "Globals", derive(Debug))]
pub struct Cli {
#[clap(long, short)]
#[baked(ignore)]
pub(crate) dotfiles: Option<PathBuf>,
#[clap(long, short, default_value_t = {
helpers::get_file_with_format(PROJECT_DIRS.config_dir(), "config")
.map(|p| p.0)
.unwrap_or_else(|| PROJECT_DIRS.config_dir().join(format!("config.{}", FILE_EXTENSIONS[0].0)))
.into()
})]
#[baked(ignore)]
pub(crate) config: PathBuf,
#[clap(long, short = 'r')]
pub(crate) dry_run: bool,
#[clap(subcommand)]
#[baked(ignore)]
pub(crate) command: Command,
}
#[derive(Debug, Args, Clone)]
#[cfg_attr(test, derive(Dummy, PartialEq, Eq))]
pub struct Dots {
#[clap(default_value = "**")]
pub(crate) dots: Vec<String>,
}
impl Dots {
#[cfg_attr(feature = "profiling", instrument)]
fn add_root(&self) -> Self {
Self {
dots: self.dots.iter().map(|d| if d.starts_with('/') { d.to_string() } else { format!("/{d}") }).collect_vec(),
}
}
}
#[derive(Debug, Args, Bake, Clone)]
#[cfg_attr(test, derive(Dummy, PartialEq, Eq))]
#[baked(name = "Link", derive(Debug))]
pub struct LinkRaw {
#[clap(flatten)]
#[baked(type = "Vec<String>", map_fn(bake = "|l| l.dots.add_root().dots"))]
pub(crate) dots: Dots,
#[clap(long, short)]
pub(crate) force: bool,
#[clap(long, short)]
#[baked(ignore)]
link_type: Option<LinkType>,
}
#[derive(Debug, Args, Bake, Clone)]
#[cfg_attr(test, derive(Dummy, PartialEq, Eq))]
#[baked(name = "Install", derive(Debug))]
#[allow(clippy::struct_excessive_bools)]
pub struct InstallRaw {
#[clap(flatten)]
#[baked(type = "Vec<String>", map_fn(bake = "|l| l.dots.add_root().dots"))]
pub(crate) dots: Dots,
#[clap(long, short)]
pub(crate) continue_on_error: bool,
#[clap(long, short = 'd')]
pub(crate) skip_dependencies: bool,
#[clap(long, short = 'i')]
pub(crate) skip_installation_dependencies: bool,
#[clap(long, short = 'a')]
pub(crate) skip_all_dependencies: bool,
}
#[derive(Debug, Args, Bake, Clone)]
#[cfg_attr(test, derive(Dummy, PartialEq, Eq))]
#[baked(name = "Sync", derive(Debug))]
pub struct SyncRaw {
#[clap(flatten)]
#[baked(type = "Vec<String>", map_fn(bake = "|l| l.dots.add_root().dots"))]
pub(crate) dots: Dots,
#[clap(long, short)]
pub(crate) no_push: bool,
#[clap(long, short)]
pub(crate) message: Option<String>,
}
#[derive(Subcommand, Debug, Clone)]
#[cfg_attr(test, derive(Dummy, PartialEq, Eq))]
pub enum Command {
Clone {
repo: String,
},
Init {
repo: Option<String>,
},
Link {
#[clap(flatten)]
link: LinkRaw,
},
Sync {
#[clap(flatten)]
sync: SyncRaw,
},
Install {
#[clap(flatten)]
install: InstallRaw,
},
}
impl Provider for Cli {
fn metadata(&self) -> Metadata {
Metadata::named("Cli")
}
#[cfg_attr(feature = "profiling", instrument)]
fn data(&self) -> Result<Map<Profile, Dict>, Error> {
let mut dict = Dict::new();
if let Some(dotfiles) = &self.dotfiles {
dict.insert("dotfiles".to_owned(), Value::serialize(dotfiles.to_string())?);
}
if let Command::Link {
link: LinkRaw { link_type: Some(link_type), .. },
} = &self.command
{
dict.insert("link_type".to_owned(), Value::serialize(link_type)?);
}
map! {
Profile::Global => dict
}
.pipe(Ok)
}
}