1use std::collections::HashMap;
2
3#[derive(Clone, Debug, envir::Deserialize, envir::Serialize)]
4#[envir(prefix = "COLOR_")]
5pub struct Colors {
6 #[envir(default)]
7 pub context: crate::Color,
8 #[envir(default)]
9 pub done: crate::Color,
10 #[envir(default)]
11 pub date: crate::Color,
12 #[envir(skip)]
13 pub none: crate::Color,
14 #[envir(default)]
15 pub meta: crate::Color,
16 #[envir(default)]
17 pub number: crate::Color,
18 #[envir(default)]
19 pub project: crate::Color,
20 #[envir(load_with = "priorities_load", export_with = "priorities_export")]
21 priorities: HashMap<String, crate::Color>,
22}
23
24fn priorities_load(env: &HashMap<String, String>) -> envir::Result<HashMap<String, crate::Color>> {
25 let mut priorities = HashMap::new();
26 priorities.insert("PRI_A".to_string(), "yellow".into());
27 priorities.insert("PRI_B".to_string(), "green".into());
28 priorities.insert("PRI_C".to_string(), "bright blue".into());
29 priorities.insert("PRI_X".to_string(), "white".into());
30
31 for (k, v) in env {
32 if k.starts_with("PRI_") {
33 priorities.insert(k.clone(), v.parse().unwrap_or_default());
34 }
35 }
36
37 Ok(priorities)
38}
39
40fn priorities_export(priorities: &HashMap<String, crate::Color>) -> HashMap<String, String> {
41 priorities
42 .iter()
43 .map(|(k, v)| (k.clone(), v.to_string()))
44 .collect()
45}
46
47impl Colors {
48 pub fn for_pri(&self, pri: &crate::Priority) -> crate::Color {
49 self.priorities
50 .get(&format!("PRI_{pri}"))
51 .unwrap_or_else(|| self.priorities.get("PRI_X").unwrap())
52 .clone()
53 }
54}
55
56#[derive(envir::Deserialize, envir::Serialize)]
57#[envir(prefix = "TODOTXT_")]
58pub struct Config {
59 #[envir(noprefix, name = "TODO_ACTIONS_DIR", default = "${HOME}/.todo/actions")]
60 pub action_dir: String,
61 #[envir(default = "true")]
62 pub auto_archive: bool,
63 #[envir(nested)]
64 pub colors: Colors,
65 #[envir(default)]
66 pub date_on_add: bool,
67 #[envir(default = "help")]
68 pub default_action: String,
69 #[envir(default)]
70 pub disable_filter: bool,
71 #[envir(default = "cat")]
72 pub final_filter: String,
73 #[envir(default)]
74 pub force: bool,
75 #[envir(
76 noprefix,
77 name = "TODO_NOTE_ARCHIVE",
78 default = "${TODO_DIR}/notes/archive.txt"
79 )]
80 pub note_archive: String,
81 #[envir(noprefix, name = "TODO_NOTES_DIR", default = "${TODO_DIR}/notes")]
82 pub notes_dir: String,
83 #[envir(noprefix, name = "TODO_NOTE_FILTER", default = "cat")]
84 pub note_filter: String,
85 #[envir(default)]
86 pub plain: bool,
87 #[envir(default = "true")]
88 pub preserve_line_numbers: bool,
89 pub priority_on_add: Option<char>,
90 #[envir(default = "env LC_COLLATE=C sort -f -k2")]
91 pub sort_command: String,
92 #[envir(noprefix)]
93 pub todo_dir: String,
94 #[envir(noprefix, default = "${TODO_DIR}/todo.txt")]
95 pub todo_file: String,
96 #[envir(noprefix, default = "${TODO_DIR}/done.txt")]
97 pub done_file: String,
98 #[envir(noprefix, default = "${TODO_DIR}/report.txt")]
99 pub report_file: String,
100 #[envir(default = "1")]
101 pub verbose: u8,
102}
103
104impl Config {
105 #[must_use]
106 pub fn from_env() -> Self {
107 Self::load_env();
108
109 envir::from_env().unwrap()
110 }
111
112 pub fn load_env() {
113 envir::set("NONE", "");
114 envir::set("BLACK", "\x1B[0;30m");
115 envir::set("RED", "\x1B[0;31m");
116 envir::set("GREEN", "\x1B[0;32m");
117 envir::set("BROWN", "\x1B[0;33m");
118 envir::set("BLUE", "\x1B[0;34m");
119 envir::set("PURPLE", "\x1B[0;35m");
120 envir::set("CYAN", "\x1B[0;36m");
121 envir::set("LIGHT_GREY", "\x1B[0;37m");
122 envir::set("DARK_GREY", "\x1B[1;30m");
123 envir::set("LIGHT_RED", "\x1B[1;31m");
124 envir::set("LIGHT_GREEN", "\x1B[1;32m");
125 envir::set("YELLOW", "\x1B[1;33m");
126 envir::set("LIGHT_BLUE", "\x1B[1;34m");
127 envir::set("LIGHT_PURPLE", "\x1B[1;35m");
128 envir::set("LIGHT_CYAN", "\x1B[1;36m");
129 envir::set("WHITE", "\x1B[1;37m");
130 envir::set("DEFAULT", "\x1B[0m");
131
132 Self::load_config_file();
133 }
134
135 #[cfg(not(debug_assertions))]
136 fn load_config_file() {
137 let home = envir::get("HOME").unwrap();
138
139 let configs: Vec<Box<dyn Fn() -> String>> = vec![
140 Box::new(|| format!("{home}/.todo/config")),
141 Box::new(|| format!("{home}/todo.cfg")),
142 Box::new(|| format!("{home}/.todo.cfg")),
143 Box::new(|| format!("{home}/.config/todo/config")),
144 Box::new(|| {
145 let filename = std::env::args().next().unwrap();
146 let mut path = std::path::PathBuf::from(filename);
147 path.pop();
148
149 format!("{}/todo.cfg", path.display())
150 }),
151 Box::new(|| {
152 envir::get("TODOTXT_GLOBAL_CFG_FILE")
153 .unwrap_or_else(|_| "/etc/todo/config".to_string())
154 }),
155 ];
156
157 for config in configs {
158 if envir::from_path(config()).is_ok() {
159 break;
160 }
161 }
162 }
163
164 #[cfg(debug_assertions)]
165 fn load_config_file() {
166 envir::dotenv();
167 }
168}