Skip to main content

skilllite_fs/
read_write.rs

1//! 读写原语:read_file, write_file, append_file, atomic_write, search_replace
2
3use std::path::Path;
4
5use anyhow::{Context, Result};
6
7use crate::dir;
8
9/// 读取文件为 UTF-8 字符串
10pub fn read_file(path: &Path) -> Result<String> {
11    std::fs::read_to_string(path)
12        .with_context(|| format!("Failed to read file: {}", path.display()))
13}
14
15/// 读取文件为原始字节
16pub fn read_bytes(path: &Path) -> Result<Vec<u8>> {
17    std::fs::read(path).with_context(|| format!("Failed to read file: {}", path.display()))
18}
19
20/// 读取文件前 `limit` 字节(用于二进制检测等)
21pub fn read_bytes_limit(path: &Path, limit: usize) -> Result<Vec<u8>> {
22    use std::io::Read;
23    let mut f = std::fs::File::open(path)
24        .with_context(|| format!("Failed to open file: {}", path.display()))?;
25    let mut buf = vec![0u8; limit];
26    let n = f.read(&mut buf)?;
27    buf.truncate(n);
28    Ok(buf)
29}
30
31/// 写入文件(覆盖),UTF-8
32pub fn write_file(path: &Path, content: &str) -> Result<()> {
33    if let Some(parent) = path.parent() {
34        dir::create_dir_all(parent)?;
35    }
36    std::fs::write(path, content)
37        .with_context(|| format!("Failed to write file: {}", path.display()))
38}
39
40/// 追加写入文件
41pub fn append_file(path: &Path, content: &str) -> Result<()> {
42    if let Some(parent) = path.parent() {
43        dir::create_dir_all(parent)?;
44    }
45    use std::io::Write;
46    let mut f = std::fs::OpenOptions::new()
47        .create(true)
48        .append(true)
49        .open(path)
50        .with_context(|| format!("Failed to open file for append: {}", path.display()))?;
51    f.write_all(content.as_bytes())
52        .with_context(|| format!("Failed to append to file: {}", path.display()))?;
53    Ok(())
54}
55
56/// 原子写入:先写 .tmp 再 rename
57pub fn atomic_write(path: &Path, content: &str) -> Result<()> {
58    if let Some(parent) = path.parent() {
59        dir::create_dir_all(parent)?;
60    }
61    let tmp = path.with_extension("tmp");
62    std::fs::write(&tmp, content)
63        .with_context(|| format!("Failed to write temp file: {}", tmp.display()))?;
64    dir::rename(&tmp, path)?;
65    Ok(())
66}
67
68/// 在文件内做精确 search_replace,返回替换次数
69pub fn search_replace(
70    path: &Path,
71    old_string: &str,
72    new_string: &str,
73    replace_all: bool,
74) -> Result<usize> {
75    let content = read_file(path)?;
76    let (new_content, count) =
77        crate::search_replace::apply_search_replace(&content, old_string, new_string, replace_all)?;
78    if count > 0 {
79        write_file(path, &new_content)?;
80    }
81    Ok(count)
82}