use std::path::{Path, PathBuf};
use crate::error::Result;
use crate::tools::workspace::Wrote;
#[derive(Debug, Clone)]
pub struct FsTool {
path: PathBuf,
}
impl FsTool {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into() }
}
pub fn path(&self) -> &Path {
&self.path
}
pub async fn read(&self) -> Result<String> {
match tokio::fs::read_to_string(&self.path).await {
Ok(s) => Ok(s),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(String::new()),
Err(e) => Err(e.into()),
}
}
pub async fn write(&self, contents: &str) -> Result<Wrote> {
let did = Wrote::classify(tokio::fs::read(&self.path).await, contents);
if let Some(parent) = self.path.parent() {
if !parent.as_os_str().is_empty() {
tokio::fs::create_dir_all(parent).await?;
}
}
tokio::fs::write(&self.path, contents).await?;
Ok(did)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn missing_reads_empty_then_roundtrips() {
let dir = tempfile::tempdir().unwrap();
let tool = FsTool::new(dir.path().join("nested/out.txt"));
assert_eq!(tool.read().await.unwrap(), "");
tool.write("hello").await.unwrap();
assert_eq!(tool.read().await.unwrap(), "hello");
}
#[tokio::test]
async fn a_write_reports_what_it_did_to_the_file() {
let dir = tempfile::tempdir().unwrap();
let tool = FsTool::new(dir.path().join("nested/out.txt"));
assert_eq!(tool.write("one").await.unwrap(), Wrote::Created);
assert_eq!(tool.write("two!").await.unwrap(), Wrote::Changed);
assert_eq!(tool.read().await.unwrap(), "two!");
assert_eq!(tool.write("two!").await.unwrap(), Wrote::Unchanged);
assert_eq!(tool.write("owt!").await.unwrap(), Wrote::Changed);
assert_eq!(tool.read().await.unwrap(), "owt!");
assert_eq!(tool.write("héllo — 日本語").await.unwrap(), Wrote::Changed);
assert_eq!(tool.read().await.unwrap(), "héllo — 日本語");
assert_eq!(
tool.write("héllo — 日本語").await.unwrap(),
Wrote::Unchanged
);
}
}