1use std::collections::BTreeMap;
8
9use serde::{Deserialize, Serialize};
10
11use crate::error::{CoreError, Result};
12use crate::model::Author;
13use crate::repo::Repository;
14
15pub const USER_NAME: &str = "user.name";
17pub const USER_EMAIL: &str = "user.email";
19pub const ENV_AUTHOR_NAME: &str = "CADVM_AUTHOR_NAME";
21pub const ENV_AUTHOR_EMAIL: &str = "CADVM_AUTHOR_EMAIL";
23
24const UNKNOWN_NAME: &str = "unknown";
26
27#[derive(Debug, Default, Clone, Serialize, Deserialize)]
29#[serde(transparent)]
30pub struct Config {
31 entries: BTreeMap<String, String>,
32}
33
34impl Config {
35 pub fn load(repo: &Repository) -> Result<Config> {
37 let path = repo.config_path();
38 match std::fs::read(&path) {
39 Ok(bytes) => Ok(serde_json::from_slice(&bytes)?),
40 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Config::default()),
41 Err(e) => Err(CoreError::io(&path, e)),
42 }
43 }
44
45 pub fn save(&self, repo: &Repository) -> Result<()> {
47 let path = repo.config_path();
48 let mut bytes = serde_json::to_vec_pretty(self)?;
49 bytes.push(b'\n');
50 std::fs::write(&path, bytes).map_err(|e| CoreError::io(&path, e))
51 }
52
53 pub fn get(&self, key: &str) -> Option<&str> {
55 self.entries.get(key).map(String::as_str)
56 }
57
58 pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
60 self.entries.insert(key.into(), value.into());
61 }
62
63 pub fn entries(&self) -> impl Iterator<Item = (&str, &str)> {
65 self.entries.iter().map(|(k, v)| (k.as_str(), v.as_str()))
66 }
67}
68
69pub fn resolve_author(repo: &Repository) -> Result<Author> {
72 let config = Config::load(repo)?;
73 let name = std::env::var(ENV_AUTHOR_NAME)
74 .ok()
75 .filter(|s| !s.is_empty())
76 .or_else(|| config.get(USER_NAME).map(str::to_string))
77 .unwrap_or_else(|| UNKNOWN_NAME.to_string());
78 let email = std::env::var(ENV_AUTHOR_EMAIL)
79 .ok()
80 .filter(|s| !s.is_empty())
81 .or_else(|| config.get(USER_EMAIL).map(str::to_string))
82 .unwrap_or_default();
83 Ok(Author { name, email })
84}