use crate::prelude::*;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::process::ExitStatus;
#[macro_export]
macro_rules! dir {
() => {
std::path::Path::new(file!()).parent().unwrap()
};
}
pub fn current_dir() -> FsResult<PathBuf> {
#[cfg(target_arch = "wasm32")]
return js_runtime::cwd().xinto::<PathBuf>().xok();
#[cfg(not(target_arch = "wasm32"))]
return std::env::current_dir().map_err(|e| FsError::io(".", e));
}
pub fn copy_recursive(
source: impl AsRef<Path>,
destination: impl AsRef<Path>,
) -> FsResult {
let source = source.as_ref();
let destination = destination.as_ref();
fs_ext::create_dir_all(&destination)?;
for entry in ReadDir::all(source)? {
let file_name = path_ext::file_name(&entry)?;
if entry.is_dir() {
fs_ext::copy_recursive(&entry, destination.join(file_name))?;
} else {
fs::copy(&entry, destination.join(file_name))
.map_err(|err| FsError::io(entry, err))?;
}
}
Ok(())
}
pub fn exists(path: impl AsRef<Path>) -> FsResult<bool> {
let path = path.as_ref();
#[cfg(target_arch = "wasm32")]
return js_runtime::exists(&path.to_string_lossy()).xok();
#[cfg(not(target_arch = "wasm32"))]
match fs::exists(path) {
Ok(val) => Ok(val),
Err(err) => Err(FsError::io(path, err)),
}
}
pub async fn exists_async(path: impl AsRef<Path>) -> FsResult<bool> {
#[cfg(not(all(feature = "fs", not(target_arch = "wasm32"))))]
{
fs_ext::exists(path)
}
#[cfg(all(feature = "fs", not(target_arch = "wasm32")))]
{
let path = path.as_ref();
match async_fs::metadata(path).await {
Ok(_) => Ok(true),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(err) => Err(FsError::io(path, err)),
}
}
}
pub fn create_dir_all(path: impl AsRef<Path>) -> FsResult<()> {
let path = path.as_ref();
#[cfg(target_arch = "wasm32")]
return js_runtime::create_dir_all(&path.to_string_lossy()).xok();
#[cfg(not(target_arch = "wasm32"))]
return fs::create_dir_all(path).map_err(|err| FsError::io(path, err));
}
pub async fn create_dir_all_async(path: impl AsRef<Path>) -> FsResult<()> {
#[cfg(not(all(feature = "fs", not(target_arch = "wasm32"))))]
{
fs_ext::create_dir_all(path)
}
#[cfg(all(feature = "fs", not(target_arch = "wasm32")))]
{
let path = path.as_ref();
async_fs::create_dir_all(path)
.await
.map_err(|err| FsError::io(path, err))
}
}
pub fn remove(path: impl AsRef<Path>) -> FsResult {
let path = path.as_ref();
match fs::metadata(path) {
Ok(meta) => {
if meta.is_dir() {
fs::remove_dir_all(path)
.map_err(|err| FsError::io(path, err))?;
} else {
fs::remove_file(path).map_err(|err| FsError::io(path, err))?;
}
Ok(())
}
Err(err) => Err(FsError::io(path, err)),
}
}
pub async fn remove_async(path: impl AsRef<Path>) -> FsResult {
#[cfg(not(all(feature = "fs", not(target_arch = "wasm32"))))]
{
fs_ext::remove(path)
}
#[cfg(all(feature = "fs", not(target_arch = "wasm32")))]
{
let path = path.as_ref();
match async_fs::metadata(path).await {
Ok(meta) => {
if meta.is_dir() {
async_fs::remove_dir_all(path)
.await
.map_err(|err| FsError::io(path, err))?;
} else {
async_fs::remove_file(path)
.await
.map_err(|err| FsError::io(path, err))?;
}
Ok(())
}
Err(err) => Err(FsError::io(path, err)),
}
}
}
pub fn workspace_root() -> PathBuf { crate::prelude::workspace_root() }
pub fn read(path: impl AsRef<Path>) -> FsResult<Vec<u8>> {
#[cfg(target_arch = "wasm32")]
return js_runtime::read_file(&path.as_ref().to_string_lossy())
.ok_or_else(|| FsError::file_not_found(path.as_ref()));
#[cfg(not(target_arch = "wasm32"))]
return std::fs::read(&path).map_err(|e| FsError::io(path, e));
}
pub async fn read_async(path: impl AsRef<Path>) -> FsResult<Vec<u8>> {
#[cfg(not(all(feature = "fs", not(target_arch = "wasm32"))))]
{
fs_ext::read(path)
}
#[cfg(all(feature = "fs", not(target_arch = "wasm32")))]
{
async_fs::read(&path)
.await
.map_err(|e| FsError::io(path, e))
}
}
pub fn read_to_string(path: impl AsRef<Path>) -> FsResult<String> {
fs_ext::read(path.as_ref()).and_then(|bytes| {
String::from_utf8(bytes).map_err(|e| FsError::other(path.as_ref(), e))
})
}
pub async fn read_to_string_async(path: impl AsRef<Path>) -> FsResult<String> {
#[cfg(not(all(feature = "fs", not(target_arch = "wasm32"))))]
{
fs_ext::read_to_string(path)
}
#[cfg(all(feature = "fs", not(target_arch = "wasm32")))]
{
async_fs::read_to_string(&path)
.await
.map_err(|e| FsError::io(path, e))
}
}
pub fn hash_file(path: impl AsRef<Path>) -> FsResult<u64> {
let bytes = fs_ext::read(path)?;
let hash = fs_ext::hash_bytes(&bytes);
Ok(hash)
}
pub fn hash_bytes(bytes: &[u8]) -> u64 {
let mut hasher = std::hash::DefaultHasher::new();
use std::hash::Hash;
use std::hash::Hasher;
bytes.hash(&mut hasher);
hasher.finish()
}
pub fn hash_string(str: &str) -> u64 {
let bytes = str.as_bytes();
fs_ext::hash_bytes(bytes)
}
#[extend::ext]
impl ExitStatus {
fn xresult(&self) -> bevy::prelude::Result<()> {
if self.success() {
Ok(())
} else {
bevybail!("Process exited with non-zero status: {}", self)
}
}
}
pub fn touch(path: impl AsRef<Path>) -> bevy::prelude::Result {
std::process::Command::new("touch")
.arg(path.as_ref())
.status()?
.xresult()?
.xok()
}
pub fn write(path: impl AsRef<Path>, data: impl AsRef<[u8]>) -> FsResult {
let path = path.as_ref();
if let Some(parent) = path.parent() {
fs_ext::create_dir_all(parent)?;
}
#[cfg(target_arch = "wasm32")]
{
js_runtime::write_file(&path.to_string_lossy(), data.as_ref());
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
{
fs::write(path, data).map_err(|err| FsError::io(path, err))?;
Ok(())
}
}
pub async fn write_async(
path: impl AsRef<Path>,
data: impl AsRef<[u8]>,
) -> FsResult {
#[cfg(not(all(feature = "fs", not(target_arch = "wasm32"))))]
{
fs_ext::write(path, data)
}
#[cfg(all(feature = "fs", not(target_arch = "wasm32")))]
{
let path = path.as_ref();
if let Some(parent) = path.parent() {
fs_ext::create_dir_all_async(parent).await?;
}
async_fs::write(path, data)
.await
.map_err(|err| FsError::io(path, err))?;
Ok(())
}
}
pub fn write_if_diff(
path: impl AsRef<Path>,
data: impl AsRef<[u8]>,
) -> FsResult {
let path = path.as_ref();
match fs::read(path) {
Ok(existing_data) if existing_data == data.as_ref() => {
return Ok(());
}
_ => {
fs_ext::write(path, data)?;
}
}
Ok(())
}
#[cfg(test)]
pub fn test_dir() -> PathBuf {
fs_ext::workspace_root().join(Path::new("tests/test_dir"))
}
#[cfg(test)]
mod test {
use crate::prelude::*;
#[test]
fn workspace_root() {
fs_ext::workspace_root()
.file_stem()
.unwrap()
.to_str()
.unwrap()
.xpect_eq("beet");
fs_ext::workspace_root()
.join("Cargo.lock")
.xmap(fs_ext::exists)
.unwrap()
.xpect_true();
}
#[test]
fn to_string() {
let content =
fs_ext::read_to_string(fs_ext::test_dir().join("mod.rs")).unwrap();
content.contains("pub mod included_dir;").xpect_true();
fs_ext::read_to_string(fs_ext::test_dir().join("foo.rs")).xpect_err();
}
#[test]
fn to_bytes() {
let bytes = fs_ext::read(fs_ext::test_dir().join("mod.rs")).unwrap();
bytes.len().xpect_greater_than(10);
fs_ext::read(fs_ext::test_dir().join("foo.rs")).xpect_err();
}
#[test]
fn hash() {
let hash1 =
fs_ext::hash_file(fs_ext::test_dir().join("mod.rs")).unwrap();
let hash2 =
fs_ext::hash_file(fs_ext::test_dir().join("included_file.rs"))
.unwrap();
hash1.xpect_not_eq(hash2);
let str =
fs_ext::read_to_string(fs_ext::test_dir().join("mod.rs")).unwrap();
let hash3 = fs_ext::hash_string(&str);
hash3.xpect_eq(hash1);
}
}