use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct FileIO {
path: PathBuf,
}
impl FileIO {
pub fn new(path: &str) -> Self {
Self { path: PathBuf::from(path) }
}
pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
Self { path: path.as_ref().to_path_buf() }
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn exists(&self) -> bool {
self.path.exists()
}
pub fn create_if_missing(&self) -> io::Result<()> {
if !self.exists() {
OpenOptions::new().create(true).write(true).open(&self.path)?;
}
Ok(())
}
pub fn read_all(&self) -> io::Result<String> {
std::fs::read_to_string(&self.path)
}
pub fn read_lines(&self) -> io::Result<Vec<String>> {
let file = File::open(&self.path)?;
let reader = BufReader::new(file);
reader.lines().collect()
}
pub fn read_non_empty_lines(&self) -> io::Result<Vec<String>> {
Ok(self
.read_lines()?
.into_iter()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect())
}
pub fn count_lines(&self) -> io::Result<usize> {
let file = File::open(&self.path)?;
let reader = BufReader::new(file);
Ok(reader.lines().count())
}
pub fn is_empty(&self) -> io::Result<bool> {
match File::open(&self.path) {
Ok(mut f) => {
use std::io::Read;
let mut buf = [0u8; 1];
Ok(f.read(&mut buf).unwrap_or(0) == 0)
}
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(true),
Err(e) => Err(e),
}
}
pub fn write(&self, content: &str) -> io::Result<()> {
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(&self.path)?;
let mut writer = BufWriter::new(file);
writer.write_all(content.as_bytes())
}
pub fn write_lines<I, S>(&self, lines: I) -> io::Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut first = true;
let mut joined = String::new();
for line in lines {
if !first { joined.push('\n'); }
first = false;
joined.push_str(line.as_ref());
}
self.write(&joined)
}
pub fn append(&self, content: &str) -> io::Result<()> {
let file = OpenOptions::new()
.append(true)
.create(true)
.open(&self.path)?;
let mut writer = BufWriter::new(file);
writeln!(writer, "{}", content)?;
Ok(())
}
pub fn append_lines<I, S>(&self, lines: I) -> io::Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let file = OpenOptions::new()
.append(true)
.create(true)
.open(&self.path)?;
let mut writer = BufWriter::new(file);
for line in lines {
writeln!(writer, "{}", line.as_ref())?;
}
Ok(())
}
pub fn write_line(&self, line_number: usize, content: &str) -> io::Result<()> {
if line_number == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"line_number must be >= 1",
));
}
let mut lines = self.read_lines().unwrap_or_default();
if line_number > lines.len() {
lines.resize(line_number, String::new());
}
lines[line_number - 1] = content.to_string();
self.write(&lines.join("\n"))
}
pub fn insert_line(&self, line_number: usize, content: &str) -> io::Result<()> {
if line_number == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"line_number must be >= 1",
));
}
let mut lines = self.read_lines().unwrap_or_default();
if line_number > lines.len() + 1 {
lines.resize(line_number - 1, String::new());
}
lines.insert(line_number - 1, content.to_string());
self.write(&lines.join("\n"))
}
pub fn insert_lines<I, S>(&self, line_number: usize, new_lines: I) -> io::Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
if line_number == 0 {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "line_number must be >= 1"));
}
let mut lines = self.read_lines().unwrap_or_default();
if line_number > lines.len() + 1 {
lines.resize(line_number - 1, String::new());
}
let mut idx = line_number - 1;
for l in new_lines {
lines.insert(idx, l.as_ref().to_string());
idx += 1;
}
self.write(&lines.join("\n"))
}
pub fn remove_line(&self, line_number: usize) -> io::Result<()> {
if line_number == 0 { return Err(io::Error::new(io::ErrorKind::InvalidInput, "line_number must be >= 1")); }
let mut lines = self.read_lines().unwrap_or_default();
if line_number <= lines.len() {
lines.remove(line_number - 1);
}
self.write(&lines.join("\n"))
}
pub fn remove_lines(&self, start: usize, end: usize) -> io::Result<()> {
if start == 0 || end == 0 { return Err(io::Error::new(io::ErrorKind::InvalidInput, "start/end must be >= 1")); }
let mut lines = self.read_lines().unwrap_or_default();
let (mut s, mut e) = (start, end);
if s > e { std::mem::swap(&mut s, &mut e); }
if s <= e && s <= lines.len() {
let s0 = s - 1;
let e0 = e.min(lines.len()) - 1;
lines.drain(s0..=e0);
}
self.write(&lines.join("\n"))
}
pub fn read_range(&self, start: usize, end: usize) -> io::Result<Vec<String>> {
if start == 0 || end == 0 { return Err(io::Error::new(io::ErrorKind::InvalidInput, "start/end must be >= 1")); }
let lines = self.read_lines().unwrap_or_default();
let (mut s, mut e) = (start, end);
if s > e { std::mem::swap(&mut s, &mut e); }
let s0 = s.saturating_sub(1);
let e0 = e.min(lines.len());
Ok(lines[s0..e0].to_vec())
}
pub fn find_replace(&self, find: &str, replace: &str) -> io::Result<()> {
let content = self.read_all().unwrap_or_default();
let updated = content.replace(find, replace);
self.write(&updated)
}
}
pub fn file(path: &str) -> FileIO {
FileIO::new(path)
}