Skip to main content

oxidant/
utils.rs

1// src/utils.rs
2use std::path::Path;
3
4use anyhow::{Context, Result};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct FileStats {
8    pub lines: usize,
9    pub words: usize,
10    pub bytes: u64,
11}
12
13/// Compatibility shim delegating to `crate::platform::read_file`, which new code should call.
14pub async fn read_file(path: impl AsRef<Path>) -> Result<String> {
15    crate::platform::read_file(path).await
16}
17
18fn count_lines_and_words(path: &Path) -> Result<(usize, usize)> {
19    use std::io::{BufRead, BufReader};
20    let file = std::fs::File::open(path)
21        .with_context(|| format!("Failed to open file '{}'", path.display()))?;
22    let reader = BufReader::new(file);
23    let mut lines = 0usize;
24    let mut words = 0usize;
25    for line in reader.lines() {
26        let line =
27            line.with_context(|| format!("Failed to read line from '{}'", path.display()))?;
28        lines += 1;
29        words += line.split_whitespace().count();
30    }
31    Ok((lines, words))
32}
33
34pub async fn file_stats(path: impl AsRef<Path>) -> Result<FileStats> {
35    let path = path.as_ref();
36
37    #[cfg(not(target_arch = "wasm32"))]
38    let bytes = tokio::fs::metadata(path)
39        .await
40        .with_context(|| format!("Failed to read metadata for '{}'", path.display()))?
41        .len();
42
43    #[cfg(target_arch = "wasm32")]
44    let bytes = std::fs::metadata(path)
45        .with_context(|| format!("Failed to read metadata for '{}'", path.display()))?
46        .len();
47
48    let path_owned = path.to_path_buf();
49
50    #[cfg(not(target_arch = "wasm32"))]
51    let (lines, words) = tokio::task::spawn_blocking(move || count_lines_and_words(&path_owned))
52        .await
53        .context("Blocking task panicked")??;
54
55    #[cfg(target_arch = "wasm32")]
56    let (lines, words) = count_lines_and_words(&path_owned)?;
57
58    Ok(FileStats {
59        lines,
60        words,
61        bytes,
62    })
63}