Initial commit
This commit is contained in:
commit
8f410d1ead
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "vagabond"
|
||||
version = "1.0.0"
|
||||
authors = ["Ben Bridle <bridle.benjamin@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
|
@ -0,0 +1,86 @@
|
|||
use crate::EntryReadError;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum EntryType {
|
||||
File,
|
||||
Directory,
|
||||
}
|
||||
|
||||
pub struct Entry {
|
||||
pub entry_type: EntryType,
|
||||
pub is_symlink: bool,
|
||||
pub name: String,
|
||||
pub extension: String,
|
||||
pub path: PathBuf,
|
||||
pub original_path: PathBuf,
|
||||
}
|
||||
impl Entry {
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, EntryReadError> {
|
||||
let path = path.as_ref();
|
||||
let metadata = path.metadata()?;
|
||||
let canonical_path = std::fs::canonicalize(path)?;
|
||||
let canonical_metadata = 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);
|
||||
};
|
||||
|
||||
let name = match path.file_name() {
|
||||
Some(osstr) => osstr.to_string_lossy().to_string(),
|
||||
None => unreachable!(),
|
||||
};
|
||||
let extension = match path.extension() {
|
||||
Some(extension) => extension.to_string_lossy().into(),
|
||||
None => String::default(),
|
||||
};
|
||||
Ok(Entry {
|
||||
entry_type,
|
||||
name,
|
||||
extension,
|
||||
path: canonical_path,
|
||||
original_path: path.to_path_buf(),
|
||||
is_symlink: metadata.is_symlink(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Splits 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(".") {
|
||||
Some(("", _)) | None => (self.name.to_string(), String::new()),
|
||||
Some((prefix, extension)) => (prefix.to_string(), extension.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_file(&self) -> bool {
|
||||
match self.entry_type {
|
||||
EntryType::File => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_directory(&self) -> bool {
|
||||
match self.entry_type {
|
||||
EntryType::Directory => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_as_bytes(&self) -> Result<Vec<u8>, EntryReadError> {
|
||||
return Ok(std::fs::read(&self.path)?);
|
||||
}
|
||||
|
||||
pub fn read_as_utf8_string(&self) -> Result<String, EntryReadError> {
|
||||
return Ok(String::from_utf8_lossy(&self.read_as_bytes()?).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for Entry {
|
||||
fn as_ref(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
use std::io::Error as IoError;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EntryReadError {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EntryWriteError {
|
||||
NotFound,
|
||||
PermissionDenied,
|
||||
}
|
||||
impl From<EntryReadError> for EntryWriteError {
|
||||
fn from(error: EntryReadError) -> Self {
|
||||
match error {
|
||||
EntryReadError::NotFound => EntryWriteError::NotFound,
|
||||
EntryReadError::PermissionDenied => EntryWriteError::PermissionDenied,
|
||||
}
|
||||
}
|
||||
}
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#![feature(io_error_more)]
|
||||
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
mod operations;
|
||||
pub use operations::*;
|
||||
|
||||
mod entry;
|
||||
pub use entry::{Entry, EntryType};
|
|
@ -0,0 +1,30 @@
|
|||
use crate::*;
|
||||
use std::path::Path;
|
||||
|
||||
mod ls;
|
||||
pub use ls::*;
|
||||
|
||||
mod cp;
|
||||
pub use cp::*;
|
||||
|
||||
mod rm;
|
||||
pub use rm::*;
|
||||
|
||||
mod mkdir;
|
||||
pub use mkdir::*;
|
||||
|
||||
pub fn append_to_file<P>(_path: P, _content: &str) -> Result<(), EntryWriteError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn write_to_file<P>(path: P, content: &str) -> Result<(), EntryWriteError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
make_parent_directory(&path)?;
|
||||
std::fs::write(&path, content)?;
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
use crate::make_parent_directory;
|
||||
use crate::{get_entry, get_optional_entry, list_directory};
|
||||
use crate::{EntryType, EntryWriteError};
|
||||
use std::path::Path;
|
||||
|
||||
pub fn copy<P, Q>(source_path: P, target_path: Q) -> Result<(), EntryWriteError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
let source = get_entry(&source_path)?;
|
||||
let target = get_optional_entry(&target_path)?;
|
||||
let target_type = target.and_then(|e| Some(e.entry_type));
|
||||
|
||||
match (source.entry_type, target_type) {
|
||||
(EntryType::File, Some(EntryType::File)) => {
|
||||
copy_file(source_path, target_path)?;
|
||||
}
|
||||
(EntryType::File, Some(EntryType::Directory)) => {
|
||||
let target_path = target_path.as_ref().join(source.name);
|
||||
copy_file(source_path, target_path)?;
|
||||
}
|
||||
(EntryType::File, None) => {
|
||||
make_parent_directory(&target_path)?;
|
||||
copy_file(source_path, target_path)?;
|
||||
}
|
||||
(EntryType::Directory, Some(EntryType::File)) => {
|
||||
std::fs::remove_file(&target_path)?;
|
||||
copy_directory(&source_path, &target_path)?;
|
||||
}
|
||||
(EntryType::Directory, Some(EntryType::Directory)) => {
|
||||
let target_path = target_path.as_ref().join(source.name);
|
||||
copy_directory(source_path, target_path)?;
|
||||
}
|
||||
(EntryType::Directory, None) => {
|
||||
make_parent_directory(&target_path)?;
|
||||
copy_directory(source_path, target_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)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_directory<P, Q>(source_path: P, target_path: Q) -> Result<(), EntryWriteError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
for entry in list_directory(&source_path)? {
|
||||
let target_path = target_path.as_ref().join(entry.name);
|
||||
copy(entry.path, &target_path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
use crate::{Entry, EntryReadError, EntryType};
|
||||
use std::path::Path;
|
||||
|
||||
pub fn get_entry<P>(path: P) -> Result<Entry, EntryReadError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Entry::from_path(path)
|
||||
}
|
||||
|
||||
pub fn get_optional_entry<P>(path: P) -> Result<Option<Entry>, EntryReadError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match get_entry(path) {
|
||||
Ok(e) => Ok(Some(e)),
|
||||
Err(EntryReadError::NotFound) => Ok(None),
|
||||
Err(other) => Err(other),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_directory<P>(path: P) -> Result<Vec<Entry>, EntryReadError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut entries = Vec::new();
|
||||
for dir_entry in std::fs::read_dir(path)? {
|
||||
let entry = match Entry::from_path(&dir_entry?.path()) {
|
||||
Ok(v) => v,
|
||||
Err(_) => continue,
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
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>,
|
||||
{
|
||||
let mut file_entries = Vec::new();
|
||||
for entry in list_directory(path)? {
|
||||
match entry.entry_type {
|
||||
EntryType::File => file_entries.push(entry),
|
||||
EntryType::Directory => file_entries.extend(traverse_directory(&entry.path)?),
|
||||
}
|
||||
}
|
||||
return Ok(file_entries);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
use crate::EntryWriteError;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn make_directory<P>(path: P) -> Result<(), EntryWriteError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
std::fs::DirBuilder::new().recursive(true).create(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn make_parent_directory<P>(path: P) -> Result<(), EntryWriteError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match path.as_ref().parent() {
|
||||
Some(parent) => make_directory(parent),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
use crate::EntryWriteError;
|
||||
use crate::{get_entry, EntryType};
|
||||
use std::path::Path;
|
||||
|
||||
pub fn remove<P>(path: P) -> Result<(), EntryWriteError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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(())
|
||||
}
|
||||
|
||||
pub fn remove_file<P>(path: P) -> Result<(), EntryWriteError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
std::fs::remove_file(path)?;
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue