My personal-knowledge-system, with deeply integrated task tracking and long term goal planning capabilities.
at db 151 lines 4.4 kB view raw
1use color_eyre::eyre::Context; 2use directories::ProjectDirs; 3use kdl::KdlDocument; 4use serde::Deserialize; 5use std::{ 6 env::{self, home_dir}, 7 fmt::Debug, 8 fs::File, 9 io::Read, 10 path::{Path, PathBuf}, 11 sync::LazyLock, 12}; 13 14use crate::keymap::KeyMap; 15 16/// Project Name: Filaments 17pub static PROJECT_NAME: LazyLock<String> = 18 LazyLock::new(|| env!("CARGO_CRATE_NAME").to_uppercase()); 19 20/// The OS-agnostic data directory for the project. 21pub static DATA_DIRECTORY: LazyLock<Option<PathBuf>> = LazyLock::new(|| { 22 env::var(format!("{}_DATA", PROJECT_NAME.clone())) 23 .ok() 24 .map(PathBuf::from) 25}); 26 27/// The OS-agnostic config directory for the project. 28pub static CONFIG_DIRECTORY: LazyLock<Option<PathBuf>> = LazyLock::new(|| { 29 env::var(format!("{}_CONFIG", PROJECT_NAME.clone())) 30 .ok() 31 .map(PathBuf::from) 32}); 33 34const DEFAULT_CONFIG: &str = include_str!("../.config/config.kdl"); 35 36/// The App Config and Data locations. 37#[derive(Clone, Debug, Deserialize, Default)] 38#[expect(dead_code)] 39pub struct AppConfig { 40 /// The directory where the single instance of the filaments exists. 41 pub filaments: PathBuf, 42 #[serde(default)] 43 pub data: PathBuf, 44 #[serde(default)] 45 pub config: PathBuf, 46} 47 48/// Configuration for the App 49#[expect(dead_code)] 50#[derive(Debug, Clone)] 51pub struct Config { 52 pub app_config: AppConfig, 53 pub keymap: KeyMap, 54 // pub styles: Styles, 55} 56 57impl Config { 58 /// generates a new config with the provided `filaments_dir` 59 pub fn generate(filaments_dir: &Path) -> KdlDocument { 60 let mut default_config: KdlDocument = DEFAULT_CONFIG 61 .parse() 62 .expect("Default config should always be a valid KDL document."); 63 64 if let Some(node) = default_config 65 .nodes_mut() 66 .iter_mut() 67 .find(|n| n.name().value() == "filaments_dir") 68 && let Some(entry) = node.entries_mut().get_mut(0) 69 { 70 *entry.value_mut() = kdl::KdlValue::String(filaments_dir.to_string_lossy().to_string()); 71 entry.clear_format(); 72 } 73 74 default_config 75 } 76 77 /// Parse the config from `~/.config/filametns` 78 /// 79 /// # Errors 80 /// 81 /// Will error if the config doesn't exist or if there 82 /// is a problem parsing it. 83 pub fn parse() -> color_eyre::Result<Self> { 84 let config: KdlDocument = { 85 let file_path = get_config_dir().join("config.kdl"); 86 87 let mut file = File::open(file_path).context("Failed to find file!")?; 88 89 let mut str = String::new(); 90 91 file.read_to_string(&mut str) 92 .context("Failed to read file!")?; 93 94 str.parse().context("Expected to be valid kdl")? 95 }; 96 97 let keymap = KeyMap::try_from( 98 config 99 .get("keymap") 100 .expect("Keymap must exist in the config"), 101 ) 102 .context("Keymap is not valid!")?; 103 104 let filaments_dir_str = config 105 .get("filaments_dir") 106 .expect("config should always have this specified") 107 .get(0) 108 .and_then(|arg| arg.as_string()) 109 .expect("filaments_dir must be a string"); 110 111 let filaments_dir = PathBuf::from(filaments_dir_str) 112 .canonicalize() 113 .context("Filaments directory does not exist!")?; 114 115 Ok(Self { 116 app_config: AppConfig { 117 filaments: filaments_dir, 118 data: get_data_dir(), 119 config: get_config_dir(), 120 }, 121 keymap, 122 }) 123 } 124} 125 126/// Returns the path to the OS-agnostic data directory. 127pub fn get_data_dir() -> PathBuf { 128 DATA_DIRECTORY.clone().unwrap_or_else(|| { 129 project_directory().map_or_else( 130 || PathBuf::from(".").join(".data"), 131 |proj_dirs| proj_dirs.data_local_dir().to_path_buf(), 132 ) 133 }) 134} 135 136/// Returns the path to the OS-agnostic config directory. 137pub fn get_config_dir() -> PathBuf { 138 CONFIG_DIRECTORY.clone().unwrap_or_else(|| { 139 home_dir().map_or_else( 140 || PathBuf::from(".").join(".config"), 141 |mut path| { 142 path.push(".config"); 143 path.push("filaments"); 144 path 145 }, 146 ) 147 }) 148} 149fn project_directory() -> Option<ProjectDirs> { 150 ProjectDirs::from("com", "suri-codes", env!("CARGO_PKG_NAME")) 151}