1use std::path::PathBuf;
15
16use clap::{CommandFactory, Parser, ValueEnum};
17use clap_complete::{generate, Shell};
18
19use crate::config::SortMode;
20use crate::package::Runner;
21
22#[derive(Parser, Debug)]
24#[command(name = "nrs")]
25#[command(author, version, about, long_about = None)]
26#[command(arg_required_else_help = false)]
27pub struct Cli {
28 #[arg(value_name = "PATH")]
30 pub path: Option<PathBuf>,
31
32 #[arg(short = 'L', long = "last")]
34 pub last: bool,
35
36 #[arg(short, long)]
38 pub list: bool,
39
40 #[arg(short, long, value_name = "PATTERN")]
42 pub exclude: Vec<String>,
43
44 #[arg(short, long, value_name = "MODE", value_enum)]
46 pub sort: Option<CliSortMode>,
47
48 #[arg(short, long, value_name = "RUNNER", value_enum)]
50 pub runner: Option<CliRunner>,
51
52 #[arg(short, long, value_name = "ARGS", allow_hyphen_values = true)]
54 pub args: Option<String>,
55
56 #[arg(short = 'n', long = "script", value_name = "NAME")]
58 pub script: Option<String>,
59
60 #[arg(short, long)]
62 pub dry_run: bool,
63
64 #[arg(short, long, value_name = "PATH")]
66 pub config: Option<PathBuf>,
67
68 #[arg(long)]
70 pub no_config: bool,
71
72 #[arg(long)]
74 pub debug: bool,
75
76 #[arg(long, value_name = "SHELL", value_enum)]
78 pub completions: Option<CliShell>,
79}
80
81#[derive(Debug, Clone, Copy, ValueEnum)]
83pub enum CliShell {
84 Bash,
86 Zsh,
88 Fish,
90 Powershell,
92 Elvish,
94}
95
96#[derive(Debug, Clone, Copy, ValueEnum)]
98pub enum CliSortMode {
99 Recent,
101 Alpha,
103 Category,
105}
106
107impl From<CliSortMode> for SortMode {
108 fn from(mode: CliSortMode) -> Self {
109 match mode {
110 CliSortMode::Recent => SortMode::Recent,
111 CliSortMode::Alpha => SortMode::Alpha,
112 CliSortMode::Category => SortMode::Category,
113 }
114 }
115}
116
117#[derive(Debug, Clone, Copy, ValueEnum)]
119pub enum CliRunner {
120 Npm,
121 Yarn,
122 Pnpm,
123 Bun,
124}
125
126impl From<CliRunner> for Runner {
127 fn from(runner: CliRunner) -> Self {
128 match runner {
129 CliRunner::Npm => Runner::Npm,
130 CliRunner::Yarn => Runner::Yarn,
131 CliRunner::Pnpm => Runner::Pnpm,
132 CliRunner::Bun => Runner::Bun,
133 }
134 }
135}
136
137impl Cli {
138 pub fn parse_args() -> Self {
140 Cli::parse()
141 }
142
143 pub fn project_dir(&self) -> PathBuf {
147 self.path
148 .clone()
149 .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
150 }
151
152 pub fn should_show_tui(&self) -> bool {
154 !self.list && !self.last && self.script.is_none()
155 }
156
157 pub fn sort_mode(&self) -> Option<SortMode> {
159 self.sort.map(Into::into)
160 }
161
162 pub fn runner_override(&self) -> Option<Runner> {
164 self.runner.map(Into::into)
165 }
166
167 pub fn generate_completions(shell: CliShell) {
169 let mut cmd = Cli::command();
170 let shell = match shell {
171 CliShell::Bash => Shell::Bash,
172 CliShell::Zsh => Shell::Zsh,
173 CliShell::Fish => Shell::Fish,
174 CliShell::Powershell => Shell::PowerShell,
175 CliShell::Elvish => Shell::Elvish,
176 };
177 generate(shell, &mut cmd, "nrs", &mut std::io::stdout());
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_default_project_dir() {
187 let cli = Cli {
188 path: None,
189 last: false,
190 list: false,
191 exclude: vec![],
192 sort: None,
193 runner: None,
194 args: None,
195 script: None,
196 dry_run: false,
197 config: None,
198 no_config: false,
199 debug: false,
200 completions: None,
201 };
202
203 assert!(cli.project_dir().is_absolute() || cli.project_dir() == PathBuf::from("."));
205 }
206
207 #[test]
208 fn test_should_show_tui() {
209 let mut cli = Cli {
210 path: None,
211 last: false,
212 list: false,
213 exclude: vec![],
214 sort: None,
215 runner: None,
216 args: None,
217 script: None,
218 dry_run: false,
219 config: None,
220 no_config: false,
221 debug: false,
222 completions: None,
223 };
224
225 assert!(cli.should_show_tui());
226
227 cli.list = true;
228 assert!(!cli.should_show_tui());
229
230 cli.list = false;
231 cli.last = true;
232 assert!(!cli.should_show_tui());
233
234 cli.last = false;
235 cli.script = Some("dev".to_string());
236 assert!(!cli.should_show_tui());
237 }
238}