indieweb_cli_common/
config.rs1use 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}