My personal-knowledge-system, with deeply integrated task tracking and long term goal planning capabilities.
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}