double_o/
commands_patterns.rs1use std::io::{self, Write};
4use std::path::Path;
5
6use crate::{init, learn, pattern};
7
8const EMPTY_USER_PATTERNS_LINE: &str = " no learned patterns yet";
10
11fn write_pattern_entry<W: Write>(
16 out: &mut W,
17 cmd_match: &str,
18 has_success: bool,
19 has_failure: bool,
20) -> io::Result<()> {
21 if has_success || has_failure {
22 let mut flags = Vec::new();
23 if has_success {
24 flags.push("success");
25 }
26 if has_failure {
27 flags.push("failure");
28 }
29 writeln!(out, " {cmd_match} [{}]", flags.join("] ["))
30 } else {
31 writeln!(out, " {cmd_match}")
32 }
33}
34
35fn with_path(path: &Path, e: &io::Error) -> io::Error {
37 io::Error::new(e.kind(), format!("{}: {e}", path.display()))
38}
39
40fn write_patterns_in<W: Write>(out: &mut W, dir: &Path) -> io::Result<bool> {
47 let entries = match std::fs::read_dir(dir) {
48 Ok(e) => e,
49 Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(false),
50 Err(e) => return Err(with_path(dir, &e)),
51 };
52
53 let mut found = false;
54 for entry in entries {
55 let path = entry.map_err(|e| with_path(dir, &e))?.path();
56 if path.extension().and_then(|e| e.to_str()) != Some("toml") {
57 continue;
58 }
59 let meta = match std::fs::metadata(&path) {
64 Ok(m) => m,
65 Err(e) if e.kind() == io::ErrorKind::NotFound => continue,
66 Err(e) => return Err(with_path(&path, &e)),
67 };
68 if !meta.is_file() {
69 continue;
70 }
71 let text = match std::fs::read_to_string(&path) {
72 Ok(text) => text,
73 Err(e)
75 if matches!(
76 e.kind(),
77 io::ErrorKind::InvalidData | io::ErrorKind::NotFound
78 ) =>
79 {
80 continue;
81 }
82 Err(e) => return Err(with_path(&path, &e)),
83 };
84 let Ok(parsed) = toml::from_str::<toml::Value>(&text) else {
85 continue;
86 };
87 found = true;
88
89 let cmd_match = parsed
90 .get("command_match")
91 .and_then(|v| v.as_str())
92 .unwrap_or("(unknown)");
93 let has_success = parsed.get("success").is_some();
94 let has_failure = parsed.get("failure").is_some();
95
96 write_pattern_entry(out, cmd_match, has_success, has_failure)?;
97 }
98 Ok(found)
99}
100
101pub fn list_patterns_in(dir: &Path) -> bool {
107 match write_patterns_in(&mut io::stdout().lock(), dir) {
108 Ok(found) => found,
109 Err(e) => {
110 eprintln!("oo: {e}");
111 false
112 }
113 }
114}
115
116pub fn cmd_patterns_in(dir: &Path) -> i32 {
122 let result = write_patterns_in(&mut io::stdout().lock(), dir);
123 match result {
124 Ok(true) => 0,
125 Ok(false) => {
126 println!("{EMPTY_USER_PATTERNS_LINE}");
127 0
128 }
129 Err(e) => {
130 eprintln!("oo: {e}");
131 1
132 }
133 }
134}
135
136fn write_user_section<W: Write>(out: &mut W, dir: &Path) -> io::Result<()> {
138 writeln!(out, "User ({}):", dir.display())?;
139 if !write_patterns_in(out, dir)? {
140 writeln!(out, "{EMPTY_USER_PATTERNS_LINE}")?;
141 }
142 Ok(())
143}
144
145fn write_builtins<W: Write>(out: &mut W) -> io::Result<()> {
147 for pat in pattern::builtins() {
148 let cmd_match = pat.command_match.as_str();
149 let has_success = pat.success.is_some();
150 let has_failure = pat.failure.is_some();
151
152 write_pattern_entry(out, cmd_match, has_success, has_failure)?;
153 }
154 Ok(())
155}
156
157fn write_all_patterns<W: Write>(out: &mut W) -> io::Result<()> {
159 let project_dir = std::env::current_dir()
160 .map(|cwd| init::project_patterns_dir(&cwd))
161 .ok();
162 let user_dir = learn::patterns_dir();
163
164 writeln!(out, "Built-in ({} patterns):", pattern::builtins().len())?;
166 write_builtins(out)?;
167 writeln!(out)?;
168
169 if let Some(ref pdir) = project_dir {
170 if pdir.exists() {
171 writeln!(out, "Project ({}):", pdir.display())?;
172 write_patterns_in(out, pdir)?;
173 writeln!(out)?;
174 }
175 }
176
177 write_user_section(out, &user_dir)
178}
179
180pub fn cmd_patterns() -> i32 {
182 match write_all_patterns(&mut io::stdout().lock()) {
183 Ok(()) => 0,
184 Err(e) => {
185 eprintln!("oo: {e}");
186 1
187 }
188 }
189}
190
191#[cfg(test)]
192#[path = "patterns_empty_tests.rs"]
193mod tests;