todor/
util.rs

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        //"blink" => { CONFIG.read().unwrap().blink.unwrap_or(true) },
17        _ => { 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
122// following i_* fn are for "inquire" based wrappers
123// "i" stands for "I would like use Inquire crate to get my Input in an Interactive way"
124
125pub 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}