use crate::{
apps::SystemApps,
common::{mime_types, DesktopHandler, MimeType, UserPath},
};
use clap::{builder::StyledStr, ArgAction, Args, Parser, Subcommand};
use clap_complete::{
engine::{ArgValueCompleter, CompletionCandidate},
PathCompleter,
};
use clap_verbosity_flag::{Verbosity, WarnLevel};
use std::{ffi::OsStr, fmt::Write, io::IsTerminal};
#[deny(missing_docs)]
#[derive(Parser)]
#[clap(disable_help_subcommand = true)]
#[clap(version, about)]
pub struct Cli {
#[command(subcommand)]
pub command: Cmd,
#[clap(global = true, long = "disable-notifications", short = 'n', action = ArgAction::SetFalse)]
enable_notifications: bool,
#[clap(global = true, long = "force-terminal-output", short = 't')]
terminal_output: Option<bool>,
#[command(flatten)]
pub verbosity: Verbosity<WarnLevel>,
}
#[allow(dead_code)] impl Cli {
pub fn terminal_output(&self) -> bool {
self.terminal_output
.unwrap_or(std::io::stdout().is_terminal())
}
pub fn show_notifications(&self) -> bool {
!self.terminal_output() && self.enable_notifications
}
}
#[deny(missing_docs)]
#[derive(Clone, Subcommand)]
pub enum Cmd {
#[clap(verbatim_doc_comment)]
List {
#[clap(long)]
json: bool,
#[clap(long, short)]
all: bool,
},
Open {
#[clap(required = true, add=ArgValueCompleter::new(PathCompleter::any()))]
paths: Vec<UserPath>,
#[command(flatten)]
selector_args: SelectorArgs,
},
Set {
#[clap(add = ArgValueCompleter::new(autocomplete_mimes))]
mime: MimeType,
#[clap(add = ArgValueCompleter::new(autocomplete_desktop_files))]
handler: DesktopHandler,
},
Unset {
#[clap(add = ArgValueCompleter::new(autocomplete_mimes))]
mime: MimeType,
},
Launch {
#[clap(add = ArgValueCompleter::new(autocomplete_mimes))]
mime: MimeType,
#[clap(add=ArgValueCompleter::new(PathCompleter::any()))]
args: Vec<String>,
#[command(flatten)]
selector_args: SelectorArgs,
},
#[clap(verbatim_doc_comment)]
Get {
#[clap(long)]
json: bool,
#[clap(add = ArgValueCompleter::new(autocomplete_mimes))]
mime: MimeType,
#[command(flatten)]
selector_args: SelectorArgs,
},
Add {
#[clap(add = ArgValueCompleter::new(autocomplete_mimes))]
mime: MimeType,
#[clap(add = ArgValueCompleter::new(autocomplete_desktop_files))]
handler: DesktopHandler,
},
Remove {
#[clap(add = ArgValueCompleter::new(autocomplete_mimes))]
mime: MimeType,
#[clap(add = ArgValueCompleter::new(autocomplete_desktop_files))]
handler: DesktopHandler,
},
#[clap(verbatim_doc_comment)]
Mime {
#[clap(required = true, add=ArgValueCompleter::new(PathCompleter::any()))]
paths: Vec<UserPath>,
#[clap(long)]
json: bool,
},
}
#[derive(Clone, Args)]
pub struct SelectorArgs {
#[clap(long, short)]
pub selector: Option<String>,
#[clap(long, short)]
pub enable_selector: Option<bool>,
}
fn autocomplete_mimes(current: &OsStr) -> Vec<CompletionCandidate> {
let mut mimes = mime_db::EXTENSIONS
.iter()
.map(|(ext, _)| format!(".{ext}"))
.chain(mime_types())
.filter(|x| x.starts_with(current.to_string_lossy().as_ref()))
.map(CompletionCandidate::new)
.collect::<Vec<_>>();
mimes.sort();
mimes
}
#[mutants::skip] fn autocomplete_desktop_files(current: &OsStr) -> Vec<CompletionCandidate> {
SystemApps::get_entries()
.expect("handlr error: Could not get system desktop entries")
.filter(|(path, _)| {
path.to_string_lossy()
.starts_with(current.to_string_lossy().as_ref())
})
.map(|(path, entry)| {
let mut name = StyledStr::new();
write!(name, "{}", entry.name)
.expect("handlr error: Could not write desktop entry name");
CompletionCandidate::new(path).help(Some(name))
})
.collect()
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::error::Result;
use super::*;
#[test]
fn test_autocomplete_mimes() {
insta::assert_compact_debug_snapshot!(autocomplete_mimes(OsStr::new(
""
)));
}
#[test]
fn test_show_notifications() -> Result<()> {
let mut cli = Cli {
command: Cmd::Unset {
mime: MimeType::from_str("fake/mime")?,
},
enable_notifications: true,
terminal_output: Some(false),
verbosity: Verbosity::default(),
};
assert!(cli.show_notifications());
cli.terminal_output = Some(true);
assert!(!cli.show_notifications());
cli.enable_notifications = false;
assert!(!cli.show_notifications());
cli.terminal_output = Some(true);
assert!(!cli.show_notifications());
Ok(())
}
}