1use std::path::{Path, PathBuf};
2use chrono::*;
3use std::ops::*;
4use cmd_lib::*;
5use colored::Colorize;
6use crossterm::execute;
7use crossterm::cursor::SetCursorStyle::*;
8
9pub use crate::*;
10pub use crate::styles::*;
11
12#[macro_export]
13macro_rules! Config_get { ($e:expr) => {
14 match $e {
15 "basedir" => { CONFIG.read().unwrap().basedir.clone().unwrap() },
16 _ => { panic!("unknown config key") }
18 }
19}; }
20
21trait PathExt {
22 fn normalize(&self) -> Option<String>;
23}
24impl PathExt for Path {
25 fn normalize(&self) -> Option<String> {
26 let real_path;
27
28 if self.starts_with("~/") {
29 real_path = dirs::home_dir()?.join(self.strip_prefix("~/").unwrap());
30 } else if self.is_relative() {
31 real_path = std::env::current_dir().expect("cannot get cwd?")
32 .join(self.strip_prefix("./").unwrap_or(self));
33 } else {
34 real_path = self.to_path_buf()
35 }
36 Some(real_path.to_str()?.into())
37 }
38}
39pub fn path_normalize(path_str: &str) -> String {
40 Path::new(path_str).normalize().unwrap()
41}
42
43pub fn pick_file() -> String {
44 run_fun!(
45 ls | fzf;
46 ).unwrap_or_else(|_|
47 std::process::exit(1)
48 )
49}
50
51pub fn get_today() -> String {
52 Local::now().date_naive().to_string()
53}
54pub fn get_yesterday() -> String {
55 Local::now().add(chrono::Duration::days(-1)).date_naive().to_string()
56}
57pub fn get_tomorrow() -> String {
58 Local::now().add(chrono::Duration::days(1)).date_naive().to_string()
59}
60pub fn weekday_from_date(date_str: &str) -> String {
61 if date_str.is_empty() { return "".into(); }
62 NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap().weekday().to_string()
63}
64
65pub fn match_routine(kind: &str, s_date_str: &str, match_to: &str) -> bool {
66 let mut s_date = NaiveDate::parse_from_str(s_date_str, "%Y-%m-%d").unwrap();
67 let match_to_date = match match_to {
68 "today" => Local::now().date_naive(),
69 "yesterday" => Local::now().add(chrono::Duration::days(-1)).date_naive(),
70 "tomorrow" => Local::now().add(chrono::Duration::days(1)).date_naive(),
71 _ => panic!("unsupported match_to date(only today/yesterday/tomorrow)"),
72 };
73
74 if kind == "m" {
75 while s_date < match_to_date {
76 s_date = s_date + chrono::Months::new(1);
77 }
78 } else {
79 let steps = match kind {
80 "d" => 1,
81 "w" => 7,
82 "b" => 14,
83 "q" => 28,
84 _ => panic!("unknown routine kind"),
85 };
86 while s_date < match_to_date {
87 s_date += chrono::Duration::days(steps);
88 }
89 }
90
91 s_date == match_to_date
92}
93
94pub fn get_box_alias(name_in: &str) -> String {
95 match name_in {
96 _ if name_in == get_today() => "today",
97 _ if name_in == get_tomorrow() => "tomorrow",
98 _ if name_in == get_yesterday() => "yesterday",
99 _ => name_in,
100 }.into()
101}
102
103pub fn get_box_unalias(alias: &str) -> String {
104 match alias {
105 "today" => get_today(),
106 "yesterday" => get_yesterday(),
107 "tomorrow" => get_tomorrow(),
108 "inbox" => taskbox::INBOX_BOXNAME.into(),
109 "routine" | "routines" => taskbox::ROUTINE_BOXNAME.into(),
110 _ => alias.into(),
111 }
112}
113
114pub fn get_inbox_file(inbox: &str) -> PathBuf {
115 let basedir = PathBuf::from(Config_get!("basedir"));
116 let enc_box = basedir.join(inbox).with_extension("mdx");
117
118 if enc_box.exists() { enc_box }
119 else { basedir.join(get_box_unalias(inbox)).with_extension("md") }
120}
121
122pub fn i_confirm(question: &str) -> bool {
126 inquire::Confirm::new(question)
127 .with_default(false)
128 .with_render_config(get_confirm_style())
129 .prompt().unwrap_or(false)
130}
131
132pub fn i_getpass(confirm: bool, msg: Option<&str>) -> String {
133 let mut com = inquire::Password::new(msg.unwrap_or("the password:"))
134 .with_help_message("<enter> | ctrl+r | ctrl+c")
135 .with_render_config(get_pass_input_style())
136 .with_display_toggle_enabled();
137
138 if ! confirm { com = com.without_confirmation() }
139
140 execute!(std::io::stdout(), SteadyBar).expect("failed to set cursor");
141 let pass = com.prompt().unwrap_or_else(|_| String::new());
142 execute!(std::io::stdout(), DefaultUserShape).expect("failed to set cursor");
143
144 pass
145}
146
147pub fn i_gettext() -> String {
148 execute!(std::io::stdout(), BlinkingBlock).expect("failed to set cursor");
149 let input = inquire::Text::new("")
150 .with_render_config(get_text_input_style())
151 .with_help_message("<enter> | ctrl+c")
152 .with_placeholder("something to do?")
153 .prompt().unwrap_or_else(|_| String::new());
154 execute!(std::io::stdout(), DefaultUserShape).expect("failed to set cursor");
155 input.trim().to_string()
156}
157
158pub fn i_select(tasks: Vec<String>, title: &str) -> Vec<String> {
159 execute!(std::io::stdout(), BlinkingBlock).expect("failed to set cursor");
160 let mut selected = inquire::MultiSelect::new(title, tasks)
161 .with_render_config(get_multi_select_style())
162 .with_vim_mode(true)
163 .with_page_size(10)
164 .with_help_message("h/j/k/l | ←↑↓→ | <space> | <enter> | ctrl+c")
165 .prompt().unwrap_or_else(|_| std::process::exit(1));
166 execute!(std::io::stdout(), DefaultUserShape).expect("failed to set cursor");
167 selected.retain(|x| !x.contains(WARN));
168 selected
169}
170
171pub fn i_getdate(routine_kind: &str) -> String {
172 inquire::DateSelect::new(&format!(" {} from:",S_routine!(routine_kind)))
173 .with_render_config(get_date_input_style())
174 .with_help_message("h/j/k/l | <enter> | ctrl+c")
175 .prompt().unwrap_or_else(|_| {
176 println!("{}", S_empty!("No starting date selected, skip."));
177 std::process::exit(1)
178 }).to_string()
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_aliases() {
187 assert_eq!(get_box_alias(&get_today()), "today");
188 assert_eq!(get_box_alias(&get_yesterday()), "yesterday");
189 assert_eq!(get_box_alias(&get_tomorrow()), "tomorrow");
190 assert_eq!(get_box_alias("dummy"), "dummy");
191 assert_eq!(get_box_alias(""), "");
192 }
193
194 #[test]
195 fn test_unaliases() {
196 assert_eq!(get_box_unalias("today"), get_today());
197 assert_eq!(get_box_unalias("yesterday"), get_yesterday());
198 assert_eq!(get_box_unalias("tomorrow"), get_tomorrow());
199 assert_eq!(get_box_unalias("dummy"), "dummy".to_string());
200 }
201
202 #[test]
203 fn test_path_normalize() {
204 let op1 = Path::new("~/dummy");
205 let op2 = Path::new("dummy");
206 let op3 = Path::new("~dummy");
207 let op4 = Path::new("./dummy");
208 let op5 = Path::new("/dummy");
209
210 assert_eq!(op1.normalize().unwrap(),
211 dirs::home_dir().unwrap().join("dummy").to_str().unwrap());
212 assert_eq!(op2.normalize().unwrap(),
213 std::env::current_dir().unwrap().join("dummy").to_str().unwrap());
214 assert_eq!(op3.normalize().unwrap(),
215 std::env::current_dir().unwrap().join("~dummy").to_str().unwrap());
216 assert_eq!(op4.normalize().unwrap(),
217 std::env::current_dir().unwrap().join("dummy").to_str().unwrap());
218 assert_eq!(op5.normalize().unwrap(),
219 "/dummy");
220 }
221}