Skip to main content

indieweb_cli_common/
config.rs

1use directories::ProjectDirs;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4use url::Url;
5
6use crate::error::{CliError, Result};
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct Config {
10    #[serde(default)]
11    pub webmention: WebmentionConfig,
12    #[serde(default)]
13    pub micropub: MicropubConfig,
14    #[serde(default)]
15    pub indieauth: IndieAuthConfig,
16}
17
18#[derive(Debug, Clone, Default, Serialize, Deserialize)]
19pub struct WebmentionConfig {
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub endpoint: Option<Url>,
22}
23
24#[derive(Debug, Clone, Default, Serialize, Deserialize)]
25pub struct MicropubConfig {
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub endpoint: Option<Url>,
28}
29
30#[derive(Debug, Clone, Default, Serialize, Deserialize)]
31pub struct IndieAuthConfig {
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub authorization_endpoint: Option<Url>,
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub token_endpoint: Option<Url>,
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub client_id: Option<Url>,
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub redirect_uri: Option<Url>,
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub scope: Option<String>,
42}
43
44#[derive(Debug, Clone, clap::Args)]
45pub struct ConfigArgs {
46    #[arg(long, env = "INDIEWEB_CONFIG", global = true)]
47    pub config: Option<PathBuf>,
48
49    #[arg(long, env = "INDIEWEB_TOKEN", global = true)]
50    pub token: Option<String>,
51
52    #[arg(long, global = true)]
53    pub verbose: bool,
54}
55
56impl Config {
57    pub fn load(args: &ConfigArgs) -> Result<Self> {
58        if let Some(config_path) = &args.config {
59            return Self::from_path(config_path);
60        }
61
62        if let Some(project_dirs) = ProjectDirs::from("org", "indieweb", "indieweb") {
63            let config_path = project_dirs.config_dir().join("config.toml");
64            if config_path.exists() {
65                return Self::from_path(&config_path);
66            }
67        }
68
69        Ok(Self::default())
70    }
71
72    pub fn from_path(path: &PathBuf) -> Result<Self> {
73        let contents = std::fs::read_to_string(path)?;
74        let config: Self = toml::from_str(&contents)?;
75        Ok(config)
76    }
77
78    pub fn save(&self, path: &PathBuf) -> Result<()> {
79        if let Some(parent) = path.parent() {
80            std::fs::create_dir_all(parent)?;
81        }
82        let contents = toml::to_string_pretty(self).map_err(|e| CliError::Config(Box::new(e)))?;
83        std::fs::write(path, contents)?;
84        Ok(())
85    }
86
87    pub fn config_path() -> Option<PathBuf> {
88        ProjectDirs::from("org", "indieweb", "indieweb")
89            .map(|dirs| dirs.config_dir().join("config.toml"))
90    }
91
92    pub fn data_dir() -> Option<PathBuf> {
93        ProjectDirs::from("org", "indieweb", "indieweb")
94            .map(|dirs| dirs.data_local_dir().to_path_buf())
95    }
96}
97
98impl ConfigArgs {
99    pub fn into_config(self) -> Result<(Config, ConfigArgs)> {
100        let config = Config::load(&self)?;
101        Ok((config, self))
102    }
103}