refactor: cleanup
All checks were successful
Rust / build (map[name:stable]) (push) Successful in 1m58s
Rust / build (map[name:nightly]) (push) Successful in 3m48s
Release Please / Release-plz (push) Successful in 35s

This commit is contained in:
Paul Campbell 2024-11-02 20:52:30 +00:00
parent 76d75cabd9
commit fa4232de6b
9 changed files with 159 additions and 46 deletions

View file

@ -2,11 +2,11 @@
use crate::fs::{DirItem, DirItemIterator, Result}; use crate::fs::{DirItem, DirItemIterator, Result};
use super::{ use super::{
path::{DirG, PathReal}, path::{DirMarker, PathReal},
FileG, PathG, Error, FileMarker, PathMarker,
}; };
impl<'base, 'path> PathReal<'base, 'path, DirG> { impl<'base, 'path> PathReal<'base, 'path, DirMarker> {
/// Creates a new, empty directory at the path /// Creates a new, empty directory at the path
/// ///
/// Wrapper for [std::fs::create_dir] /// Wrapper for [std::fs::create_dir]
@ -22,7 +22,7 @@ impl<'base, 'path> PathReal<'base, 'path, DirG> {
/// ``` /// ```
pub fn create(&self) -> Result<()> { pub fn create(&self) -> Result<()> {
self.check_error()?; self.check_error()?;
std::fs::create_dir(self.as_pathbuf()).map_err(Into::into) std::fs::create_dir(self.as_pathbuf()).map_err(Error::Io)
} }
/// Recursively create a directory and all of its parent components if they are missing. /// Recursively create a directory and all of its parent components if they are missing.
@ -40,7 +40,7 @@ impl<'base, 'path> PathReal<'base, 'path, DirG> {
/// ``` /// ```
pub fn create_all(&self) -> Result<()> { pub fn create_all(&self) -> Result<()> {
self.check_error()?; self.check_error()?;
std::fs::create_dir_all(self.as_pathbuf()).map_err(Into::into) std::fs::create_dir_all(self.as_pathbuf()).map_err(Error::Io)
} }
/// Returns an iterator over the entries within a directory. /// Returns an iterator over the entries within a directory.
@ -58,14 +58,18 @@ impl<'base, 'path> PathReal<'base, 'path, DirG> {
/// ``` /// ```
pub fn read(&self) -> Result<Box<dyn Iterator<Item = Result<DirItem>>>> { pub fn read(&self) -> Result<Box<dyn Iterator<Item = Result<DirItem>>>> {
self.check_error()?; self.check_error()?;
let read_dir = std::fs::read_dir(self.as_pathbuf())?; let read_dir = std::fs::read_dir(self.as_pathbuf()).map_err(Error::Io)?;
Ok(Box::new(DirItemIterator::new(read_dir))) Ok(Box::new(DirItemIterator::new(read_dir)))
} }
} }
impl<'base, 'path> TryFrom<PathReal<'base, 'path, PathG>> for PathReal<'base, 'path, FileG> { impl<'base, 'path> TryFrom<PathReal<'base, 'path, PathMarker>>
for PathReal<'base, 'path, FileMarker>
{
type Error = crate::fs::Error; type Error = crate::fs::Error;
fn try_from(path: PathReal<'base, 'path, PathG>) -> std::result::Result<Self, Self::Error> { fn try_from(
path: PathReal<'base, 'path, PathMarker>,
) -> std::result::Result<Self, Self::Error> {
match path.as_file() { match path.as_file() {
Ok(Some(dir)) => Ok(dir.clone()), Ok(Some(dir)) => Ok(dir.clone()),
Ok(None) => Err(crate::fs::Error::NotADirectory { Ok(None) => Err(crate::fs::Error::NotADirectory {
@ -75,10 +79,14 @@ impl<'base, 'path> TryFrom<PathReal<'base, 'path, PathG>> for PathReal<'base, 'p
} }
} }
} }
impl<'base, 'path> TryFrom<PathReal<'base, 'path, PathG>> for PathReal<'base, 'path, DirG> { impl<'base, 'path> TryFrom<PathReal<'base, 'path, PathMarker>>
for PathReal<'base, 'path, DirMarker>
{
type Error = crate::fs::Error; type Error = crate::fs::Error;
fn try_from(path: PathReal<'base, 'path, PathG>) -> std::result::Result<Self, Self::Error> { fn try_from(
path: PathReal<'base, 'path, PathMarker>,
) -> std::result::Result<Self, Self::Error> {
match path.as_dir() { match path.as_dir() {
Ok(Some(dir)) => Ok(dir.clone()), Ok(Some(dir)) => Ok(dir.clone()),
Ok(None) => Err(crate::fs::Error::NotADirectory { Ok(None) => Err(crate::fs::Error::NotADirectory {

View file

@ -4,6 +4,8 @@ use std::{
path::PathBuf, path::PathBuf,
}; };
use super::Error;
/// Represents an item in a directory /// Represents an item in a directory
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum DirItem { pub enum DirItem {
@ -26,8 +28,8 @@ impl Iterator for DirItemIterator {
} }
fn map_dir_item(item: std::io::Result<DirEntry>) -> super::Result<DirItem> { fn map_dir_item(item: std::io::Result<DirEntry>) -> super::Result<DirItem> {
let item = item?; let item = item.map_err(Error::Io)?;
let file_type = item.file_type()?; let file_type = item.file_type().map_err(Error::Io)?;
if file_type.is_dir() { if file_type.is_dir() {
Ok(DirItem::Dir(item.path())) Ok(DirItem::Dir(item.path()))
} else if file_type.is_file() { } else if file_type.is_file() {

View file

@ -2,11 +2,12 @@
use crate::fs::Result; use crate::fs::Result;
use super::{ use super::{
path::{FileG, PathReal}, path::{FileMarker, PathReal},
reader::Reader, reader::Reader,
Error,
}; };
impl<'base, 'path> PathReal<'base, 'path, FileG> { impl<'base, 'path> PathReal<'base, 'path, FileMarker> {
/// Returns a [Reader] for the file. /// Returns a [Reader] for the file.
/// ///
/// ``` /// ```
@ -38,6 +39,6 @@ impl<'base, 'path> PathReal<'base, 'path, FileG> {
/// ``` /// ```
pub fn write<C: AsRef<[u8]>>(&self, contents: C) -> Result<()> { pub fn write<C: AsRef<[u8]>>(&self, contents: C) -> Result<()> {
self.check_error()?; self.check_error()?;
std::fs::write(self.as_pathbuf(), contents).map_err(Into::into) std::fs::write(self.as_pathbuf(), contents).map_err(Error::Io)
} }
} }

View file

@ -1,10 +1,31 @@
/// Provides an injectable reference to part of the filesystem. //! Provides an injectable reference to part of the filesystem.
/// //!
/// Create a new `FileSystem` to access a directory using `kxio::fs::new(path)`. //! Create a new `FileSystem` to access a directory using `kxio::fs::new(path)`.
/// Create a new `TempFileSystem` to access a temporary directory using `kxio::fs::temp()?`; //! Create a new `TempFileSystem` to access a temporary directory using `kxio::fs::temp()?`;
/// //!
/// `TempFileSystem` derefs automaticalyl to `FileSystem` so can be used anywhere //! `TempFileSystem` derefs automaticalyl to `FileSystem` so can be used anywhere
/// you would use `FileSystem`. //! you would use `FileSystem`.
//!
//! ```
//! # use std::path::PathBuf;
//! # use kxio::fs::FileSystem;
//! # use kxio::fs::PathReal;
//! # use kxio::fs::DirHandle;
//! # use kxio::fs::FileHandle;
//! # fn try_main() -> kxio::fs::Result<()> {
//! let fs = kxio::fs::temp()?;
//! let fs: FileSystem = kxio::fs::new(fs.base().to_path_buf());
//! let dir_path: PathBuf = fs.base().join("foo");
//! let dir: DirHandle = fs.dir(&dir_path);
//! dir.create()?;
//! let file_path = dir_path.join("bar.txt");
//! let file: FileHandle = fs.file(&file_path);
//! file.write("new file contents")?;
//! let reader = file.reader()?;
//! assert_eq!(reader.to_string(), "new file contents");
//! # Ok(())
//! # }
//! ```
use std::path::PathBuf; use std::path::PathBuf;
mod dir; mod dir;
@ -20,7 +41,7 @@ pub use dir_item::{DirItem, DirItemIterator};
pub use path::*; pub use path::*;
pub use reader::Reader; pub use reader::Reader;
pub use result::{Error, Result}; pub use result::{Error, Result};
pub use system::FileSystem; pub use system::{DirHandle, FileHandle, FileSystem, PathHandle};
/// Creates a new `FileSystem` for the path. /// Creates a new `FileSystem` for the path.
/// ///

View file

@ -6,19 +6,23 @@ use std::{
use crate::fs::{Error, Result}; use crate::fs::{Error, Result};
/// Marker trait for the type of [PathReal].
pub trait PathType {} pub trait PathType {}
/// Path marker for the type of [PathReal].
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PathG; pub struct PathMarker;
impl PathType for PathG {} impl PathType for PathMarker {}
/// File marker for the type of [PathReal].
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FileG; pub struct FileMarker;
impl PathType for FileG {} impl PathType for FileMarker {}
/// Dir marker for the type of [PathReal].
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DirG; pub struct DirMarker;
impl PathType for DirG {} impl PathType for DirMarker {}
/// Represents a path in the filesystem. /// Represents a path in the filesystem.
/// ///
@ -63,7 +67,7 @@ impl<'base, 'path, T: PathType> PathReal<'base, 'path, T> {
} }
fn validate(base: &Path, path: &Path) -> Option<Error> { fn validate(base: &Path, path: &Path) -> Option<Error> {
match PathReal::<PathG>::clean_path(path) { match PathReal::<PathMarker>::clean_path(path) {
Err(error) => Some(error), Err(error) => Some(error),
Ok(path) => { Ok(path) => {
if !path.starts_with(base) { if !path.starts_with(base) {
@ -166,7 +170,7 @@ impl<'base, 'path, T: PathType> PathReal<'base, 'path, T> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn as_dir(&self) -> Result<Option<PathReal<'base, 'path, DirG>>> { pub fn as_dir(&self) -> Result<Option<PathReal<'base, 'path, DirMarker>>> {
self.check_error()?; self.check_error()?;
if self.as_pathbuf().is_dir() { if self.as_pathbuf().is_dir() {
Ok(Some(PathReal::new(self.base, self.path))) Ok(Some(PathReal::new(self.base, self.path)))
@ -189,7 +193,7 @@ impl<'base, 'path, T: PathType> PathReal<'base, 'path, T> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn as_file(&self) -> Result<Option<PathReal<'base, 'path, FileG>>> { pub fn as_file(&self) -> Result<Option<PathReal<'base, 'path, FileMarker>>> {
self.check_error()?; self.check_error()?;
if self.as_pathbuf().is_file() { if self.as_pathbuf().is_file() {
Ok(Some(PathReal::new(self.base, self.path))) Ok(Some(PathReal::new(self.base, self.path)))
@ -198,8 +202,8 @@ impl<'base, 'path, T: PathType> PathReal<'base, 'path, T> {
} }
} }
} }
impl From<PathReal<'_, '_, PathG>> for PathBuf { impl From<PathReal<'_, '_, PathMarker>> for PathBuf {
fn from(path: PathReal<PathG>) -> Self { fn from(path: PathReal<PathMarker>) -> Self {
path.base.join(path.path) path.base.join(path.path)
} }
} }

View file

@ -3,13 +3,15 @@ use std::{fmt::Display, path::Path, str::Lines};
use crate::fs::Result; use crate::fs::Result;
use super::Error;
/// A reader for a file. /// A reader for a file.
pub struct Reader { pub struct Reader {
contents: String, contents: String,
} }
impl Reader { impl Reader {
pub(super) fn new(path: &Path) -> Result<Self> { pub(super) fn new(path: &Path) -> Result<Self> {
let contents = std::fs::read_to_string(path)?; let contents = std::fs::read_to_string(path).map_err(Error::Io)?;
Ok(Self { contents }) Ok(Self { contents })
} }

View file

@ -1,14 +1,12 @@
// //
use std::path::PathBuf; use std::path::PathBuf;
use derive_more::From;
/// Represents a error accessing the file system. /// Represents a error accessing the file system.
/// ///
/// Any failure is related to `std::io`, a Path Traversal /// Any failure is related to `std::io`, a Path Traversal
/// (i.e. trying to escape the base of the `FileSystem`), /// (i.e. trying to escape the base of the `FileSystem`),
/// or attempting to use a file as a directory or /vise versa/. /// or attempting to use a file as a directory or /vise versa/.
#[derive(Debug, From, derive_more::Display)] #[derive(Debug, derive_more::Display)]
pub enum Error { pub enum Error {
Io(std::io::Error), Io(std::io::Error),
@ -24,6 +22,11 @@ pub enum Error {
NotADirectory { NotADirectory {
path: PathBuf, path: PathBuf,
}, },
#[display("Path must be a file: {path:?}")]
NotAFile {
path: PathBuf,
},
} }
impl std::error::Error for Error {} impl std::error::Error for Error {}
impl Clone for Error { impl Clone for Error {
@ -36,6 +39,7 @@ impl Clone for Error {
path: path.clone(), path: path.clone(),
}, },
Error::NotADirectory { path } => Error::NotADirectory { path: path.clone() }, Error::NotADirectory { path } => Error::NotADirectory { path: path.clone() },
Error::NotAFile { path } => Error::NotAFile { path: path.clone() },
} }
} }
} }

View file

@ -3,22 +3,34 @@ use std::path::{Path, PathBuf};
use crate::fs::{Error, Result}; use crate::fs::{Error, Result};
use super::path::{DirG, FileG, PathG, PathReal}; use super::path::{DirMarker, FileMarker, PathMarker, PathReal};
/// Represents to base of a section of a file system. /// Represents to base of a section of a file system.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FileSystem { pub struct FileSystem {
base: PathBuf, base: PathBuf,
} }
/// Represents a directory path in the filesystem.
pub type DirHandle<'base, 'path> = PathReal<'base, 'path, DirMarker>;
/// Represents a file path in the filesystem.
pub type FileHandle<'base, 'path> = PathReal<'base, 'path, FileMarker>;
/// Represents a path in the filesystem.
pub type PathHandle<'base, 'path> = PathReal<'base, 'path, PathMarker>;
impl FileSystem { impl FileSystem {
pub const fn new(base: PathBuf) -> Self { pub const fn new(base: PathBuf) -> Self {
Self { base } Self { base }
} }
/// Returns the base of the [FileSystem].
pub fn base(&self) -> &Path { pub fn base(&self) -> &Path {
&self.base &self.base
} }
/// Returns a [PathBuf] for the path.
pub fn path_of(&self, path: PathBuf) -> Result<PathBuf> { pub fn path_of(&self, path: PathBuf) -> Result<PathBuf> {
let path_of = self.base.as_path().join(path); let path_of = self.base.as_path().join(path);
self.validate(&path_of)?; self.validate(&path_of)?;
@ -26,7 +38,21 @@ impl FileSystem {
} }
/// Access the path as a directory. /// Access the path as a directory.
pub fn dir<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path, DirG> { ///
/// The path must exist and be a directory.
///
/// If the path does not exist, or is not a directory, an error is returned.
///
/// ```
/// # fn try_main() -> kxio::fs::Result<()> {
/// let fs = kxio::fs::temp()?;
/// let path = fs.base().join("foo");
/// let dir = fs.dir(&path);
/// dir.create()?;
/// # Ok(())
/// # }
/// ```
pub fn dir<'base, 'path>(&'base self, path: &'path Path) -> DirHandle<'base, 'path> {
let mut dir = PathReal::new(self.base(), path); let mut dir = PathReal::new(self.base(), path);
if dir.error.is_none() { if dir.error.is_none() {
@ -46,11 +72,56 @@ impl FileSystem {
dir dir
} }
pub fn file<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path, FileG> { /// Access the path as a file.
PathReal::new(self.base(), path) ///
/// The path must exist and be a file.
///
/// If the path does not exist, or is not a file, an error is returned.
///
/// ```
/// # fn try_main() -> kxio::fs::Result<()> {
/// let fs = kxio::fs::temp()?;
/// let path = fs.base().join("foo");
/// let file = fs.file(&path);
/// file.write("new file contents")?;
/// # Ok(())
/// # }
/// ```
pub fn file<'base, 'path>(&'base self, path: &'path Path) -> FileHandle<'base, 'path> {
let mut file = PathReal::new(self.base(), path);
if file.error.is_none() {
if let Ok(exists) = file.exists() {
if exists {
if let Ok(is_file) = file.is_file() {
if !is_file {
file.put(Error::NotAFile {
path: file.as_pathbuf(),
})
}
}
}
}
}
file
} }
pub fn path<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path, PathG> { /// Access the path as a path.
///
/// The path must exist.
///
/// If the path does not exist, an error is returned.
///
/// ```
/// # fn try_main() -> kxio::fs::Result<()> {
/// let fs = kxio::fs::temp()?;
/// let path = fs.base().join("foo");
/// let path_handle = fs.path(&path);
/// # Ok(())
/// # }
/// ```
pub fn path<'base, 'path>(&'base self, path: &'path Path) -> PathHandle<'base, 'path> {
PathReal::new(self.base(), path) PathReal::new(self.base(), path)
} }
@ -71,7 +142,7 @@ impl FileSystem {
let abs_path = if path.is_absolute() { let abs_path = if path.is_absolute() {
path.to_path_buf() path.to_path_buf()
} else { } else {
std::env::current_dir()?.join(path) std::env::current_dir().map_err(Error::Io)?.join(path)
} }
.clean(); .clean();
Ok(abs_path) Ok(abs_path)

View file

@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex};
use tempfile::TempDir; use tempfile::TempDir;
use super::FileSystem; use super::{Error, FileSystem};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct TempFileSystem { pub struct TempFileSystem {
@ -11,7 +11,7 @@ pub struct TempFileSystem {
} }
impl TempFileSystem { impl TempFileSystem {
pub fn new() -> super::Result<Self> { pub fn new() -> super::Result<Self> {
let temp_dir = tempfile::tempdir()?; let temp_dir = tempfile::tempdir().map_err(Error::Io)?;
let base = temp_dir.path().to_path_buf(); let base = temp_dir.path().to_path_buf();
let temp_dir = Arc::new(Mutex::new(temp_dir)); let temp_dir = Arc::new(Mutex::new(temp_dir));
let real = super::new(base); let real = super::new(base);