1#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum SeatWrite {
6 Lesson(String),
7 Preference(String),
8 Accept(String),
9}
10
11#[must_use]
13pub fn admit_seat_write(text: &str) -> Option<SeatWrite> {
14 let last = text.trim().lines().last()?.trim();
15 let lower = last.to_ascii_lowercase();
16 if let Some(rest) = strip_prefix_ci(&lower, last, "accept:") {
17 let id = rest
18 .trim()
19 .trim_matches(|c: char| c == ':' || c.is_whitespace());
20 if id.len() >= 4 {
21 return Some(SeatWrite::Accept(id.to_string()));
22 }
23 return None;
24 }
25 match claim_from_user(text)? {
26 ("lesson", claim) => Some(SeatWrite::Lesson(claim)),
27 ("preference", claim) => Some(SeatWrite::Preference(claim)),
28 _ => None,
29 }
30}
31
32pub fn claim_from_user(text: &str) -> Option<(&'static str, String)> {
34 let last = text.trim().lines().last()?.trim();
35 if last.ends_with('?') {
36 return None;
37 }
38 let lower = last.to_ascii_lowercase();
39 #[allow(clippy::question_mark)]
42 let (kind, rest) = if let Some(r) = strip_prefix_ci(&lower, last, "remember:") {
43 ("lesson", r)
44 } else if let Some(r) = strip_prefix_ci(&lower, last, "from now on:") {
45 ("habit", r)
46 } else if let Some(r) = strip_prefix_ci(&lower, last, "prefer:") {
47 ("preference", r)
48 } else {
49 return None;
50 };
51 let claim = rest
52 .trim_start_matches([':', ' ', ','])
53 .trim()
54 .trim_end_matches(['.', ',', ';', ':']);
55 if claim.len() < 8 {
56 return None;
57 }
58 Some((kind, claim.to_string()))
59}
60
61fn strip_prefix_ci<'a>(lower: &str, orig: &'a str, prefix: &str) -> Option<&'a str> {
62 if lower.starts_with(prefix) {
63 Some(&orig[prefix.len()..])
64 } else {
65 None
66 }
67}
68
69pub fn is_tool_dump(text: &str) -> bool {
71 let t = text.trim();
72 if t.is_empty() {
73 return true;
74 }
75 let lower = t.to_ascii_lowercase();
76 if lower.contains("```") && (lower.contains("stdout") || lower.contains("stderr")) {
77 return true;
78 }
79 if lower.starts_with("<!doctype") || lower.starts_with("<html") {
80 return true;
81 }
82 let lines: Vec<&str> = t.lines().collect();
83 if lines.len() >= 8
84 && lines
85 .iter()
86 .filter(|l| l.starts_with('-') || l.starts_with("drwx") || l.starts_with("total "))
87 .count()
88 >= 6
89 {
90 return true;
91 }
92 false
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn remember_line() {
101 let (k, c) = claim_from_user("Remember: always pin the review set").unwrap();
102 assert_eq!(k, "lesson");
103 assert!(c.contains("pin the review set"));
104 }
105
106 #[test]
107 fn note_that_and_remember_that_are_not_claims() {
108 assert!(claim_from_user("Note that the test failed on line 12").is_none());
109 assert!(claim_from_user("Remember that: pin the review set").is_none());
110 assert!(claim_from_user("Prefer conventional commits").is_none());
111 assert!(claim_from_user("From now on, file a ticket first").is_none());
112 }
113
114 #[test]
115 fn seat_write_is_remember_prefer_or_accept() {
116 assert!(matches!(
117 admit_seat_write("Remember: pin the review set"),
118 Some(SeatWrite::Lesson(_))
119 ));
120 assert!(matches!(
121 admit_seat_write("Prefer: conventional commits always"),
122 Some(SeatWrite::Preference(_))
123 ));
124 assert!(matches!(
125 admit_seat_write("Accept: ab12cd"),
126 Some(SeatWrite::Accept(id)) if id == "ab12cd"
127 ));
128 assert!(admit_seat_write("From now on: file a ticket first").is_none());
129 assert!(admit_seat_write("The user lives in Berlin and likes tea.").is_none());
130 }
131
132 #[test]
133 fn listing_is_dump() {
134 let blob = (0..8)
135 .map(|i| format!("- file{i}.rs"))
136 .collect::<Vec<_>>()
137 .join("\n");
138 assert!(is_tool_dump(&blob));
139 assert!(!is_tool_dump("Remember: keep the habit."));
140 }
141}