use k8s_openapi::{api::core::v1 as api, List};
use rustyline::{
completion::{Completer, Pair},
highlight::Highlighter,
hint::Hinter,
validate::Validator,
Context, Helper, Result,
};
use crate::command::command_def::Cmd;
use crate::env::Env;
use crate::kobj::ObjType;
use std::rc::Rc;
pub struct ClickHelper {
commands: Vec<Box<dyn Cmd>>,
help_topics: Vec<&'static str>,
env: Option<Rc<Env>>,
command_completions: Vec<String>,
}
impl Helper for ClickHelper {}
impl Highlighter for ClickHelper {}
impl Validator for ClickHelper {}
impl Hinter for ClickHelper {
type Hint = String;
fn hint(&self, _line: &str, _pos: usize, _context: &Context) -> Option<String> {
None
}
}
fn get_command_completion_strings(commands: &[Box<dyn Cmd>], env: Option<&Rc<Env>>) -> Vec<String> {
let mut v = vec!["help".to_string()];
for cmd in commands.iter() {
v.push(cmd.get_name().to_string());
}
if let Some(env) = env.as_ref() {
for alias in env.click_config.aliases.iter() {
v.push(alias.alias.to_string());
}
}
v.sort_unstable();
v
}
impl ClickHelper {
pub fn new(commands: Vec<Box<dyn Cmd>>, help_topics: Vec<&'static str>) -> ClickHelper {
let command_completions = get_command_completion_strings(&commands, None);
ClickHelper {
commands,
help_topics,
env: None,
command_completions,
}
}
pub fn set_env(&mut self, env: Option<Rc<Env>>) {
self.env = env;
let command_completions = get_command_completion_strings(&self.commands, self.env.as_ref());
self.command_completions = command_completions;
}
#[allow(clippy::borrowed_box)]
fn get_exact_command(&self, line: &str) -> Option<&Box<dyn Cmd>> {
self.commands.iter().find(|&cmd| cmd.is(line))
}
fn complete_exact_command(&self, line: &str, cmd_len: usize) -> (usize, Vec<Pair>) {
let mut split = line.split_whitespace();
let linecmd = split.next().unwrap(); if let Some(cmd) = self.get_exact_command(linecmd) {
let (pos, prefix, last_opt) = match split.next_back() {
Some(back) => {
if line.ends_with(' ') {
let mut count = split.filter(|s| !s.starts_with('-')).count();
let last_opt = if back.starts_with('-') {
Some(back)
} else {
count += 1;
None
};
(count, "", last_opt)
} else if back == "-" {
return (
cmd_len,
vec![Pair {
display: "-".to_owned(),
replacement: "-".to_owned(),
}],
);
} else if let Some(opt_str) = back.strip_prefix("--") {
let mut opts = cmd.complete_option(opt_str);
if "--help".starts_with(back) {
opts.push(Pair {
display: "--help".to_owned(),
replacement: "help"[(back.len() - 2)..].to_owned(),
});
}
return (cmd_len, opts);
} else {
let mut prev_arg = split.next_back();
let mut count = split.filter(|s| !s.starts_with('-')).count();
if let Some(pa) = prev_arg {
if !pa.starts_with('-') {
count += 1;
prev_arg = None;
}
}
(count, back, prev_arg)
}
}
None => (0, "", None),
};
if let Some(ref env) = self.env {
match last_opt {
Some(opt) => {
let opts = cmd.try_completed_named(pos, opt, prefix, env);
(cmd_len, opts)
}
None => {
let opts = cmd.try_complete(pos, prefix, env);
(cmd_len, opts)
}
}
} else {
(0, vec![])
}
} else if linecmd == "help" {
let cmd_part = split.next().unwrap_or("");
if split.next().is_none() {
let mut v = vec![];
self.get_command_completions(cmd_part, &mut v);
self.get_help_completions(cmd_part, &mut v);
(5, v) } else {
(0, vec![])
}
} else {
(0, vec![])
}
}
fn get_command_completions(&self, line: &str, candidates: &mut Vec<Pair>) {
for opt in self.command_completions.iter() {
if opt.starts_with(line) {
candidates.push(Pair {
display: opt.clone(),
replacement: format!("{} ", opt),
});
}
}
}
fn get_help_completions(&self, line: &str, candidates: &mut Vec<Pair>) {
for topic in self.help_topics.iter() {
if topic.starts_with(line) {
candidates.push(Pair {
display: (*topic).to_string(),
replacement: (*topic).to_string(),
});
}
}
}
fn completion_vec(&self) -> Vec<Pair> {
self.command_completions
.iter()
.map(|s| Pair {
display: s.clone(),
replacement: format!("{} ", s),
})
.collect()
}
}
pub fn long_matches(long: &Option<&str>, prefix: &str) -> bool {
match long {
Some(lstr) => lstr.starts_with(prefix),
None => false,
}
}
impl Completer for ClickHelper {
type Candidate = Pair;
fn complete(&self, line: &str, pos: usize, _ctx: &Context) -> Result<(usize, Vec<Pair>)> {
if pos == 0 {
Ok((0, self.completion_vec()))
} else if line.contains(char::is_whitespace) {
let expanded = self
.env
.as_ref()
.map(|e| crate::command_processor::alias_expand_line(e, line));
Ok(self.complete_exact_command(expanded.as_deref().unwrap_or(line), line.len()))
} else {
let mut v = Vec::new();
self.get_command_completions(line, &mut v);
Ok((0, v))
}
}
}
pub fn context_complete(prefix: &str, env: &Env) -> Vec<Pair> {
let mut v = Vec::new();
for context in env.config.contexts.keys() {
if let Some(rest) = context.strip_prefix(prefix) {
v.push(Pair {
display: context.to_string(),
replacement: rest.to_string(),
})
}
}
v
}
pub fn namespace_completer(prefix: &str, env: &Env) -> Vec<Pair> {
let (request, _response_body) = api::Namespace::list_namespace(Default::default()).unwrap();
match env.run_on_context::<_, List<api::Namespace>>(|c| c.execute_list(request)) {
Ok(nslist) => nslist
.items
.into_iter()
.filter_map(|ns| {
ns.metadata.name.and_then(|name| {
if let Some(rep) = name.strip_prefix(prefix) {
let replacement = rep.to_string();
Some(Pair {
display: name,
replacement,
})
} else {
None
}
})
})
.collect(),
Err(_) => vec![],
}
}
pub fn container_completer(prefix: &str, env: &Env) -> Vec<Pair> {
let mut v = vec![];
if let Some(pod) = env.current_pod() {
if let ObjType::Pod { ref containers } = pod.typ {
for cont in containers.iter() {
if let Some(rest) = cont.strip_prefix(prefix) {
v.push(Pair {
display: cont.clone(),
replacement: rest.to_string(),
});
}
}
}
}
v
}
macro_rules! possible_values_completer {
($name: ident, $values: expr) => {
pub fn $name(prefix: &str, _env: &Env) -> Vec<Pair> {
let mut v = vec![];
for val in $values.iter() {
if let Some(rest) = val.strip_prefix(prefix) {
v.push(Pair {
display: val.to_string(),
replacement: rest.to_string(),
});
}
}
v
}
};
}
possible_values_completer!(setoptions_values_completer, crate::command::click::SET_OPTS);
possible_values_completer!(
unsetoptions_values_completer,
crate::command::click::UNSET_OPTS
);
possible_values_completer!(
portforwardaction_values_completer,
["list", "output", "stop"]
);