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::tui::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 workspace: PathBuf,
42 #[serde(default)]
43 pub data: PathBuf,
44 #[serde(default)]
45 pub config: PathBuf,
46}
47
48/// Configuration for the App
49#[derive(Debug, Clone)]
50pub struct Config {
51 pub app_config: AppConfig,
52 pub keymap: KeyMap,
53 // pub styles: Styles,
54}
55
56impl Config {
57 /// generates a new config with the provided `filaments_dir`
58 pub fn generate(filaments_dir: &Path) -> KdlDocument {
59 let mut default_config: KdlDocument = DEFAULT_CONFIG
60 .parse()
61 .expect("Default config should always be a valid KDL document.");
62
63 if let Some(node) = default_config
64 .nodes_mut()
65 .iter_mut()
66 .find(|n| n.name().value() == "filaments_dir")
67 && let Some(entry) = node.entries_mut().get_mut(0)
68 {
69 *entry.value_mut() = kdl::KdlValue::String(filaments_dir.to_string_lossy().to_string());
70 entry.clear_format();
71 }
72
73 default_config
74 }
75
76 /// Parse the config from `~/.config/filametns`
77 ///
78 /// # Errors
79 ///
80 /// Will error if the config doesn't exist or if there
81 /// is a problem parsing it.
82 pub fn parse() -> color_eyre::Result<Self> {
83 let config: KdlDocument = {
84 let file_path = get_config_dir().join("config.kdl");
85
86 let mut file = File::open(file_path).context("Failed to find file!")?;
87
88 let mut str = String::new();
89
90 file.read_to_string(&mut str)
91 .context("Failed to read file!")?;
92
93 str.parse().context("Expected to be valid kdl")?
94 };
95
96 let keymap = KeyMap::try_from(
97 config
98 .get("keymap")
99 .expect("Keymap must exist in the config"),
100 )
101 .context("Keymap is not valid!")?;
102
103 let filaments_dir_str = config
104 .get("filaments_dir")
105 .expect("config should always have this specified")
106 .get(0)
107 .and_then(|arg| arg.as_string())
108 .expect("filaments_dir must be a string");
109
110 let filaments_dir = PathBuf::from(filaments_dir_str)
111 .canonicalize()
112 .context("Filaments directory does not exist!")?;
113
114 Ok(Self {
115 app_config: AppConfig {
116 workspace: filaments_dir,
117 data: get_data_dir(),
118 config: get_config_dir(),
119 },
120 keymap,
121 })
122 }
123}
124
125/// Returns the path to the OS-agnostic data directory.
126pub fn get_data_dir() -> PathBuf {
127 DATA_DIRECTORY.clone().unwrap_or_else(|| {
128 project_directory().map_or_else(
129 || PathBuf::from(".").join(".data"),
130 |proj_dirs| proj_dirs.data_local_dir().to_path_buf(),
131 )
132 })
133}
134
135/// Returns the path to the OS-agnostic config directory.
136pub fn get_config_dir() -> PathBuf {
137 CONFIG_DIRECTORY.clone().unwrap_or_else(|| {
138 home_dir().map_or_else(
139 || PathBuf::from(".").join(".config"),
140 |mut path| {
141 path.push(".config");
142 path.push("filaments");
143 path
144 },
145 )
146 })
147}
148fn project_directory() -> Option<ProjectDirs> {
149 ProjectDirs::from("com", "suri-codes", env!("CARGO_PKG_NAME"))
150}