summaryrefslogtreecommitdiff
path: root/src/entry.rs
blob: 72eeb7e05fd939a2a45447b13200d70c55d758a0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use std::path::{Path, PathBuf};
use crate::*;

#[derive(PartialEq)]
pub enum EntryType {
    File,
    Directory,
}

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(path: impl AsRef<Path>) -> ReadResult<Self> {
        let path = path.as_ref();
        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 {
            unreachable!("Canonical metadata must describe either a file or a directory");
        };

        let name = match path.file_name() {
            Some(os_str) => os_str.to_string_lossy().to_string(),
            None => path.to_string_lossy().to_string(),
        };
        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(),
        })
    }

    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(".") {
            Some(("", _)) | None => (self.name.to_string(), String::new()),
            Some((prefix, extension)) => (prefix.to_string(), extension.to_string()),
        }
    }

    pub fn is_file(&self) -> bool {
        self.entry_type == EntryType::File
    }

    pub fn is_directory(&self) -> bool {
        self.entry_type == EntryType::Directory
    }

    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(&self) -> ReadResult<String> {
        return Ok(String::from_utf8_lossy(&self.read_as_bytes()?).to_string());
    }
}

impl AsRef<Path> for Entry {
    fn as_ref(&self) -> &Path {
        &self.original_path
    }
}