Skip to main content

git_side/
config.rs

1use std::collections::HashMap;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5use crate::error::{Error, Result};
6
7/// Get the config directory path (~/.config/git-side/).
8fn config_dir() -> PathBuf {
9    dirs::config_dir()
10        .unwrap_or_else(|| PathBuf::from("~/.config"))
11        .join("git-side")
12}
13
14/// Get the cache file path (~/.config/git-side/cache).
15fn cache_file() -> PathBuf {
16    config_dir().join("cache")
17}
18
19/// Get the paths file path (~/.config/git-side/paths).
20fn paths_file() -> PathBuf {
21    config_dir().join("paths")
22}
23
24/// Ensure the config directory exists.
25fn ensure_config_dir() -> Result<()> {
26    let dir = config_dir();
27    if !dir.exists() {
28        fs::create_dir_all(&dir).map_err(|e| Error::CreateDir {
29            path: dir,
30            source: e,
31        })?;
32    }
33    Ok(())
34}
35
36/// Read a key=value file into a `HashMap`.
37fn read_kv_file(path: &Path) -> Result<HashMap<String, String>> {
38    if !path.exists() {
39        return Ok(HashMap::new());
40    }
41
42    let content = fs::read_to_string(path).map_err(|e| Error::ReadFile {
43        path: path.to_path_buf(),
44        source: e,
45    })?;
46
47    let mut map = HashMap::new();
48    for line in content.lines() {
49        let line = line.trim();
50        if line.is_empty() || line.starts_with('#') {
51            continue;
52        }
53        if let Some((key, value)) = line.split_once('=') {
54            map.insert(key.to_string(), value.to_string());
55        }
56    }
57    Ok(map)
58}
59
60/// Write a `HashMap` to a key=value file.
61fn write_kv_file(path: &Path, map: &HashMap<String, String>) -> Result<()> {
62    ensure_config_dir()?;
63
64    let content: String = map
65        .iter()
66        .map(|(k, v)| format!("{k}={v}"))
67        .collect::<Vec<_>>()
68        .join("\n");
69
70    fs::write(path, content).map_err(|e| Error::WriteFile {
71        path: path.to_path_buf(),
72        source: e,
73    })
74}
75
76/// Hash a path to a 16-character hex string.
77#[must_use]
78pub fn hash_path(path: &Path) -> String {
79    use std::collections::hash_map::DefaultHasher;
80    use std::hash::{Hash, Hasher};
81
82    let mut hasher = DefaultHasher::new();
83    path.hash(&mut hasher);
84    format!("{:016x}", hasher.finish())
85}
86
87/// Cache: lookup root SHA by hashed repo path.
88///
89/// # Errors
90///
91/// Returns an error if the cache file cannot be read.
92pub fn cache_lookup(path_hash: &str) -> Result<Option<String>> {
93    let map = read_kv_file(&cache_file())?;
94    Ok(map.get(path_hash).cloned())
95}
96
97/// Cache: store root SHA for hashed repo path.
98///
99/// # Errors
100///
101/// Returns an error if the cache file cannot be written.
102pub fn cache_store(path_hash: &str, root_sha: &str) -> Result<()> {
103    let mut map = read_kv_file(&cache_file())?;
104    map.insert(path_hash.to_string(), root_sha.to_string());
105    write_kv_file(&cache_file(), &map)
106}
107
108/// Paths: lookup custom base path by root SHA.
109///
110/// # Errors
111///
112/// Returns an error if the paths file cannot be read.
113pub fn paths_lookup(root_sha: &str) -> Result<Option<PathBuf>> {
114    let map = read_kv_file(&paths_file())?;
115    Ok(map.get(root_sha).map(PathBuf::from))
116}
117
118/// Paths: store custom base path for root SHA.
119///
120/// # Errors
121///
122/// Returns an error if the paths file cannot be written.
123pub fn paths_store(root_sha: &str, base_path: &Path) -> Result<()> {
124    let mut map = read_kv_file(&paths_file())?;
125    map.insert(
126        root_sha.to_string(),
127        base_path.to_string_lossy().to_string(),
128    );
129    write_kv_file(&paths_file(), &map)
130}
131
132/// Get the default base path for side repos.
133#[must_use]
134pub fn default_base_path() -> PathBuf {
135    dirs::data_local_dir()
136        .unwrap_or_else(|| PathBuf::from("~/.local/share"))
137        .join("git-side")
138}