qs 0.1.3

Quick SSH - Dead simple, zero-friction SSH wrapper that makes working with remote machines effortless
Documentation
use std::{collections::HashMap, fs, path::PathBuf};

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Default)]
pub struct Config {
    pub default: Option<String>,
    pub profiles: HashMap<String, Profile>,
}

impl Config {
    pub fn load() -> Result<Self, String> {
        Self::load_from(Self::path())
    }

    pub fn load_from(path: PathBuf) -> Result<Self, String> {
        if !path.exists() {
            return Ok(Self::default());
        }

        let content =
            fs::read_to_string(&path).map_err(|e| format!("Failed to read config file: {e}"))?;

        toml::from_str(&content).map_err(|e| format!("Invalid config file format: {e}"))
    }

    pub fn save(&self) -> Result<(), String> {
        self.save_to(Self::path())
    }

    pub fn save_to(&self, path: PathBuf) -> Result<(), String> {
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)
                .map_err(|e| format!("Failed to create config directory: {e}"))?;
        }

        let content =
            toml::to_string_pretty(self).map_err(|e| format!("Failed to serialize config: {e}"))?;

        fs::write(&path, content).map_err(|e| format!("Failed to write config file: {e}"))?;

        Ok(())
    }

    pub fn path() -> PathBuf {
        dirs::home_dir()
            .unwrap()
            .join(".config")
            .join("qs")
            .join("config.toml")
    }

    pub fn get_profile(&self, alias: &str) -> Result<&Profile, String> {
        if alias == "default" {
            match &self.default {
                Some(default_alias) => self
                    .profiles
                    .get(default_alias)
                    .ok_or_else(|| format!("Default alias '{default_alias}' not found")),
                None => {
                    if self.profiles.is_empty() {
                        Err("No host found. Add a host: 'qs add <alias> --host <host> --user <user>'".to_string())
                    } else {
                        Err("No default set. Use 'qs set-default <alias>'".to_string())
                    }
                }
            }
        } else {
            self.profiles
                .get(alias)
                .ok_or_else(|| format!("Alias '{alias}' doesn't exist"))
        }
    }
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Profile {
    pub host: String,
    pub user: String,
}