Skip to main content

unity_solution_generator/
io.rs

1use std::fs::{File, OpenOptions};
2use std::io::{Read, Write};
3use std::path::Path;
4
5use crate::error::{Result, io_err};
6
7pub fn read_file(path: &str) -> Result<String> {
8    let mut f = File::open(path).map_err(|e| io_err(path, e))?;
9    let mut s = String::new();
10    f.read_to_string(&mut s).map_err(|e| io_err(path, e))?;
11    Ok(s)
12}
13
14/// Read a file as bytes; used for byte-equality fast path in `write_file_if_changed`.
15fn read_bytes(path: &str) -> std::io::Result<Vec<u8>> {
16    let mut f = File::open(path)?;
17    let mut buf = Vec::new();
18    f.read_to_end(&mut buf)?;
19    Ok(buf)
20}
21
22/// Returns true if the file was written, false if it was already byte-identical.
23pub fn write_file_if_changed(path: &str, content: &str) -> Result<bool> {
24    let bytes = content.as_bytes();
25    if let Ok(existing) = read_bytes(path) {
26        if existing.as_slice() == bytes {
27            return Ok(false);
28        }
29    }
30    let mut f = OpenOptions::new()
31        .create(true)
32        .write(true)
33        .truncate(true)
34        .open(path)
35        .map_err(|e| io_err(path, e))?;
36    f.write_all(bytes).map_err(|e| io_err(path, e))?;
37    Ok(true)
38}
39
40pub fn create_dir_all(path: &str) {
41    let _ = std::fs::create_dir_all(path);
42}
43
44pub fn file_exists(path: &str) -> bool {
45    Path::new(path).exists()
46}
47
48pub fn is_dir(path: &str) -> bool {
49    std::fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false)
50}
51
52/// Look for a `# version: N` line near the top of a cache file and return
53/// `true` if it parses to exactly `expected`. Older caches without the line,
54/// caches with a newer/older `N`, and malformed lines all return `false` so
55/// the caller can silently regenerate. Stops scanning at the first non-comment
56/// line to keep the cost bounded.
57pub fn has_matching_version(content: &str, expected: u32) -> bool {
58    for line in content.split('\n') {
59        if line.is_empty() {
60            continue;
61        }
62        if !line.starts_with('#') {
63            // Reached cache body without finding a version line.
64            return false;
65        }
66        if let Some(rest) = line.strip_prefix("# version:") {
67            return rest.trim().parse::<u32>().ok() == Some(expected);
68        }
69    }
70    false
71}
72
73/// Plain `readdir` returning names (excluding `.` and `..`). Order undefined.
74pub fn list_directory(path: &str) -> Vec<String> {
75    let Ok(rd) = std::fs::read_dir(path) else {
76        return Vec::new();
77    };
78    rd.filter_map(|e| e.ok().map(|e| e.file_name().to_string_lossy().into_owned()))
79        .collect()
80}