1use crate::{
6 Config,
7 cmd_dashboard::CmdDashboard,
8 cmd_event::CmdEventList,
9 cmd_generate_completion::CmdGenerateCompletion,
10 cmd_todo::{CmdTodoDone, CmdTodoEdit, CmdTodoList, CmdTodoNew, CmdTodoUndo},
11 cmd_tui::{CmdEdit, CmdNew},
12 config::APP_NAME,
13};
14use aimcal_core::Aim;
15use clap::{Command, ValueHint, arg, builder::styling, crate_version, value_parser};
16use colored::Colorize;
17use futures::{FutureExt, future::BoxFuture};
18use std::{error::Error, path::PathBuf};
19
20pub async fn run() -> Result<(), Box<dyn Error>> {
22 env_logger::init();
23 match Cli::parse() {
24 Ok(cli) => {
25 if let Err(e) = cli.run().await {
26 println!("{} {}", "Error:".red(), e);
27 }
28 }
29 Err(e) => println!("{} {}", "Error:".red(), e),
30 };
31 Ok(())
32}
33
34#[derive(Debug)]
36pub struct Cli {
37 pub config: Option<PathBuf>,
39
40 pub command: Commands,
42}
43
44impl Cli {
45 pub fn command() -> Command {
47 const STYLES: styling::Styles = styling::Styles::styled()
48 .header(styling::AnsiColor::Green.on_default().bold())
49 .usage(styling::AnsiColor::Green.on_default().bold())
50 .literal(styling::AnsiColor::Blue.on_default().bold())
51 .placeholder(styling::AnsiColor::Cyan.on_default());
52
53 Command::new(APP_NAME)
54 .about("Analyze. Interact. Manage Your Time, with calendar support.")
55 .author("Zexin Yuan <aim@yzx9.xyz>")
56 .version(crate_version!())
57 .styles(STYLES)
58 .subcommand_required(false) .arg_required_else_help(false)
60 .arg(
61 arg!(-c --config [CONFIG] "Path to the configuration file")
62 .long_help(
63 "\
64Path to the configuration file. Defaults to $XDG_CONFIG_HOME/aim/config.toml on Linux and MacOS, \
65%LOCALAPPDATA%/aim/config.toml on Windows.",
66 )
67 .value_parser(value_parser!(PathBuf))
68 .value_hint(ValueHint::FilePath),
69 )
70 .subcommand(CmdDashboard::command())
71 .subcommand(CmdNew::command())
72 .subcommand(CmdEdit::command())
73 .subcommand(
74 Command::new("event")
75 .alias("e")
76 .about("Manage your event list")
77 .arg_required_else_help(true)
78 .subcommand_required(true)
79 .subcommand(CmdEventList::command()),
80 )
81 .subcommand(
82 Command::new("todo")
83 .alias("t")
84 .about("Manage your todo list")
85 .arg_required_else_help(true)
86 .subcommand_required(true)
87 .subcommand(CmdTodoNew::command())
88 .subcommand(CmdTodoEdit::command())
89 .subcommand(CmdTodoDone::command())
90 .subcommand(CmdTodoUndo::command())
91 .subcommand(CmdTodoList::command()),
92 )
93 .subcommand(CmdTodoDone::command())
94 .subcommand(CmdTodoUndo::command())
95 .subcommand(CmdGenerateCompletion::command())
96 }
97
98 pub fn parse() -> Result<Self, Box<dyn Error>> {
100 use Commands::*;
101 let matches = Self::command().get_matches();
102 let command = match matches.subcommand() {
103 Some((CmdDashboard::NAME, matches)) => Dashboard(CmdDashboard::from(matches)),
104 Some((CmdNew::NAME, matches)) => New(CmdNew::from(matches)),
105 Some((CmdEdit::NAME, matches)) => Edit(CmdEdit::from(matches)),
106 Some(("event", matches)) => match matches.subcommand() {
107 Some(("list", matches)) => EventList(CmdEventList::from(matches)),
108 _ => unreachable!(),
109 },
110 Some(("todo", matches)) => match matches.subcommand() {
111 Some((CmdTodoNew::NAME, matches)) => TodoNew(CmdTodoNew::from(matches)?),
112 Some((CmdTodoEdit::NAME, matches)) => TodoEdit(CmdTodoEdit::from(matches)),
113 Some((CmdTodoDone::NAME, matches)) => TodoDone(CmdTodoDone::from(matches)),
114 Some((CmdTodoUndo::NAME, matches)) => TodoUndo(CmdTodoUndo::from(matches)),
115 Some((CmdTodoList::NAME, matches)) => TodoList(CmdTodoList::from(matches)),
116 _ => unreachable!(),
117 },
118 Some((CmdTodoDone::NAME, matches)) => TodoDone(CmdTodoDone::from(matches)),
119 Some((CmdTodoUndo::NAME, matches)) => TodoUndo(CmdTodoUndo::from(matches)),
120 Some((CmdGenerateCompletion::NAME, matches)) => {
121 GenerateCompletion(CmdGenerateCompletion::from(matches))
122 }
123 None => Dashboard(CmdDashboard),
124 _ => unreachable!(),
125 };
126
127 let config = matches.get_one("config").cloned();
128 Ok(Cli { config, command })
129 }
130
131 pub async fn run(self) -> Result<(), Box<dyn Error>> {
133 self.command.run(self.config).await
134 }
135}
136
137#[derive(Debug, Clone)]
139pub enum Commands {
140 Dashboard(CmdDashboard),
142
143 New(CmdNew),
145
146 Edit(CmdEdit),
148
149 EventList(CmdEventList),
151
152 TodoNew(CmdTodoNew),
154
155 TodoEdit(CmdTodoEdit),
157
158 TodoDone(CmdTodoDone),
160
161 TodoUndo(CmdTodoUndo),
163
164 TodoList(CmdTodoList),
166
167 GenerateCompletion(CmdGenerateCompletion),
169}
170
171impl Commands {
172 #[rustfmt::skip]
174 pub async fn run(self, config: Option<PathBuf>) -> Result<(), Box<dyn Error>> {
175 use Commands::*;
176 match self {
177 Dashboard(a) => Self::run_with(config, |x| a.run(x).boxed()).await,
178 New(a) => Self::run_with(config, |x| a.run(x).boxed()).await,
179 Edit(a) => Self::run_with(config, |x| a.run(x).boxed()).await,
180 EventList(a) => Self::run_with(config, |x| a.run(x).boxed()).await,
181 TodoNew(a) => Self::run_with(config, |x| a.run(x).boxed()).await,
182 TodoEdit(a) => Self::run_with(config, |x| a.run(x).boxed()).await,
183 TodoDone(a) => Self::run_with(config, |x| a.run(x).boxed()).await,
184 TodoUndo(a) => Self::run_with(config, |x| a.run(x).boxed()).await,
185 TodoList(a) => Self::run_with(config, |x| a.run(x).boxed()).await,
186 GenerateCompletion(a) => a.run(),
187 }
188 }
189
190 async fn run_with<F>(config: Option<PathBuf>, f: F) -> Result<(), Box<dyn Error>>
191 where
192 F: for<'a> FnOnce(&'a mut Aim) -> BoxFuture<'a, Result<(), Box<dyn Error>>>,
193 {
194 log::debug!("Parsing configuration...");
195 let config = Config::parse(config).await?;
196 let mut aim = Aim::new(config.core).await?;
197
198 f(&mut aim).await?;
199
200 aim.dump().await?;
201 Ok(())
202 }
203}