summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/entry.rs41
-rw-r--r--src/error.rs87
-rw-r--r--src/lib.rs14
-rw-r--r--src/operations.rs31
-rw-r--r--src/operations/cp.rs65
-rw-r--r--src/operations/ls.rs41
-rw-r--r--src/operations/mkdir.rs21
-rw-r--r--src/operations/rm.rs30
8 files changed, 168 insertions, 162 deletions
diff --git a/src/entry.rs b/src/entry.rs
index 2679e42..6ea78bc 100644
--- a/src/entry.rs
+++ b/src/entry.rs
@@ -1,5 +1,5 @@
-use crate::EntryReadError;
use std::path::{Path, PathBuf};
+use crate::*;
#[derive(PartialEq)]
pub enum EntryType {
@@ -10,23 +10,30 @@ pub enum EntryType {
pub struct Entry {
pub entry_type: EntryType,
pub is_symlink: bool,
+ /// The final segment of the file path, including any file extensions.
pub name: String,
pub extension: String,
+ /// The canonical file path, with intermediate symbolic links resolved.
pub path: PathBuf,
+ /// The file path as originally presented to the [Entry] constructor.
pub original_path: PathBuf,
}
+
impl Entry {
- pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, EntryReadError> {
+ pub fn from_path(path: impl AsRef<Path>) -> ReadResult<Self> {
let path = path.as_ref();
- let metadata = path.metadata()?;
- let canonical_path = std::fs::canonicalize(path)?;
- let canonical_metadata = canonical_path.metadata()?;
+ macro_rules! raise {
+ ($err:expr) => { io_result_to_read_result($err, path)? }; }
+
+ let metadata = raise!(path.metadata());
+ let canonical_path = raise!(std::fs::canonicalize(path));
+ let canonical_metadata = raise!(canonical_path.metadata());
let entry_type = if canonical_metadata.is_file() {
EntryType::File
} else if canonical_metadata.is_dir() {
EntryType::Directory
} else {
- return Err(EntryReadError::NotFound);
+ unreachable!("Canonical metadata must describe either a file or a directory");
};
let name = match path.file_name() {
@@ -47,7 +54,11 @@ impl Entry {
})
}
- /// Splits the filename on the last period, ignoring any period at the
+ pub fn parent(&self) -> Option<Entry> {
+ Entry::from_path(self.original_path.parent()?).ok()
+ }
+
+ /// Split the filename on the last period, ignoring any period at the
/// start of the filename. If no extension is found, the extension is empty.
pub fn split_name(&self) -> (String, String) {
match self.name.rsplit_once(".") {
@@ -57,24 +68,18 @@ impl Entry {
}
pub fn is_file(&self) -> bool {
- match self.entry_type {
- EntryType::File => true,
- _ => false,
- }
+ self.entry_type == EntryType::File
}
pub fn is_directory(&self) -> bool {
- match self.entry_type {
- EntryType::Directory => true,
- _ => false,
- }
+ self.entry_type == EntryType::Directory
}
- pub fn read_as_bytes(&self) -> Result<Vec<u8>, EntryReadError> {
- return Ok(std::fs::read(&self.path)?);
+ pub fn read_as_bytes(&self) -> ReadResult<Vec<u8>> {
+ Ok(io_result_to_read_result(std::fs::read(&self.path), &self.path)?)
}
- pub fn read_as_utf8_string(&self) -> Result<String, EntryReadError> {
+ pub fn read_as_utf8(&self) -> ReadResult<String> {
return Ok(String::from_utf8_lossy(&self.read_as_bytes()?).to_string());
}
}
diff --git a/src/error.rs b/src/error.rs
index 0e8d99f..be0ebb7 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,48 +1,69 @@
-use std::io::Error as IoError;
-use std::io::ErrorKind;
+use std::io::{ErrorKind as IoErrorKind, Error as IoError};
+use crate::*;
-#[derive(Debug)]
-pub enum EntryReadError {
+#[derive(Debug, PartialEq)]
+pub enum EntryErrorKind {
NotFound,
PermissionDenied,
}
-impl From<IoError> for EntryReadError {
- fn from(io_error: IoError) -> Self {
- match io_error.kind() {
- ErrorKind::NotFound => Self::NotFound,
- // An intermediate path component was a plain file, not a directory
- ErrorKind::NotADirectory => Self::NotFound,
- // A cyclic symbolic link chain was included in the provided path
- ErrorKind::FilesystemLoop => Self::NotFound,
- ErrorKind::PermissionDenied => Self::PermissionDenied,
- err => panic!("Unexpected IoError encountered: {:?}", err),
- }
- }
+
+pub struct EntryReadError {
+ pub path: PathBuf,
+ pub error_kind: EntryErrorKind,
}
-#[derive(Debug)]
-pub enum EntryWriteError {
- NotFound,
- PermissionDenied,
+pub struct EntryWriteError {
+ pub path: PathBuf,
+ pub error_kind: EntryErrorKind,
}
+
impl From<EntryReadError> for EntryWriteError {
fn from(error: EntryReadError) -> Self {
- match error {
- EntryReadError::NotFound => EntryWriteError::NotFound,
- EntryReadError::PermissionDenied => EntryWriteError::PermissionDenied,
+ EntryWriteError { path: error.path, error_kind: error.error_kind }
+ }
+}
+
+pub(crate) fn io_result_to_read_result<T>(io_result: Result<T, IoError>, path: &Path) -> ReadResult<T> {
+ match io_result {
+ Ok(t) => Ok(t),
+ Err(io_error) => {
+ match io_error_to_entry_error(io_error) {
+ Ok(error_kind) => Err( EntryReadError { path: path.to_path_buf(), error_kind }),
+ Err(err) => panic!("Unexpected IO error while attempting to read from {path:?}: {err:?}"),
+ }
}
}
}
-impl From<IoError> for EntryWriteError {
- fn from(io_error: IoError) -> Self {
- match io_error.kind() {
- ErrorKind::NotFound => Self::NotFound,
- // An intermediate path component was a plain file, not a directory
- ErrorKind::NotADirectory => Self::NotFound,
- // A cyclic symbolic link chain was included in the provided path
- ErrorKind::FilesystemLoop => Self::NotFound,
- ErrorKind::PermissionDenied => Self::PermissionDenied,
- err => panic!("Unexpected IoError encountered: {:?}", err),
+pub(crate) fn io_result_to_write_result<T>(io_result: Result<T, IoError>, path: &Path) -> WriteResult<T> {
+ match io_result {
+ Ok(t) => Ok(t),
+ Err(io_error) => {
+ match io_error_to_entry_error(io_error) {
+ Ok(error_kind) => Err( EntryWriteError { path: path.to_path_buf(), error_kind }),
+ Err(err) => panic!("Unexpected IO error while attempting to write to {path:?}: {err:?}"),
+ }
}
}
}
+fn io_error_to_entry_error(io_error: IoError) -> Result<EntryErrorKind, IoErrorKind> {
+ match io_error.kind() {
+ IoErrorKind::NotFound => Ok(EntryErrorKind::NotFound),
+ // An intermediate path component was a plain file, not a directory
+ IoErrorKind::NotADirectory => Ok(EntryErrorKind::NotFound),
+ // A cyclic symbolic link chain was included in the provided path
+ IoErrorKind::FilesystemLoop => Ok(EntryErrorKind::NotFound),
+ IoErrorKind::PermissionDenied => Ok(EntryErrorKind::PermissionDenied),
+ err => Err(err),
+ }
+}
+
+impl std::fmt::Debug for EntryReadError {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
+ write!(f, "Error while attempting to read from file '{:?}': {:?}", self.path, self.error_kind)
+ }
+}
+impl std::fmt::Debug for EntryWriteError {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
+ write!(f, "Error while attempting to write to file '{:?}': {:?}", self.path, self.error_kind)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index deea25c..38fb5ee 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,10 +1,16 @@
#![feature(io_error_more)]
+#![feature(never_type)]
+mod entry;
mod error;
-pub use error::*;
-
mod operations;
+
+pub use entry::*;
+pub use error::*;
pub use operations::*;
-mod entry;
-pub use entry::{Entry, EntryType};
+pub use std::path::{Path, PathBuf};
+
+pub type ReadResult<T> = Result<T, EntryReadError>;
+pub type WriteResult<T> = Result<T, EntryWriteError>;
+
diff --git a/src/operations.rs b/src/operations.rs
index 54ce8c7..dd0a132 100644
--- a/src/operations.rs
+++ b/src/operations.rs
@@ -1,30 +1,25 @@
use crate::*;
-use std::path::Path;
-
-mod ls;
-pub use ls::*;
mod cp;
-pub use cp::*;
-
+mod ls;
+mod mkdir;
mod rm;
-pub use rm::*;
-mod mkdir;
+pub use cp::*;
+pub use ls::*;
pub use mkdir::*;
+pub use rm::*;
-pub fn append_to_file<P>(_path: P, _content: &str) -> Result<(), EntryWriteError>
-where
- P: AsRef<Path>,
-{
+/// Append bytes to the end of a file.
+#[must_use]
+pub fn append_to_file(_path: impl AsRef<Path>, _content: &str) -> WriteResult<()> {
unimplemented!()
}
-pub fn write_to_file<P>(path: P, content: &str) -> Result<(), EntryWriteError>
-where
- P: AsRef<Path>,
-{
+/// Write a slice of bytes to a file, overwriting the existing file if it already exists.
+#[must_use]
+pub fn write_to_file(path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> WriteResult<()> {
make_parent_directory(&path)?;
- std::fs::write(&path, content)?;
- Ok(())
+ let write_result = std::fs::write(&path, content);
+ Ok(io_result_to_write_result(write_result, path.as_ref())?)
}
diff --git a/src/operations/cp.rs b/src/operations/cp.rs
index be43826..27be3ce 100644
--- a/src/operations/cp.rs
+++ b/src/operations/cp.rs
@@ -1,62 +1,55 @@
-use crate::make_parent_directory;
-use crate::{get_entry, get_optional_entry, list_directory};
-use crate::{EntryType, EntryWriteError};
-use std::path::Path;
+use crate::*;
-pub fn copy<P, Q>(source_path: P, target_path: Q) -> Result<(), EntryWriteError>
-where
- P: AsRef<Path>,
- Q: AsRef<Path>,
-{
+#[must_use]
+pub fn copy(source_path: impl AsRef<Path>, destination_path: impl AsRef<Path>) -> WriteResult<()> {
let source = get_entry(&source_path)?;
- let target = get_optional_entry(&target_path)?;
- let target_type = target.and_then(|e| Some(e.entry_type));
+ let destination = get_optional_entry(&destination_path)?;
+ let destination_type = destination.and_then(|e| Some(e.entry_type));
- match (source.entry_type, target_type) {
+ match (source.entry_type, destination_type) {
(EntryType::File, Some(EntryType::File)) => {
- copy_file(source_path, target_path)?;
+ copy_file(source_path, destination_path)?;
}
(EntryType::File, Some(EntryType::Directory)) => {
- let target_path = target_path.as_ref().join(source.name);
- copy_file(source_path, target_path)?;
+ let destination_path = destination_path.as_ref().join(source.name);
+ copy_file(source_path, destination_path)?;
}
(EntryType::File, None) => {
- make_parent_directory(&target_path)?;
- copy_file(source_path, target_path)?;
+ make_parent_directory(&destination_path)?;
+ copy_file(&source_path, &destination_path)?;
}
(EntryType::Directory, Some(EntryType::File)) => {
- std::fs::remove_file(&target_path)?;
- copy_directory(&source_path, &target_path)?;
+ // The file is replaced by a copy of the source directory
+ remove_file(&destination_path)?;
+ copy_directory(&source_path, &destination_path)?;
}
(EntryType::Directory, Some(EntryType::Directory)) => {
- let target_path = target_path.as_ref().join(source.name);
- copy_directory(source_path, target_path)?;
+ // The destination is filled with the contents of the source directory
+ for entry in list_directory(source_path)? {
+ let destination_path = destination_path.as_ref().join(&entry.name);
+ copy_file(entry.path, destination_path)?;
+ }
}
(EntryType::Directory, None) => {
- make_parent_directory(&target_path)?;
- copy_directory(source_path, target_path)?;
+ // The destination is created as a copy of the source directory
+ copy_directory(&source_path, &destination_path)?;
}
}
Ok(())
}
-fn copy_file<P, Q>(source_path: P, target_path: Q) -> Result<(), EntryWriteError>
-where
- P: AsRef<Path>,
- Q: AsRef<Path>,
-{
- std::fs::copy(source_path, target_path)?;
+#[must_use]
+fn copy_file(source_path: impl AsRef<Path>, destination_path: impl AsRef<Path>) -> WriteResult<()> {
+ let copy_result = std::fs::copy(&source_path, &destination_path);
+ io_result_to_write_result(copy_result, &destination_path.as_ref())?;
Ok(())
}
-fn copy_directory<P, Q>(source_path: P, target_path: Q) -> Result<(), EntryWriteError>
-where
- P: AsRef<Path>,
- Q: AsRef<Path>,
-{
+#[must_use]
+fn copy_directory(source_path: impl AsRef<Path>, destination_path: impl AsRef<Path>) -> WriteResult<()> {
for entry in list_directory(&source_path)? {
- let target_path = target_path.as_ref().join(entry.name);
- copy(entry.path, &target_path)?;
+ let destination_path = destination_path.as_ref().join(entry.name);
+ copy(entry.path, &destination_path)?;
}
Ok(())
}
diff --git a/src/operations/ls.rs b/src/operations/ls.rs
index 2d2da5d..9dd3258 100644
--- a/src/operations/ls.rs
+++ b/src/operations/ls.rs
@@ -1,31 +1,29 @@
-use crate::{Entry, EntryReadError, EntryType};
-use std::path::Path;
+use crate::*;
-pub fn get_entry<P>(path: P) -> Result<Entry, EntryReadError>
-where
- P: AsRef<Path>,
-{
+/// Convert a file path into an [Entry].
+pub fn get_entry(path: impl AsRef<Path>) -> ReadResult<Entry> {
Entry::from_path(path)
}
-pub fn get_optional_entry<P>(path: P) -> Result<Option<Entry>, EntryReadError>
-where
- P: AsRef<Path>,
-{
+/// Convert a file path that might not be valid into an [Entry]. This will return
+/// as [Option::None] instead of [EntryReadError::NotFound] if the file doesn't exist.
+pub fn get_optional_entry(path: impl AsRef<Path>) -> ReadResult<Option<Entry>> {
match get_entry(path) {
Ok(e) => Ok(Some(e)),
- Err(EntryReadError::NotFound) => Ok(None),
+ Err(EntryReadError { error_kind: EntryErrorKind::NotFound, .. }) => Ok(None),
Err(other) => Err(other),
}
}
-pub fn list_directory<P>(path: P) -> Result<Vec<Entry>, EntryReadError>
-where
- P: AsRef<Path>,
-{
+/// Get an [Entry] for every file and subdirectory within a directory.
+pub fn list_directory(path: impl AsRef<Path>) -> ReadResult<Vec<Entry>>{
+ let path = path.as_ref();
+ macro_rules! raise {
+ ($err:expr) => {io_result_to_read_result($err, path)?}; }
+
let mut entries = Vec::new();
- for dir_entry in std::fs::read_dir(path)? {
- let entry = match Entry::from_path(&dir_entry?.path()) {
+ for dir_entry in raise!(std::fs::read_dir(path)) {
+ let entry = match Entry::from_path(&raise!(dir_entry).path()) {
Ok(v) => v,
Err(_) => continue,
};
@@ -34,12 +32,9 @@ where
return Ok(entries);
}
-/// Recursively descend into a directory and all sub-directories,
-/// returning an [`Entry`](struct.Entry.html) for each discovered file.
-pub fn traverse_directory<P>(path: P) -> Result<Vec<Entry>, EntryReadError>
-where
- P: AsRef<Path>,
-{
+/// Recursively descend into a directory and all sub-directories, returning an
+/// [Entry] for each discovered file.
+pub fn traverse_directory(path: impl AsRef<Path>) -> ReadResult<Vec<Entry>> {
let mut file_entries = Vec::new();
for entry in list_directory(path)? {
match entry.entry_type {
diff --git a/src/operations/mkdir.rs b/src/operations/mkdir.rs
index 011b8cf..9b2abd2 100644
--- a/src/operations/mkdir.rs
+++ b/src/operations/mkdir.rs
@@ -1,18 +1,15 @@
-use crate::EntryWriteError;
-use std::path::Path;
+use crate::*;
-pub fn make_directory<P>(path: P) -> Result<(), EntryWriteError>
-where
- P: AsRef<Path>,
-{
- std::fs::DirBuilder::new().recursive(true).create(path)?;
- Ok(())
+/// Create a new directory and all parent directories.
+#[must_use]
+pub fn make_directory(path: impl AsRef<Path>) -> WriteResult<()> {
+ let make_result = std::fs::DirBuilder::new().recursive(true).create(&path);
+ Ok(io_result_to_write_result(make_result, &path.as_ref())?)
}
-pub fn make_parent_directory<P>(path: P) -> Result<(), EntryWriteError>
-where
- P: AsRef<Path>,
-{
+/// Create the parent directory of a path.
+#[must_use]
+pub fn make_parent_directory(path: impl AsRef<Path>) -> WriteResult<()> {
match path.as_ref().parent() {
Some(parent) => make_directory(parent),
None => Ok(()),
diff --git a/src/operations/rm.rs b/src/operations/rm.rs
index 846a094..bf206a5 100644
--- a/src/operations/rm.rs
+++ b/src/operations/rm.rs
@@ -1,23 +1,17 @@
-use crate::EntryWriteError;
-use crate::{get_entry, EntryType};
-use std::path::Path;
+use crate::*;
-pub fn remove<P>(path: P) -> Result<(), EntryWriteError>
-where
- P: AsRef<Path>,
-{
+#[must_use]
+pub fn remove(path: impl AsRef<Path>) -> WriteResult<()> {
let entry = get_entry(&path)?;
- match entry.entry_type {
- EntryType::File => std::fs::remove_file(&path)?,
- EntryType::Directory => std::fs::remove_dir_all(&path)?,
- }
- Ok(())
+ let remove_result = match entry.entry_type {
+ EntryType::File => std::fs::remove_file(&path),
+ EntryType::Directory => std::fs::remove_dir_all(&path),
+ };
+ Ok(io_result_to_write_result(remove_result, path.as_ref())?)
}
-pub fn remove_file<P>(path: P) -> Result<(), EntryWriteError>
-where
- P: AsRef<Path>,
-{
- std::fs::remove_file(path)?;
- Ok(())
+#[must_use]
+pub fn remove_file(path: impl AsRef<Path>) -> WriteResult<()> {
+ let remove_result = std::fs::remove_file(&path);
+ Ok(io_result_to_write_result(remove_result, &path.as_ref())?)
}