use super::*;
pub(super) fn emit_bash(data: &CompletionData) -> String {
let mut s = String::new();
s.push_str("# fdl bash completion (generated)\n");
s.push_str("# eval \"$(fdl completions bash)\"\n");
s.push_str("__fdl_find_envs() {\n");
s.push_str(" local _dir=\"$PWD\" _f _n\n");
s.push_str(" while :; do\n");
s.push_str(" if [[ -f \"$_dir/fdl.yml\" ]]; then\n");
s.push_str(" for _f in \"$_dir\"/fdl.*.yml; do\n");
s.push_str(" [[ -f \"$_f\" ]] || continue\n");
s.push_str(" _n=\"${_f##*/fdl.}\"\n");
s.push_str(" _n=\"${_n%.yml}\"\n");
s.push_str(" [[ -n \"$_n\" && \"$_n\" != *.* ]] && printf '@%s\\n' \"$_n\"\n");
s.push_str(" done\n");
s.push_str(" return\n");
s.push_str(" fi\n");
s.push_str(" [[ \"$_dir\" == \"/\" ]] && return\n");
s.push_str(" _dir=\"$(dirname \"$_dir\")\"\n");
s.push_str(" done\n");
s.push_str("}\n");
s.push_str("_fdl_completions() {\n");
s.push_str(" local cur prev cmd cword env_offset _fdl_envs\n");
s.push_str(" cur=\"${COMP_WORDS[COMP_CWORD]}\"\n");
s.push_str(" prev=\"${COMP_WORDS[COMP_CWORD-1]}\"\n");
s.push('\n');
s.push_str(" _fdl_envs=\" $(__fdl_find_envs | tr '\\n' ' ') \"\n");
s.push_str(" if [[ \"$cur\" == @* ]]; then\n");
s.push_str(" COMPREPLY=($(compgen -W \"${_fdl_envs}\" -- \"$cur\"))\n");
s.push_str(" return\n");
s.push_str(" fi\n");
s.push_str(" env_offset=0\n");
s.push_str(" if [[ ${#COMP_WORDS[@]} -gt 1 && \"$_fdl_envs\" == *\" ${COMP_WORDS[1]} \"* ]]; then\n");
s.push_str(" env_offset=1\n");
s.push_str(" fi\n");
s.push_str(" cmd=\"${COMP_WORDS[$((1 + env_offset))]}\"\n");
s.push_str(" cword=$((COMP_CWORD - env_offset))\n");
s.push('\n');
let top = join_for_shell(&data.top_level);
let top_with_flags = format!(
"{top} {}",
TOP_FLAGS.join(" ")
);
s.push_str(" if [[ $cword -eq 1 ]]; then\n");
s.push_str(" if [[ $env_offset -eq 0 ]]; then\n");
s.push_str(&format!(
" COMPREPLY=($(compgen -W \"{top_with_flags}${{_fdl_envs}}\" -- \"$cur\"))\n"
));
s.push_str(" else\n");
s.push_str(&format!(
" COMPREPLY=($(compgen -W \"{top_with_flags}\" -- \"$cur\"))\n"
));
s.push_str(" fi\n");
s.push_str(" return\n");
s.push_str(" fi\n");
for cmd in &data.commands {
for tc in &cmd.tree_commands {
s.push_str(&format!(
"\n if [[ \"$cmd\" == \"{cmdname}\" && \"${{COMP_WORDS[$((2 + env_offset))]}}\" == \"{sub}\" && $cword -ge 3 ]]; then\n",
cmdname = cmd.name,
sub = tc.name
));
s.push_str(" case \"$prev\" in\n");
for opt in &tc.options {
if !opt.takes_value {
continue;
}
let flags = opt.flag_tokens().join("|");
let line = match &opt.value {
ValueKind::Choices(cs) => format!(
" {flags}) COMPREPLY=($(compgen -W \"{}\" -- \"$cur\")); return ;;\n",
cs.join(" ")
),
ValueKind::Path => format!(
" {flags}) COMPREPLY=($(compgen -f -- \"$cur\")); return ;;\n",
),
ValueKind::Completer(c) => format!(
" {flags}) COMPREPLY=($(compgen -W \"$({c})\" -- \"$cur\")); return ;;\n",
),
ValueKind::Any => format!(
" {flags}) return ;;\n",
),
ValueKind::None => continue,
};
s.push_str(&line);
}
s.push_str(" esac\n");
let mut flags: Vec<String> =
tc.options.iter().flat_map(|o| o.flag_tokens()).collect();
flags.push("--help".into());
flags.push("-h".into());
s.push_str(&format!(
" COMPREPLY=($(compgen -W \"{}\" -- \"$cur\"))\n",
flags.join(" ")
));
s.push_str(" return\n");
s.push_str(" fi\n");
}
}
for cmd in &data.commands {
s.push_str(&format!("\n if [[ \"$cmd\" == \"{name}\" ]]; then\n", name = cmd.name));
s.push_str(" case \"$prev\" in\n");
for opt in &cmd.options {
if !opt.takes_value {
continue;
}
let flags = opt.flag_tokens().join("|");
let line = match &opt.value {
ValueKind::Choices(cs) => format!(
" {flags}) COMPREPLY=($(compgen -W \"{}\" -- \"$cur\")); return ;;\n",
cs.join(" ")
),
ValueKind::Path => format!(
" {flags}) COMPREPLY=($(compgen -f -- \"$cur\")); return ;;\n",
),
ValueKind::Completer(c) => format!(
" {flags}) COMPREPLY=($(compgen -W \"$({c})\" -- \"$cur\")); return ;;\n",
),
ValueKind::Any => format!(
" {flags}) return ;;\n",
),
ValueKind::None => continue,
};
s.push_str(&line);
}
s.push_str(" esac\n");
let option_flags: Vec<String> = cmd
.options
.iter()
.flat_map(|o| o.flag_tokens())
.collect();
let cmd_flags_str = {
let mut v = option_flags.clone();
v.push("--help".into());
v.push("-h".into());
v.join(" ")
};
let positional_tokens = cmd.first_positional_tokens();
let positionals_str = positional_tokens.join(" ");
s.push_str(" if [[ $cword -eq 2 ]]; then\n");
if positional_tokens.is_empty() {
s.push_str(&format!(
" COMPREPLY=($(compgen -W \"{cmd_flags_str}\" -- \"$cur\"))\n"
));
} else {
s.push_str(&format!(
" COMPREPLY=($(compgen -W \"{positionals_str} {cmd_flags_str}\" -- \"$cur\"))\n"
));
}
s.push_str(" return\n");
s.push_str(" fi\n");
s.push_str(&format!(
" COMPREPLY=($(compgen -W \"{cmd_flags_str}\" -- \"$cur\"))\n"
));
s.push_str(" return\n");
s.push_str(" fi\n");
}
for b in data.builtins.iter().filter(|b| b.path.len() == 2) {
let parent = &b.path[0];
let child = &b.path[1];
s.push_str(&format!(
"\n if [[ \"$cmd\" == \"{parent}\" && \"${{COMP_WORDS[$((2 + env_offset))]}}\" == \"{child}\" && $cword -ge 3 ]]; then\n"
));
s.push_str(" case \"$prev\" in\n");
for opt in &b.options {
if !opt.takes_value {
continue;
}
let flags = opt.flag_tokens().join("|");
let line = match &opt.value {
ValueKind::Choices(cs) => format!(
" {flags}) COMPREPLY=($(compgen -W \"{}\" -- \"$cur\")); return ;;\n",
cs.join(" ")
),
ValueKind::Path => format!(
" {flags}) COMPREPLY=($(compgen -f -- \"$cur\")); return ;;\n",
),
ValueKind::Completer(c) => format!(
" {flags}) COMPREPLY=($(compgen -W \"$({c})\" -- \"$cur\")); return ;;\n",
),
ValueKind::Any => format!(
" {flags}) return ;;\n",
),
ValueKind::None => continue,
};
s.push_str(&line);
}
s.push_str(" esac\n");
let flags_str = b.option_flags_with_help().join(" ");
s.push_str(&format!(
" COMPREPLY=($(compgen -W \"{flags_str}\" -- \"$cur\"))\n"
));
s.push_str(" return\n");
s.push_str(" fi\n");
}
for b in data.builtins.iter().filter(|b| b.path.len() == 1) {
let name = &b.path[0];
let has_subs = !b.sub_commands.is_empty();
let has_opts = !b.options.is_empty();
if !has_subs && !has_opts {
continue; }
if b.options.iter().any(|o| o.takes_value) {
s.push_str(&format!(
"\n if [[ \"$cmd\" == \"{name}\" && $cword -ge 2 ]]; then\n"
));
s.push_str(" case \"$prev\" in\n");
for opt in &b.options {
if !opt.takes_value {
continue;
}
let flags = opt.flag_tokens().join("|");
let line = match &opt.value {
ValueKind::Choices(cs) => format!(
" {flags}) COMPREPLY=($(compgen -W \"{}\" -- \"$cur\")); return ;;\n",
cs.join(" ")
),
ValueKind::Path => format!(
" {flags}) COMPREPLY=($(compgen -f -- \"$cur\")); return ;;\n",
),
ValueKind::Completer(c) => format!(
" {flags}) COMPREPLY=($(compgen -W \"$({c})\" -- \"$cur\")); return ;;\n",
),
ValueKind::Any => format!(
" {flags}) return ;;\n",
),
ValueKind::None => continue,
};
s.push_str(&line);
}
s.push_str(" esac\n fi\n");
}
s.push_str(&format!(
"\n if [[ \"$cmd\" == \"{name}\" && $cword -eq 2 ]]; then\n"
));
let mut position2_words: Vec<String> = b.sub_commands.clone();
if has_opts {
position2_words.extend(b.option_flags_with_help());
} else if has_subs {
position2_words.push("--help".into());
position2_words.push("-h".into());
}
s.push_str(&format!(
" COMPREPLY=($(compgen -W \"{}\" -- \"$cur\"))\n",
position2_words.join(" ")
));
s.push_str(" return\n");
s.push_str(" fi\n");
if has_opts {
s.push_str(&format!(
"\n if [[ \"$cmd\" == \"{name}\" && $cword -ge 3 ]]; then\n"
));
s.push_str(&format!(
" COMPREPLY=($(compgen -W \"{}\" -- \"$cur\"))\n",
b.option_flags_with_help().join(" ")
));
s.push_str(" return\n");
s.push_str(" fi\n");
}
}
s.push_str("}\n");
s.push_str("complete -F _fdl_completions fdl\n");
s
}
fn join_for_shell(v: &[String]) -> String {
v.join(" ")
}