use super::error::{Error, ErrorKind};
use directories::BaseDirs;
use failure::ResultExt;
use lazy_static::lazy_static;
use serde::Deserialize;
use shellexpand;
use std::collections::{BTreeMap, HashMap};
use std::fs;
use structopt::StructOpt;
use toml;
type Commands = BTreeMap<String, String>;
lazy_static! {
static ref STEPS_MAPPING: HashMap<&'static str, Step> = {
let mut m = HashMap::new();
m.insert("system", Step::System);
m.insert("git-repos", Step::GitRepos);
m.insert("vim", Step::Vim);
m.insert("emacs", Step::Emacs);
m.insert("gem", Step::Gem);
#[cfg(windows)]
m.insert("powershell", Step::Powershell);
m
};
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Step {
System,
GitRepos,
Vim,
Emacs,
Gem,
#[cfg(windows)]
Powershell,
}
impl Step {
fn possible_values() -> Vec<&'static str> {
STEPS_MAPPING.keys().cloned().collect()
}
}
impl std::str::FromStr for Step {
type Err = structopt::clap::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(STEPS_MAPPING.get(s).unwrap().clone())
}
}
#[derive(Deserialize, Default)]
pub struct ConfigFile {
pre_commands: Option<Commands>,
commands: Option<Commands>,
git_repos: Option<Vec<String>>,
disable: Option<Vec<Step>>,
}
impl ConfigFile {
fn read(base_dirs: &BaseDirs) -> Result<ConfigFile, Error> {
let config_path = base_dirs.config_dir().join("topgrade.toml");
if !config_path.exists() {
return Ok(Default::default());
}
let mut result: Self = toml::from_str(&fs::read_to_string(config_path).context(ErrorKind::Configuration)?)
.context(ErrorKind::Configuration)?;
if let Some(ref mut paths) = &mut result.git_repos {
for path in paths.iter_mut() {
*path = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
}
}
Ok(result)
}
}
#[derive(StructOpt, Debug)]
#[structopt(name = "Topgrade")]
pub struct CommandLineArgs {
#[structopt(short = "t", long = "tmux")]
run_in_tmux: bool,
#[structopt(short = "c", long = "cleanup")]
cleanup: bool,
#[structopt(short = "n", long = "dry-run")]
dry_run: bool,
#[structopt(long = "no-retry")]
no_retry: bool,
#[structopt(long = "disable", raw(possible_values = "&Step::possible_values()"))]
disable: Vec<Step>,
}
pub struct Config {
opt: CommandLineArgs,
config_file: ConfigFile,
}
impl Config {
pub fn load(base_dirs: &BaseDirs) -> Result<Self, Error> {
Ok(Self {
opt: CommandLineArgs::from_args(),
config_file: ConfigFile::read(base_dirs)?,
})
}
pub fn pre_commands(&self) -> &Option<Commands> {
&self.config_file.pre_commands
}
pub fn commands(&self) -> &Option<Commands> {
&self.config_file.commands
}
pub fn git_repos(&self) -> &Option<Vec<String>> {
&self.config_file.git_repos
}
pub fn should_run(&self, step: Step) -> bool {
!(self
.config_file
.disable
.as_ref()
.map(|d| d.contains(&step))
.unwrap_or(false)
|| self.opt.disable.contains(&step))
}
pub fn run_in_tmux(&self) -> bool {
self.opt.run_in_tmux
}
#[cfg(not(windows))]
pub fn cleanup(&self) -> bool {
self.opt.cleanup
}
pub fn dry_run(&self) -> bool {
self.opt.dry_run
}
pub fn no_retry(&self) -> bool {
self.opt.no_retry
}
}