use std::collections::HashSet;
use anyhow::Result;
use async_trait::async_trait;
use clap::Args;
use toml_edit::{Array, DocumentMut, Item, Table, value};
use crate::{
brew::{
types::BrewListType,
utils::{brew_list, ensure_brew},
},
cli::atomic::should_dry_run,
commands::{Runnable, RunnableInvokeRules},
config::ConfigCoreMethods,
context::AppContext,
log_cute, log_dry, log_info, log_warn,
util::io::confirm,
};
#[derive(Debug, Args)]
pub struct BrewBackupCmd {
#[arg(long)]
no_deps: bool,
}
#[async_trait]
impl Runnable for BrewBackupCmd {
fn get_invoke_rules(&self) -> RunnableInvokeRules {
RunnableInvokeRules {
do_config_autosync: false,
require_sudo: false,
respect_lock: true,
}
}
async fn run(&self, ctx: &AppContext) -> Result<()> {
let dry_run = should_dry_run();
let mut backup_no_deps = self.no_deps;
ensure_brew().await?;
let mut doc = if let Ok(doc) = ctx.config.load_as_mut().await {
doc
} else {
log_warn!("Configuration does not exist; a new one will be created.");
DocumentMut::new()
};
let brew_item = doc.entry("brew").or_insert(Item::Table(Table::new()));
let brew_tbl = if let Some(brew_tbl) = brew_item.as_table_mut() {
brew_tbl
} else {
&mut Table::new()
};
let no_deps = brew_tbl
.get("no_deps")
.and_then(toml_edit::Item::as_bool)
.unwrap_or(false);
if self.no_deps {
if !no_deps {
log_info!("Setting no_deps to true in config for later reads.",);
brew_tbl["no_deps"] = value(true);
} else {
log_info!("no_deps already found true in configuration, so not setting.",);
}
} else if no_deps && confirm("The previous backup was without dependencies. Do now too?") {
backup_no_deps = true;
} else {
brew_tbl["no_deps"] = Item::None;
}
let deps = if backup_no_deps {
brew_list(BrewListType::Dependency).await?
} else {
HashSet::new()
};
let formulas = brew_list(BrewListType::Formula).await?;
let casks = brew_list(BrewListType::Cask).await?;
let taps = brew_list(BrewListType::Tap).await?;
let mut formula_arr = Array::new();
for formula in &formulas {
if backup_no_deps {
if !deps.contains(formula) {
if dry_run {
log_dry!("Would push {formula} as a manually installed formula.",);
} else {
log_info!("Pushing {formula} as a manually installed formula.",);
formula_arr.push(formula.clone());
}
}
} else if dry_run {
log_dry!("Would push {formula}");
} else {
log_info!("Pushing {formula}");
formula_arr.push(formula.clone());
}
}
log_info!("Pushed {} formulae.", formula_arr.len());
brew_tbl["formulae"] = value(formula_arr);
let mut cask_arr = Array::new();
for cask in &casks {
if backup_no_deps {
if !deps.contains(cask) {
if dry_run {
log_dry!("Would push {cask} as a manually installed cask.",);
} else {
log_info!("Pushing {cask} as a manually installed cask.",);
cask_arr.push(cask.clone());
}
}
} else if dry_run {
log_dry!("Would push {cask}");
} else {
log_info!("Pushed {cask} as a cask.");
cask_arr.push(cask.clone());
}
}
log_info!("Pushed {} casks.", cask_arr.len());
brew_tbl["casks"] = value(cask_arr);
let mut taps_arr = Array::new();
for tap in &taps {
if dry_run {
log_dry!("Would push {tap} as tap.");
} else {
log_info!("Pushed {tap} as a tap.");
taps_arr.push(tap.clone());
}
}
log_info!("Pushed {} taps.", taps_arr.len());
brew_tbl["taps"] = value(taps_arr);
let path = ctx.config.path();
if dry_run {
log_info!("Backup would be saved to {:?}", path);
} else {
doc.save(path).await?;
log_cute!("Backup written to current configuration file.");
}
Ok(())
}
}