1use crate::config::APP_NAME;
6use clap::{Arg, Command, ValueEnum, ValueHint, arg, crate_version, value_parser};
7use clap_complete::generate;
8use std::{io, path::PathBuf, process};
9
10#[derive(Debug)]
12pub struct Cli {
13 pub config: Option<PathBuf>,
15
16 pub command: Commands,
18}
19
20impl Cli {
21 pub fn parse() -> Cli {
23 let matches = build_cli().get_matches();
24
25 fn output_format(matches: &clap::ArgMatches) -> OutputFormat {
26 matches
27 .get_one::<OutputFormat>("output-format")
28 .copied()
29 .unwrap_or(OutputFormat::Table)
30 }
31
32 fn uid_or_short_id(matches: &clap::ArgMatches) -> String {
33 matches
34 .get_one::<String>("id")
35 .expect("id is required")
36 .clone()
37 }
38
39 fn todo_edit_args(matches: &clap::ArgMatches) -> TodoEditArgs {
40 TodoEditArgs {
41 uid_or_short_id: uid_or_short_id(matches),
42 output_format: output_format(matches),
43 }
44 }
45
46 let command = match matches.subcommand() {
47 Some(("dashboard", _)) => Commands::Dashboard,
48 Some(("event", matches)) => Commands::Events(OutputArgs {
49 output_format: output_format(matches),
50 }),
51 Some(("todo", matches)) => Commands::Todos(OutputArgs {
52 output_format: output_format(matches),
53 }),
54 Some(("done", matches)) => Commands::Done(todo_edit_args(matches)),
55 Some(("undo", matches)) => Commands::Undo(todo_edit_args(matches)),
56 Some(("generate-completion", matches)) => match matches.get_one::<Shell>("shell") {
57 Some(shell) => {
58 shell.generate_completion();
59 process::exit(1)
60 }
61 _ => unreachable!(),
62 },
63 None => Commands::Dashboard,
64 _ => unreachable!(),
65 };
66
67 let config = matches.get_one::<PathBuf>("config").cloned();
68 Cli { config, command }
69 }
70}
71
72#[derive(Debug, Clone)]
74pub enum Commands {
75 Dashboard,
77
78 Events(OutputArgs),
80
81 Todos(OutputArgs),
83
84 Done(TodoEditArgs),
86
87 Undo(TodoEditArgs),
89}
90
91#[derive(Debug, Clone, Copy)]
93pub struct OutputArgs {
94 pub output_format: OutputFormat,
95}
96
97#[derive(Debug, Clone)]
98pub struct TodoEditArgs {
99 pub uid_or_short_id: String,
100 pub output_format: OutputFormat,
101}
102
103fn build_cli() -> Command {
104 fn output_format() -> Arg {
105 arg!(--"output-format" <FORMAT> "Output format")
106 .value_parser(value_parser!(OutputFormat))
107 .default_value("table")
108 }
109
110 Command::new(APP_NAME)
111 .about("Analyze. Interact. Manage Your Time.")
112 .author("Zexin Yuan <aim@yzx9.xyz>")
113 .version(crate_version!())
114 .subcommand_required(false) .arg_required_else_help(false)
116 .arg(
117 arg!(-c --config [CONFIG] "Path to the configuration file")
118 .long_help(
119 "\
120Path to the configuration file. Defaults to $XDG_CONFIG_HOME/aim/config.toml on Linux and MacOS, \
121%LOCALAPPDATA%/aim/config.toml on Windows.",
122 )
123 .value_parser(value_parser!(PathBuf))
124 .value_hint(ValueHint::FilePath),
125 )
126 .subcommand(
127 Command::new("dashboard")
128 .about("Show the dashboard, which includes upcoming events and todos")
129 .arg(output_format()),
130 )
131 .subcommand(
132 Command::new("event")
133 .about("List events")
134 .arg(output_format()),
135 )
136 .subcommand(
137 Command::new("todo")
138 .about("List todos")
139 .arg(output_format()),
140 )
141 .subcommand(
142 Command::new("done")
143 .about("Mark a todo as done")
144 .arg(arg!(<id> "The short id or uid of the todo to mark as done"))
145 .arg(output_format()),
146 )
147 .subcommand(
148 Command::new("undo")
149 .about("Mark a todo as undone")
150 .arg(arg!(<id> "The short id or uid of the todo to mark as undone"))
151 .arg(output_format()),
152 )
153 .subcommand(
154 Command::new("generate-completion")
155 .about("Generate shell completion for the specified shell")
156 .hide(true)
157 .arg(
158 arg!(shell: <SHELL> "The shell generator to use")
159 .value_parser(value_parser!(Shell)),
160 ),
161 )
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
166pub enum OutputFormat {
167 Json,
168 Table,
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
172enum Shell {
173 Bash,
174 Elvish,
175 Fish,
176 Nushell,
177 #[clap(name = "powershell")]
178 #[allow(clippy::enum_variant_names)]
179 PowerShell,
180 Zsh,
181}
182
183impl Shell {
184 fn generate_completion(&self) {
185 use clap_complete::Shell as ClapShell;
186
187 let mut cmd = build_cli();
188 let name = cmd.get_name().to_string();
189 match self {
190 Shell::Bash => generate(ClapShell::Bash, &mut cmd, name, &mut io::stdout()),
191 Shell::Elvish => generate(ClapShell::Elvish, &mut cmd, name, &mut io::stdout()),
192 Shell::Fish => generate(ClapShell::Fish, &mut cmd, name, &mut io::stdout()),
193 Shell::PowerShell => generate(ClapShell::PowerShell, &mut cmd, name, &mut io::stdout()),
194 Shell::Zsh => generate(ClapShell::Zsh, &mut cmd, name, &mut io::stdout()),
195
196 Shell::Nushell => generate(
197 clap_complete_nushell::Nushell {},
198 &mut cmd,
199 name,
200 &mut io::stdout(),
201 ),
202 }
203 }
204}