#[derive(Clone, Debug)]
pub struct Entry {
pub relative_path: String,
pub depth: u32,
pub size: u64,
pub is_dir: bool,
pub is_symlink: bool,
pub is_hidden: bool,
pub modified: i64,
}
impl AsRef<Entry> for Entry {
fn as_ref(&self) -> &Entry {
self
}
}
impl Entry {
#[inline]
pub fn name(&self) -> &str {
match self.relative_path.rfind(['/', '\\']) {
Some(pos) => &self.relative_path[pos + 1..],
None => &self.relative_path,
}
}
#[inline]
pub fn extension(&self) -> Option<&str> {
if self.is_dir {
return None;
}
let name = self.name();
let dot = name.rfind('.')?;
if dot == 0 {
return None;
}
let ext = &name[dot + 1..];
if ext.is_empty() {
return None;
}
Some(ext)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn file(relative_path: &str) -> Entry {
Entry {
relative_path: relative_path.to_owned(),
depth: 1,
size: 1,
is_dir: false,
is_symlink: false,
is_hidden: false,
modified: 0,
}
}
fn dir(relative_path: &str) -> Entry {
Entry {
relative_path: relative_path.to_owned(),
depth: 1,
size: 0,
is_dir: true,
is_symlink: false,
is_hidden: false,
modified: 0,
}
}
#[test]
fn name_no_separator() {
assert_eq!(file("foo.txt").name(), "foo.txt");
}
#[test]
fn name_forward_slash() {
assert_eq!(file("sub/foo.txt").name(), "foo.txt");
}
#[test]
fn name_backslash() {
assert_eq!(file("sub\\foo.txt").name(), "foo.txt");
}
#[test]
fn name_nested() {
assert_eq!(file("a/b/c.rs").name(), "c.rs");
}
#[test]
fn extension_simple() {
assert_eq!(file("foo.txt").extension(), Some("txt"));
}
#[test]
fn extension_multiple_dots() {
assert_eq!(file("archive.tar.gz").extension(), Some("gz"));
}
#[test]
fn extension_trailing_dot() {
assert_eq!(file("foo.").extension(), None);
}
#[test]
fn extension_no_dot() {
assert_eq!(file("Makefile").extension(), None);
}
#[test]
fn extension_hidden_file_no_ext() {
assert_eq!(file(".hidden").extension(), None);
}
#[test]
fn extension_hidden_file_with_ext() {
assert_eq!(file(".env.bak").extension(), Some("bak"));
}
#[test]
fn extension_preserves_case() {
assert_eq!(file("photo.PNG").extension(), Some("PNG"));
}
#[test]
fn extension_dir_always_none() {
assert_eq!(dir("src.bak").extension(), None);
}
#[test]
fn extension_in_subdirectory() {
assert_eq!(file("sub/foo.rs").extension(), Some("rs"));
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Entry {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeStruct;
let mut s = serializer.serialize_struct("Entry", 9)?;
s.serialize_field("name", self.name())?;
s.serialize_field("relative_path", &self.relative_path)?;
s.serialize_field("depth", &self.depth)?;
s.serialize_field("size", &self.size)?;
s.serialize_field("is_dir", &self.is_dir)?;
s.serialize_field("is_symlink", &self.is_symlink)?;
s.serialize_field("is_hidden", &self.is_hidden)?;
s.serialize_field("modified", &self.modified)?;
match self.extension() {
Some(ext) => s.serialize_field("extension", ext)?,
None => s.skip_field("extension")?,
}
s.end()
}
}