forevervm 0.1.28

foreverVM CLI. Allows you to start foreverVMs and run a REPL on them.
Documentation
use crate::DEFAULT_SERVER_URL;
use anyhow::{Context, Result};
use dirs::home_dir;
use forevervm_sdk::{api::token::ApiToken, client::ForeverVMClient};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use url::Url;

#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Config {
    pub token: Option<ApiToken>,
    pub server_url: Option<Url>,
}

impl Config {
    pub fn server_url(&self) -> Result<Url> {
        if let Some(url) = &self.server_url {
            return Ok(url.clone());
        }

        Ok(DEFAULT_SERVER_URL.parse()?)
    }
}

pub struct ConfigManager {
    config_path: PathBuf,
}

impl ConfigManager {
    pub fn new() -> Result<Self> {
        let home_dir = home_dir().context("Failed to get home directory")?;
        let config_path = home_dir
            .join(".config")
            .join("forevervm")
            .join("config.json");

        Ok(Self { config_path })
    }

    pub fn client(&self) -> Result<ForeverVMClient> {
        let config = self.load()?;
        if let Some(token) = &config.token {
            Ok(ForeverVMClient::new(config.server_url()?, token.clone()))
        } else {
            Err(anyhow::anyhow!("Not logged in"))
        }
    }

    pub fn load(&self) -> Result<Config> {
        if !self.config_path.exists() {
            if let Some(parent) = self.config_path.parent() {
                std::fs::create_dir_all(parent)?;
            }
            return Ok(Config::default());
        }

        let config_str =
            std::fs::read_to_string(&self.config_path).context("Failed to read config file")?;
        let config = serde_json::from_str(&config_str).context("Failed to parse config file")?;
        Ok(config)
    }

    pub fn save(&self, config: &Config) -> Result<()> {
        if let Some(parent) = self.config_path.parent() {
            std::fs::create_dir_all(parent)?;
        }

        let mut config_str =
            serde_json::to_string_pretty(config).context("Failed to serialize config")?;
        config_str.push('\n');
        std::fs::write(&self.config_path, config_str).context("Failed to write config file")?;
        Ok(())
    }

    pub fn get_path(&self) -> &Path {
        &self.config_path
    }
}