1use std::{error::Error, fs, io, path::Path};
4
5use crate::{StringError, bait::ResultExt, bog::BogOkExt};
6
7pub fn dump_type<'a, T, E: Error>(
13 path: impl AsRef<Path>,
14 input: &'a T,
15 string_maker: impl FnOnce(&'a T) -> Result<String, E>,
16) -> Result<(), StringError> {
17 let path = path.as_ref().with_extension("toml");
18 let type_name = std::any::type_name::<T>().rsplit("::").next().unwrap();
19 let error_prefix = format!("Failed to save {type_name} to {}", path.to_string_lossy());
20
21 let content = string_maker(input).prefix(&error_prefix)?;
22 fs::write(path, content).prefix(&error_prefix)
23}
24
25pub fn load_type<T, E: std::fmt::Display>(
27 path: impl AsRef<Path>,
28 str_loader: impl FnOnce(&str) -> Result<T, E>, ) -> Result<T, StringError> {
30 let path = path.as_ref().with_extension("toml");
31 let type_name = std::any::type_name::<T>().rsplit("::").next().unwrap();
32 let error_prefix = format!("Failed to load {type_name} from {}", path.to_string_lossy());
33
34 let mut file = fs::File::open(path).prefix(&error_prefix)?;
35
36 let mut contents = String::new();
37 io::Read::read_to_string(&mut file, &mut contents).prefix(&error_prefix)?;
38
39 str_loader(&contents).prefix(&error_prefix)
40}
41
42pub fn load_type_or_default<T: Default, E: std::fmt::Display>(
68 path: impl AsRef<Path>,
69 str_loader: impl Fn(&str) -> Result<T, E>,
70) -> T {
71 let path = path.as_ref();
72 if path.is_file() {
73 load_type(path, &str_loader)
74 .prefix("Using default config due to errors")
75 ._wbog()
76 .unwrap_or_else(T::default)
77 } else {
78 T::default()
79 }
80}
81
82pub fn write_str(path: &Path, contents: &str) -> io::Result<()> {
84 if let Some(p) = path.parent() {
85 std::fs::create_dir_all(p)?; }
87 std::fs::write(path, contents)?;
88
89 Ok(())
90}
91
92use std::io::{BufRead, Read};
95
96#[derive(Debug, thiserror::Error)]
97#[non_exhaustive]
98pub enum MapReaderError<E> {
99 #[error("Failed to read chunk {0}: {1}")]
100 ChunkError(usize, std::io::Error),
101 #[error("Aborted: {0}")]
102 Custom(#[from] E),
103}
104
105pub fn read_to_chunks<R: Read>(reader: R, delim: char) -> std::io::Split<std::io::BufReader<R>> {
107 io::BufReader::new(reader).split(delim as u8)
108}
109
110pub fn map_chunks<const INVALID_FAIL: bool, E>(
142 iter: impl Iterator<Item = std::io::Result<Vec<u8>>>,
143 mut f: impl FnMut(String) -> Result<(), E>,
144) -> Result<usize, MapReaderError<E>> {
145 let mut count = 0;
146 for (i, chunk_result) in iter.enumerate() {
147 let bytes = chunk_result.map_err(|e| MapReaderError::ChunkError(i, e))?;
148
149 match String::from_utf8(bytes) {
150 Ok(s) => {
151 if let Err(e) = f(s) {
152 return Err(MapReaderError::Custom(e));
153 } else {
154 count += 1;
155 }
156 }
157 Err(e) => {
158 let err = format!(
159 "Invalid UTF-8 in stdin at byte {}: {}",
160 e.utf8_error().valid_up_to(),
161 e
162 );
163 if INVALID_FAIL {
165 return Err(MapReaderError::ChunkError(i, std::io::Error::other(err)));
166 } else {
167 continue;
168 }
169 }
170 }
171 }
172 Ok(count)
173}
174
175pub fn map_reader_lines<const INVALID_FAIL: bool, E>(
178 reader: impl Read,
179 mut f: impl FnMut(String) -> Result<(), E>,
180) -> Result<usize, MapReaderError<E>> {
181 let buf_reader = io::BufReader::new(reader);
182 let mut count = 0;
183
184 for (i, line) in buf_reader.lines().enumerate() {
185 match line {
186 Ok(l) => {
187 if let Err(e) = f(l) {
188 return Err(MapReaderError::Custom(e));
189 } else {
190 count += 1;
191 }
192 }
193 Err(e) => {
194 if INVALID_FAIL {
195 return Err(MapReaderError::ChunkError(i, e));
196 } else {
197 continue;
198 }
199 }
200 }
201 }
202 Ok(count)
203}