use super::phantom::KNOWN_PROGRAMS;
const SHELL_TAGS: &[&str] = &[
"bash",
"sh",
"zsh",
"shell",
"shell-session",
"sh-session",
"console",
"terminal",
"fish",
"ksh",
"cmd",
"powershell",
"ps1",
];
const PROMPT_MARKERS: &[char] = &['$', '%', '>', '#'];
const LEADING_KEYWORDS: &[&str] = &[
"do", "then", "else", "elif", "sudo", "time", "nohup", "exec", "command", "nice", "builtin",
];
pub(crate) fn narrates_unrun_shell_block(text: &str) -> bool {
shell_block_bodies(text)
.iter()
.any(|body| holds_known_command(body))
}
fn shell_block_bodies(text: &str) -> Vec<String> {
let mut out = Vec::new();
let mut open: Option<String> = None;
for line in text.lines() {
if let Some(rest) = line.trim_start().strip_prefix("```") {
match open.take() {
Some(body) => out.push(body),
None => {
let tag = rest
.split_whitespace()
.next()
.unwrap_or_default()
.to_lowercase();
if SHELL_TAGS.contains(&tag.as_str()) {
open = Some(String::new());
}
}
}
continue;
}
if let Some(body) = open.as_mut() {
body.push_str(line);
body.push('\n');
}
}
if let Some(body) = open {
out.push(body);
}
out
}
fn holds_known_command(body: &str) -> bool {
body.lines().any(starts_known_command)
}
fn starts_known_command(line: &str) -> bool {
let line = line.trim().trim_start_matches(PROMPT_MARKERS).trim();
line.split(['|', ';', '\n'])
.flat_map(|seg| seg.split("&&"))
.flat_map(|seg| seg.split("||"))
.any(segment_is_known_command)
}
fn segment_is_known_command(seg: &str) -> bool {
let mut words = seg
.split_whitespace()
.skip_while(|w| LEADING_KEYWORDS.contains(w));
words
.next()
.is_some_and(|prog| KNOWN_PROGRAMS.contains(&prog))
&& words.next().is_some()
}