use serde::{Deserialize, Serialize};
use std::fs;
use crate::paths;
use crate::utils::io;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct HomeboyConfig {
#[serde(default)]
pub defaults: Defaults,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Defaults {
#[serde(default = "default_install_methods")]
pub install_methods: InstallMethodsConfig,
#[serde(default = "default_version_candidates")]
pub version_candidates: Vec<VersionCandidateConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub post_version_bump_commands: Vec<String>,
#[serde(default = "default_deploy")]
pub deploy: DeployConfig,
#[serde(default = "default_permissions")]
pub permissions: PermissionsConfig,
}
impl Default for Defaults {
fn default() -> Self {
Self {
install_methods: default_install_methods(),
version_candidates: default_version_candidates(),
post_version_bump_commands: Vec::new(),
deploy: default_deploy(),
permissions: default_permissions(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstallMethodsConfig {
#[serde(default = "default_homebrew_config")]
pub homebrew: InstallMethodConfig,
#[serde(default = "default_cargo_config")]
pub cargo: InstallMethodConfig,
#[serde(default = "default_source_config")]
pub source: InstallMethodConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstallMethodConfig {
pub path_patterns: Vec<String>,
pub upgrade_command: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub list_command: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionCandidateConfig {
pub file: String,
pub pattern: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeployConfig {
#[serde(default = "default_scp_flags")]
pub scp_flags: Vec<String>,
#[serde(default = "default_artifact_prefix")]
pub artifact_prefix: String,
#[serde(default = "default_ssh_port")]
pub default_ssh_port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionsConfig {
#[serde(default = "default_local_permissions")]
pub local: PermissionModes,
#[serde(default = "default_remote_permissions")]
pub remote: PermissionModes,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionModes {
pub file_mode: String,
pub dir_mode: String,
}
fn default_install_methods() -> InstallMethodsConfig {
InstallMethodsConfig {
homebrew: default_homebrew_config(),
cargo: default_cargo_config(),
source: default_source_config(),
}
}
fn default_homebrew_config() -> InstallMethodConfig {
InstallMethodConfig {
path_patterns: vec!["/Cellar/".to_string(), "/homebrew/".to_string()],
upgrade_command: "brew update && brew upgrade homeboy".to_string(),
list_command: Some("brew list homeboy".to_string()),
}
}
fn default_cargo_config() -> InstallMethodConfig {
InstallMethodConfig {
path_patterns: vec!["/.cargo/bin/".to_string()],
upgrade_command: "cargo install homeboy".to_string(),
list_command: None,
}
}
fn default_source_config() -> InstallMethodConfig {
InstallMethodConfig {
path_patterns: vec!["/target/release/".to_string(), "/target/debug/".to_string()],
upgrade_command: "git pull && cargo build --release".to_string(),
list_command: None,
}
}
fn default_version_candidates() -> Vec<VersionCandidateConfig> {
vec![
VersionCandidateConfig {
file: "Cargo.toml".to_string(),
pattern: r#"version\s*=\s*"(\d+\.\d+\.\d+)""#.to_string(),
},
VersionCandidateConfig {
file: "package.json".to_string(),
pattern: r#""version"\s*:\s*"(\d+\.\d+\.\d+)""#.to_string(),
},
VersionCandidateConfig {
file: "composer.json".to_string(),
pattern: r#""version"\s*:\s*"(\d+\.\d+\.\d+)""#.to_string(),
},
VersionCandidateConfig {
file: "style.css".to_string(),
pattern: r"Version:\s*(\d+\.\d+\.\d+)".to_string(),
},
]
}
fn default_deploy() -> DeployConfig {
DeployConfig {
scp_flags: default_scp_flags(),
artifact_prefix: default_artifact_prefix(),
default_ssh_port: default_ssh_port(),
}
}
fn default_scp_flags() -> Vec<String> {
vec!["-O".to_string()]
}
fn default_artifact_prefix() -> String {
".homeboy-".to_string()
}
fn default_ssh_port() -> u16 {
22
}
fn default_permissions() -> PermissionsConfig {
PermissionsConfig {
local: default_local_permissions(),
remote: default_remote_permissions(),
}
}
fn default_local_permissions() -> PermissionModes {
PermissionModes {
file_mode: "g+rw".to_string(),
dir_mode: "g+rwx".to_string(),
}
}
fn default_remote_permissions() -> PermissionModes {
PermissionModes {
file_mode: "g+w".to_string(),
dir_mode: "g+w".to_string(),
}
}
pub fn load_defaults() -> Defaults {
load_config().defaults
}
pub fn load_config() -> HomeboyConfig {
load_config_from_file().unwrap_or_default()
}
fn load_config_from_file() -> crate::Result<HomeboyConfig> {
let path = paths::homeboy_json()?;
if !path.exists() {
return Err(crate::Error::other("homeboy.json not found"));
}
let content = io::read_file(&path, &format!("read {}", path.display()))?;
let config: HomeboyConfig = serde_json::from_str(&content).map_err(|e| {
crate::Error::validation_invalid_json(
e,
Some("parse homeboy.json".to_string()),
Some(content.chars().take(200).collect::<String>()),
)
})?;
Ok(config)
}
pub fn save_config(config: &HomeboyConfig) -> crate::Result<()> {
let path = paths::homeboy_json()?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|e| {
crate::Error::internal_io(e.to_string(), Some(format!("create {}", parent.display())))
})?;
}
let content = serde_json::to_string_pretty(config).map_err(|e| {
crate::Error::validation_invalid_json(e, Some("serialize homeboy.json".to_string()), None)
})?;
io::write_file(&path, &content, &format!("write {}", path.display()))?;
Ok(())
}
pub fn config_exists() -> bool {
paths::homeboy_json().map(|p| p.exists()).unwrap_or(false)
}
pub fn reset_config() -> crate::Result<bool> {
let path = paths::homeboy_json()?;
if path.exists() {
fs::remove_file(&path).map_err(|e| {
crate::Error::internal_io(e.to_string(), Some(format!("delete {}", path.display())))
})?;
Ok(true)
} else {
Ok(false)
}
}
pub fn config_path() -> crate::Result<String> {
Ok(paths::homeboy_json()?.display().to_string())
}
pub fn builtin_defaults() -> Defaults {
Defaults::default()
}