use std::io::{self, Write};
use std::path::Path;
use crate::{init, learn, pattern};
const EMPTY_USER_PATTERNS_LINE: &str = " no learned patterns yet";
fn write_pattern_entry<W: Write>(
out: &mut W,
cmd_match: &str,
has_success: bool,
has_failure: bool,
) -> io::Result<()> {
if has_success || has_failure {
let mut flags = Vec::new();
if has_success {
flags.push("success");
}
if has_failure {
flags.push("failure");
}
writeln!(out, " {cmd_match} [{}]", flags.join("] ["))
} else {
writeln!(out, " {cmd_match}")
}
}
fn with_path(path: &Path, e: &io::Error) -> io::Error {
io::Error::new(e.kind(), format!("{}: {e}", path.display()))
}
fn write_patterns_in<W: Write>(out: &mut W, dir: &Path) -> io::Result<bool> {
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(false),
Err(e) => return Err(with_path(dir, &e)),
};
let mut found = false;
for entry in entries {
let path = entry.map_err(|e| with_path(dir, &e))?.path();
if path.extension().and_then(|e| e.to_str()) != Some("toml") {
continue;
}
let meta = match std::fs::metadata(&path) {
Ok(m) => m,
Err(e) if e.kind() == io::ErrorKind::NotFound => continue,
Err(e) => return Err(with_path(&path, &e)),
};
if !meta.is_file() {
continue;
}
let text = match std::fs::read_to_string(&path) {
Ok(text) => text,
Err(e)
if matches!(
e.kind(),
io::ErrorKind::InvalidData | io::ErrorKind::NotFound
) =>
{
continue;
}
Err(e) => return Err(with_path(&path, &e)),
};
let Ok(parsed) = toml::from_str::<toml::Value>(&text) else {
continue;
};
found = true;
let cmd_match = parsed
.get("command_match")
.and_then(|v| v.as_str())
.unwrap_or("(unknown)");
let has_success = parsed.get("success").is_some();
let has_failure = parsed.get("failure").is_some();
write_pattern_entry(out, cmd_match, has_success, has_failure)?;
}
Ok(found)
}
pub fn list_patterns_in(dir: &Path) -> bool {
match write_patterns_in(&mut io::stdout().lock(), dir) {
Ok(found) => found,
Err(e) => {
eprintln!("oo: {e}");
false
}
}
}
pub fn cmd_patterns_in(dir: &Path) -> i32 {
let result = write_patterns_in(&mut io::stdout().lock(), dir);
match result {
Ok(true) => 0,
Ok(false) => {
println!("{EMPTY_USER_PATTERNS_LINE}");
0
}
Err(e) => {
eprintln!("oo: {e}");
1
}
}
}
fn write_user_section<W: Write>(out: &mut W, dir: &Path) -> io::Result<()> {
writeln!(out, "User ({}):", dir.display())?;
if !write_patterns_in(out, dir)? {
writeln!(out, "{EMPTY_USER_PATTERNS_LINE}")?;
}
Ok(())
}
fn write_builtins<W: Write>(out: &mut W) -> io::Result<()> {
for pat in pattern::builtins() {
let cmd_match = pat.command_match.as_str();
let has_success = pat.success.is_some();
let has_failure = pat.failure.is_some();
write_pattern_entry(out, cmd_match, has_success, has_failure)?;
}
Ok(())
}
fn write_all_patterns<W: Write>(out: &mut W) -> io::Result<()> {
let project_dir = std::env::current_dir()
.map(|cwd| init::project_patterns_dir(&cwd))
.ok();
let user_dir = learn::patterns_dir();
writeln!(out, "Built-in ({} patterns):", pattern::builtins().len())?;
write_builtins(out)?;
writeln!(out)?;
if let Some(ref pdir) = project_dir {
if pdir.exists() {
writeln!(out, "Project ({}):", pdir.display())?;
write_patterns_in(out, pdir)?;
writeln!(out)?;
}
}
write_user_section(out, &user_dir)
}
pub fn cmd_patterns() -> i32 {
match write_all_patterns(&mut io::stdout().lock()) {
Ok(()) => 0,
Err(e) => {
eprintln!("oo: {e}");
1
}
}
}
#[cfg(test)]
#[path = "patterns_empty_tests.rs"]
mod tests;