use std::path::PathBuf;
use clap::{CommandFactory, Parser, ValueEnum};
use clap_complete::{generate, Shell};
use crate::config::SortMode;
use crate::package::Runner;
#[derive(Parser, Debug)]
#[command(name = "nrs")]
#[command(author, version, about, long_about = None)]
#[command(arg_required_else_help = false)]
pub struct Cli {
#[arg(value_name = "PATH")]
pub path: Option<PathBuf>,
#[arg(short = 'L', long = "last")]
pub last: bool,
#[arg(short, long)]
pub list: bool,
#[arg(short, long, value_name = "PATTERN")]
pub exclude: Vec<String>,
#[arg(short, long, value_name = "MODE", value_enum)]
pub sort: Option<CliSortMode>,
#[arg(short, long, value_name = "RUNNER", value_enum)]
pub runner: Option<CliRunner>,
#[arg(short, long, value_name = "ARGS", allow_hyphen_values = true)]
pub args: Option<String>,
#[arg(short = 'n', long = "script", value_name = "NAME")]
pub script: Option<String>,
#[arg(short, long)]
pub dry_run: bool,
#[arg(short, long, value_name = "PATH")]
pub config: Option<PathBuf>,
#[arg(long)]
pub no_config: bool,
#[arg(long)]
pub debug: bool,
#[arg(long, value_name = "SHELL", value_enum)]
pub completions: Option<CliShell>,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum CliShell {
Bash,
Zsh,
Fish,
Powershell,
Elvish,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum CliSortMode {
Recent,
Alpha,
Category,
}
impl From<CliSortMode> for SortMode {
fn from(mode: CliSortMode) -> Self {
match mode {
CliSortMode::Recent => SortMode::Recent,
CliSortMode::Alpha => SortMode::Alpha,
CliSortMode::Category => SortMode::Category,
}
}
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum CliRunner {
Npm,
Yarn,
Pnpm,
Bun,
}
impl From<CliRunner> for Runner {
fn from(runner: CliRunner) -> Self {
match runner {
CliRunner::Npm => Runner::Npm,
CliRunner::Yarn => Runner::Yarn,
CliRunner::Pnpm => Runner::Pnpm,
CliRunner::Bun => Runner::Bun,
}
}
}
impl Cli {
pub fn parse_args() -> Self {
Cli::parse()
}
pub fn project_dir(&self) -> PathBuf {
self.path
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
}
pub fn should_show_tui(&self) -> bool {
!self.list && !self.last && self.script.is_none()
}
pub fn sort_mode(&self) -> Option<SortMode> {
self.sort.map(Into::into)
}
pub fn runner_override(&self) -> Option<Runner> {
self.runner.map(Into::into)
}
pub fn generate_completions(shell: CliShell) {
let mut cmd = Cli::command();
let shell = match shell {
CliShell::Bash => Shell::Bash,
CliShell::Zsh => Shell::Zsh,
CliShell::Fish => Shell::Fish,
CliShell::Powershell => Shell::PowerShell,
CliShell::Elvish => Shell::Elvish,
};
generate(shell, &mut cmd, "nrs", &mut std::io::stdout());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_project_dir() {
let cli = Cli {
path: None,
last: false,
list: false,
exclude: vec![],
sort: None,
runner: None,
args: None,
script: None,
dry_run: false,
config: None,
no_config: false,
debug: false,
completions: None,
};
assert!(cli.project_dir().is_absolute() || cli.project_dir() == PathBuf::from("."));
}
#[test]
fn test_should_show_tui() {
let mut cli = Cli {
path: None,
last: false,
list: false,
exclude: vec![],
sort: None,
runner: None,
args: None,
script: None,
dry_run: false,
config: None,
no_config: false,
debug: false,
completions: None,
};
assert!(cli.should_show_tui());
cli.list = true;
assert!(!cli.should_show_tui());
cli.list = false;
cli.last = true;
assert!(!cli.should_show_tui());
cli.last = false;
cli.script = Some("dev".to_string());
assert!(!cli.should_show_tui());
}
}