use clap::{Arg, ArgMatches, Command as ClapCommand};
use rustyline::completion::Pair as RustlinePair;
use crate::env::Env;
use crate::error::ClickError;
use crate::output::ClickWriter;
use std::cell::RefCell;
use std::io::Write;
pub fn identity<T>(t: T) -> T {
t
}
pub fn try_complete_all(prefix: &str, cols: &[&str], extra_cols: &[&str]) -> Vec<RustlinePair> {
let mut v = vec![];
for val in cols.iter().chain(extra_cols.iter()) {
if let Some(rest) = val.strip_prefix(prefix) {
v.push(RustlinePair {
display: val.to_string(),
replacement: rest.to_string(),
});
}
}
v
}
pub fn try_complete(prefix: &str, extra_cols: &[&str]) -> Vec<RustlinePair> {
let mut v = vec![];
for val in extra_cols.iter() {
if let Some(rest) = val.strip_prefix(prefix) {
v.push(RustlinePair {
display: val.to_string(),
replacement: rest.to_string(),
});
}
}
v
}
macro_rules! extract_first {
($map: ident) => {{
let mut result: [&str; $map.len()] = [""; $map.len()];
let mut i = 0;
while i < $map.len() {
result[i] = $map[i].0;
i += 1;
}
result
}};
}
const DEFAULT_HELP_TEMPLATE: &str = "\
{bin} {version}\n\
{about-with-newline}\n\
\n\
{before-help}\
{usage-heading}\n {usage}\n\
\n\
{all-args}{after-help}\
";
pub trait Cmd {
fn exec(
&self,
env: &mut Env,
args: &mut dyn Iterator<Item = &str>,
writer: &mut ClickWriter,
) -> Result<(), ClickError>;
fn is(&self, l: &str) -> bool;
fn get_name(&self) -> &'static str;
fn try_complete(&self, index: usize, prefix: &str, env: &Env) -> Vec<RustlinePair>;
fn try_completed_named(
&self,
index: usize,
opt: &str,
prefix: &str,
env: &Env,
) -> Vec<RustlinePair>;
fn complete_option(&self, prefix: &str) -> Vec<RustlinePair>;
fn write_help(&self, writer: &mut ClickWriter);
fn about(&self) -> &'static str;
}
pub fn start_clap(
name: &'static str,
about: &'static str,
aliases: &'static str,
trailing_var_arg: bool,
) -> ClapCommand<'static> {
let app = ClapCommand::new(name)
.about(about)
.before_help(aliases)
.help_template(DEFAULT_HELP_TEMPLATE)
.disable_version_flag(true)
.no_binary_name(true);
if trailing_var_arg {
app.trailing_var_arg(true)
} else {
app
}
}
pub fn exec_match<F>(
clap: &RefCell<ClapCommand<'static>>,
env: &mut Env,
args: &mut dyn Iterator<Item = &str>,
writer: &mut ClickWriter,
func: F,
) -> Result<(), ClickError>
where
F: FnOnce(ArgMatches, &mut Env, &mut ClickWriter) -> Result<(), ClickError>,
{
let mut cmd = clap.borrow_mut();
let matches = cmd.try_get_matches_from_mut(args);
match matches {
Ok(matches) => func(matches, env, writer),
Err(e) => {
if e.kind() == clap::ErrorKind::DisplayHelp {
cmd.print_help().expect("Couldn't print help");
Ok(())
} else if e.kind() == clap::ErrorKind::DisplayVersion {
clickwriteln!(writer, "{}", e);
Ok(())
} else {
Err(ClickError::Clap(e))
}
}
}
}
macro_rules! noop_complete {
() => {
vec![]
};
}
macro_rules! no_named_complete {
() => {
HashMap::new()
};
}
macro_rules! command {
($cmd_name:ident, $name:expr, $about:expr, $extra_args:expr, $aliases:expr, $cmplters: expr,
$named_cmplters: expr, $cmd_expr:expr) => {
command!(
$cmd_name,
$name,
$about,
$extra_args,
$aliases,
$cmplters,
$named_cmplters,
$cmd_expr,
false
);
};
($cmd_name:ident, $name:expr, $about:expr, $extra_args:expr, $aliases:expr, $cmplters: expr,
$named_cmplters: expr, $cmd_expr:expr, $trailing_var_arg: expr) => {
pub struct $cmd_name {
aliases: Vec<&'static str>,
clap: RefCell<ClapCommand<'static>>,
completers: Vec<&'static dyn Fn(&str, &Env) -> Vec<RustlinePair>>,
named_completers: HashMap<String, fn(&str, &Env) -> Vec<RustlinePair>>,
}
impl $cmd_name {
pub fn new() -> $cmd_name {
use crossterm::style::Stylize;
lazy_static! {
static ref ALIASES_STR: String =
format!("{}:\n {:?}", "ALIASES".yellow(), $aliases);
}
let clap = start_clap($name, $about, &ALIASES_STR, $trailing_var_arg);
let extra = $extra_args(clap);
$cmd_name {
aliases: $aliases,
clap: RefCell::new(extra),
completers: $cmplters,
named_completers: $named_cmplters,
}
}
}
impl Cmd for $cmd_name {
fn exec(
&self,
env: &mut Env,
args: &mut dyn Iterator<Item = &str>,
writer: &mut ClickWriter,
) -> Result<(), crate::error::ClickError> {
exec_match(&self.clap, env, args, writer, $cmd_expr)
}
fn is(&self, l: &str) -> bool {
self.aliases.contains(&l)
}
fn get_name(&self) -> &'static str {
$name
}
fn write_help(&self, writer: &mut ClickWriter) {
if let Err(res) = self.clap.borrow_mut().print_help() {
clickwriteln!(writer, "Couldn't print help: {}", res);
}
}
fn about(&self) -> &'static str {
$about
}
fn try_complete(&self, index: usize, prefix: &str, env: &Env) -> Vec<RustlinePair> {
match self.completers.get(index) {
Some(completer) => completer(prefix, env),
None => vec![],
}
}
fn try_completed_named(
&self,
index: usize,
opt: &str,
prefix: &str,
env: &Env,
) -> Vec<RustlinePair> {
let app = self.clap.borrow();
let mut args = app.get_arguments();
let arg_opt = args.find(|arg| {
if let Some(long) = arg.get_long() {
if long == &opt[2..] {
return true;
}
}
if opt.len() == 2 {
if let Some(short) = arg.get_short() {
if (short == opt.chars().nth(1).unwrap()) {
return true;
}
}
}
false
});
match arg_opt {
Some(arg) => match self
.named_completers
.get(arg.get_long().unwrap_or_else(|| ""))
{
Some(completer) => completer(prefix, env),
None => vec![],
},
None => self.try_complete(index, prefix, env),
}
}
fn complete_option(&self, prefix: &str) -> Vec<RustlinePair> {
let repoff = prefix.len();
let app = self.clap.borrow();
let args = app.get_arguments();
args.filter(|arg| completer::long_matches(&arg.get_long(), prefix))
.map(|arg| {
RustlinePair {
display: format!("--{}", arg.get_long().unwrap()), replacement: format!(
"{} ",
arg.get_long().unwrap()[repoff..].to_string()
),
}
})
.collect()
}
}
};
}
macro_rules! list_command {
($cmd_name:ident, $name:expr, $about:expr, $cols: expr, $extra_cols:expr, $extra_args:expr,
$aliases:expr, $cmplters: expr, $named_cmplters: expr, $cmd_expr:expr) => {
mod list_sort_completers {
use crate::{command::command_def::try_complete_all, env::Env};
use rustyline::completion::Pair as RustlinePair;
#[allow(non_snake_case)]
pub fn $cmd_name(prefix: &str, _env: &Env) -> Vec<RustlinePair> {
try_complete_all(prefix, $cols, $extra_cols)
}
}
mod list_show_completers {
use crate::{command::command_def::try_complete, env::Env};
use rustyline::completion::Pair as RustlinePair;
#[allow(non_snake_case)]
pub fn $cmd_name(prefix: &str, _env: &Env) -> Vec<RustlinePair> {
try_complete(prefix, $extra_cols)
}
}
use rustyline::completion::Pair as RustlinePair;
command!(
$cmd_name,
$name,
$about,
$extra_args,
$aliases,
$cmplters,
[
(
"sort".to_string(),
list_sort_completers::$cmd_name as fn(&str, &Env) -> Vec<RustlinePair>
),
(
"show".to_string(),
list_show_completers::$cmd_name as fn(&str, &Env) -> Vec<RustlinePair>
)
]
.into_iter()
.chain($named_cmplters)
.collect(),
$cmd_expr,
false
);
};
}
pub fn add_extra_cols<'a>(
cols: &mut Vec<&'a str>,
labels: bool,
flags: Vec<&str>,
extra_cols: &[(&'a str, &'a str)],
) {
let show_all = flags.iter().any(|e| e.eq_ignore_ascii_case("all"));
for (flag, col) in extra_cols.iter() {
if col.eq(&"Labels") {
if labels || flags.iter().any(|e| e.eq_ignore_ascii_case("labels")) {
cols.push(col)
}
} else if show_all || flags.iter().any(|e| e.eq_ignore_ascii_case(flag)) {
cols.push(col)
}
}
}
pub struct SortCol(pub &'static str);
pub fn sort_arg<'a>(cols: &'a [&'a str], extra_cols: Option<&'a [&'a str]>) -> Arg<'a> {
let arg = Arg::new("sort")
.short('s')
.long("sort")
.help(
"Sort by specified column (if column isn't shown by default, it will \
be shown)",
)
.takes_value(true)
.ignore_case(true)
.possible_values(cols);
match extra_cols {
Some(extra) => arg.possible_values(extra),
None => arg,
}
}
static SHOW_HELP: &str =
"Comma separated list (case-insensitive) of extra columns to show in output. \
Use '--show all' to show all available columns.";
static SHOW_HELP_WITH_LABELS: &str =
"Comma separated list (case-insensitive) of extra columns to show in output. \
Use '--show all,labels' to show all available columns. (Note that 'all' doesn't \
include labels due to thier size)";
pub fn show_arg<'a>(extra_cols: &'a [&'a str], labels: bool) -> Arg<'a> {
let arg = Arg::new("show")
.short('S')
.long("show")
.takes_value(true)
.possible_value("all")
.possible_values(extra_cols)
.ignore_case(true)
.use_value_delimiter(true);
if labels {
arg.help(SHOW_HELP_WITH_LABELS)
} else {
arg.help(SHOW_HELP)
}
}