Skip to main content

colab_cli/
config.rs

1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::{ColabError, Result};
6
7const APP_NAME: &str = "colab-cli";
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ColabEnvironment {
11    Production,
12    Sandbox,
13    Local,
14}
15
16impl ColabEnvironment {
17    fn colab_domain(&self) -> &'static str {
18        match self {
19            ColabEnvironment::Production => "https://colab.research.google.com",
20            ColabEnvironment::Sandbox => "https://colab.sandbox.google.com",
21            ColabEnvironment::Local => "https://localhost:8888",
22        }
23    }
24}
25
26impl std::str::FromStr for ColabEnvironment {
27    type Err = String;
28    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
29        match s {
30            "production" => Ok(ColabEnvironment::Production),
31            "sandbox" => Ok(ColabEnvironment::Sandbox),
32            "local" => Ok(ColabEnvironment::Local),
33            other => Err(format!(
34                "unknown environment '{other}' \u{2014} expected production, sandbox, or local"
35            )),
36        }
37    }
38}
39
40#[derive(Debug, Clone)]
41pub struct ColabConfig {
42    pub colab_domain: String,
43    pub client_id: String,
44    pub client_secret: String,
45    pub environment: ColabEnvironment,
46    pub data_dir: PathBuf,
47}
48
49#[derive(Debug, Deserialize, Serialize, Default)]
50struct ConfigFile {
51    environment: Option<String>,
52    client_id: Option<String>,
53    client_secret: Option<String>,
54    colab_domain: Option<String>,
55    colab_gapi_domain: Option<String>,
56}
57
58impl ColabConfig {
59    pub fn load(_quiet: bool) -> Result<Self> {
60        let config_dir = config_dir()?;
61        let data_dir = data_dir()?;
62        let file = load_config_file(&config_dir);
63
64        let env_str = std::env::var("COLAB_EXTENSION_ENVIRONMENT")
65            .ok()
66            .or(file.environment)
67            .unwrap_or_else(|| "production".to_string());
68
69        let environment: ColabEnvironment =
70            env_str.parse().map_err(|e: String| ColabError::config(e))?;
71
72        let colab_domain = std::env::var("COLAB_DOMAIN")
73            .ok()
74            .or(file.colab_domain)
75            .unwrap_or_else(|| environment.colab_domain().to_string());
76
77        let client_id = std::env::var("COLAB_EXTENSION_CLIENT_ID")
78            .ok()
79            .filter(|s| !s.is_empty())
80            .or_else(|| file.client_id.filter(|s| !s.is_empty()))
81            .unwrap_or_else(crate::embedded::embedded_client_id);
82
83        if client_id.is_empty() {
84            return Err(ColabError::config(
85                "COLAB_EXTENSION_CLIENT_ID is not set \u{2014} add it to your .env file or ~/.config/colab-cli/config.toml",
86            ));
87        }
88
89        let client_secret = std::env::var("COLAB_EXTENSION_CLIENT_NOT_SO_SECRET")
90            .ok()
91            .filter(|s| !s.is_empty())
92            .or_else(|| file.client_secret.filter(|s| !s.is_empty()))
93            .unwrap_or_else(crate::embedded::embedded_client_secret);
94
95        if client_secret.is_empty() {
96            return Err(ColabError::config(
97                "COLAB_EXTENSION_CLIENT_NOT_SO_SECRET is not set \u{2014} add it to your .env file or ~/.config/colab-cli/config.toml",
98            ));
99        }
100
101        Ok(Self {
102            colab_domain,
103            client_id,
104            client_secret,
105            environment,
106            data_dir,
107        })
108    }
109
110    pub fn servers_file(&self) -> PathBuf {
111        self.data_dir.join("servers.json")
112    }
113
114    pub fn is_local(&self) -> bool {
115        self.environment == ColabEnvironment::Local
116    }
117}
118
119fn load_config_file(config_dir: &Path) -> ConfigFile {
120    let path = config_dir.join("config.toml");
121    let Ok(contents) = std::fs::read_to_string(&path) else {
122        return ConfigFile::default();
123    };
124    toml::from_str(&contents).unwrap_or_default()
125}
126
127fn config_dir() -> Result<PathBuf> {
128    let base = dirs::config_dir()
129        .ok_or_else(|| ColabError::config("could not determine config directory"))?;
130    let dir = base.join(APP_NAME);
131    std::fs::create_dir_all(&dir)?;
132    Ok(dir)
133}
134
135fn data_dir() -> Result<PathBuf> {
136    let base = dirs::data_local_dir()
137        .ok_or_else(|| ColabError::config("could not determine data directory"))?;
138    let dir = base.join(APP_NAME);
139    std::fs::create_dir_all(&dir)?;
140    Ok(dir)
141}