use std::borrow::Cow;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use lineread::complete::escape;
use lineread::complete::escaped_word_start;
use lineread::complete::unescape;
use lineread::complete::Suffix;
use lineread::complete::{Completer, Completion};
use lineread::prompter::Prompter;
use lineread::terminal::Terminal;
use yaml_rust::yaml::Hash;
use yaml_rust::{Yaml, YamlLoader};
use crate::execute;
use crate::libs::prefix;
use crate::parsers;
use crate::tools;
pub struct DotsCompleter;
impl<Term: Terminal> Completer<Term> for DotsCompleter {
fn complete(
&self,
word: &str,
reader: &Prompter<Term>,
_start: usize,
_end: usize,
) -> Option<Vec<Completion>> {
let line = reader.buffer();
Some(complete_dots(line, word))
}
fn word_start(&self, line: &str, end: usize, _reader: &Prompter<Term>) -> usize {
escaped_word_start(&line[..end])
}
fn quote<'a>(&self, word: &'a str) -> Cow<'a, str> {
escape(word)
}
fn unquote<'a>(&self, word: &'a str) -> Cow<'a, str> {
unescape(word)
}
}
fn get_dot_file(line: &str) -> (String, String) {
let cmd = match prefix::get_effective_command(line) {
Some(c) => c,
None => return (String::new(), String::new()),
};
let dir = tools::get_user_completer_dir();
let dot_file = format!("{}/{}.yaml", dir, cmd);
if !Path::new(&dot_file).exists() {
return (String::new(), String::new());
}
let segment = prefix::get_current_segment(line);
let args = parsers::parser_line::line_to_plain_tokens(segment);
let mut cmd_pos = None;
for (i, arg) in args.iter().enumerate() {
if arg == &cmd {
cmd_pos = Some(i);
break;
}
}
let sub_cmd = match cmd_pos {
Some(pos) => {
let remaining_args: Vec<_> = args.iter().skip(pos + 1).collect();
if (remaining_args.len() >= 2 && !remaining_args[0].starts_with('-'))
|| (!remaining_args.is_empty()
&& !remaining_args[0].starts_with('-')
&& line.ends_with(' '))
{
remaining_args[0].as_str()
} else {
""
}
}
None => "",
};
(dot_file, sub_cmd.to_string())
}
fn handle_lv1_string(res: &mut Vec<Completion>, value: &str, word: &str) {
if !value.starts_with(word) && !value.starts_with('`') {
return;
}
let linfo = parsers::parser_line::parse_line(value);
let tokens = linfo.tokens;
if tokens.len() == 1 && tokens[0].0 == "`" {
log!("run subcmd: {:?}", &tokens[0].1);
let cr = execute::run(&tokens[0].1);
let v: Vec<&str> = cr.stdout.split_whitespace().collect();
for s in v {
if s.trim().is_empty() {
continue;
}
handle_lv1_string(res, s, word);
}
return;
}
let display = None;
let suffix = Suffix::Default;
res.push(Completion {
completion: value.to_string(),
display,
suffix,
});
}
fn handle_lv1_hash(res: &mut Vec<Completion>, h: &Hash, word: &str) {
for v in h.values() {
if let Yaml::Array(ref arr) = v {
for s in arr {
if let Yaml::String(value) = s {
if !value.starts_with(word) && !value.starts_with('`') {
continue;
}
handle_lv1_string(res, value, word);
}
}
}
}
}
fn complete_dots(line: &str, word: &str) -> Vec<Completion> {
let mut res = Vec::new();
if line.trim().is_empty() {
return res;
}
let (dot_file, sub_cmd) = get_dot_file(line);
if dot_file.is_empty() {
return res;
}
let mut f;
match File::open(&dot_file) {
Ok(x) => f = x,
Err(e) => {
println_stderr!("\ncicada: open dot_file error: {:?}", e);
return res;
}
}
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => {}
Err(e) => {
println_stderr!("\ncicada: read_to_string error: {:?}", e);
return res;
}
}
let docs = match YamlLoader::load_from_str(&s) {
Ok(x) => x,
Err(e) => {
println_stderr!("\ncicada: Bad Yaml file: {}: {:?}", dot_file, e);
return res;
}
};
for doc in docs.iter() {
match *doc {
Yaml::Array(ref v) => {
for x in v {
match *x {
Yaml::String(ref name) => {
if !sub_cmd.is_empty() {
continue;
}
handle_lv1_string(&mut res, name, word);
}
Yaml::Hash(ref h) => {
if sub_cmd.is_empty() {
for k in h.keys() {
if let Yaml::String(value) = k {
handle_lv1_string(&mut res, value, word);
}
}
} else {
let key = Yaml::from_str(&sub_cmd);
if !h.contains_key(&key) {
continue;
}
handle_lv1_hash(&mut res, h, word);
}
}
_ => {
println_stderr!("\nThis yaml file is in bad format: {}", dot_file);
}
}
}
}
_ => {
println_stderr!("\nThis yaml file is in bad format: {}", dot_file);
}
}
}
res
}