Skip to main content

semdiff_core/
fs.rs

1use crate::{LeafTraverse, NodeTraverse, TraversalNode};
2use memmap2::Mmap;
3use mime::Mime;
4use std::fs::File;
5use std::io;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8use thiserror::Error;
9
10#[derive(Debug, Clone)]
11pub struct FileLeaf {
12    pub name: String,
13    pub kind: Mime,
14    pub content: Arc<Mmap>,
15}
16
17impl LeafTraverse for FileLeaf {
18    fn name(&self) -> &str {
19        &self.name
20    }
21}
22
23#[derive(Debug, Error)]
24pub enum FsTreeError {
25    #[error("failed to read directory: {0}")]
26    ReadDir(#[source] io::Error),
27    #[error("failed to read file metadata: {0}")]
28    Metadata(#[source] io::Error),
29    #[error("failed to open file: {0}")]
30    Open(io::Error),
31}
32
33#[derive(Clone, Debug)]
34pub struct FsNode {
35    abs_path: PathBuf,
36    name: String,
37}
38
39impl FsNode {
40    pub fn new_root(path: PathBuf) -> FsNode {
41        FsNode {
42            abs_path: path,
43            name: "".to_owned(),
44        }
45    }
46
47    fn new(abs_path: PathBuf, name: String) -> Self {
48        Self { abs_path, name }
49    }
50}
51
52impl NodeTraverse for FsNode {
53    type Leaf = FileLeaf;
54
55    type TraverseError = FsTreeError;
56
57    fn name(&self) -> &str {
58        &self.name
59    }
60
61    fn children(
62        &mut self,
63    ) -> Result<impl Iterator<Item = Result<TraversalNode<Self, Self::Leaf>, Self::TraverseError>>, Self::TraverseError>
64    {
65        let entries = match std::fs::read_dir(&self.abs_path) {
66            Ok(entries) => entries,
67            Err(err) => return Err(FsTreeError::ReadDir(err)),
68        };
69
70        Ok(entries.map(|entry| {
71            let entry = entry.map_err(FsTreeError::ReadDir)?;
72            let file_type = entry.file_type().map_err(FsTreeError::Metadata)?;
73            let name = entry.file_name();
74            let abs_path = entry.path();
75            let name = name.to_string_lossy().into_owned();
76            if file_type.is_dir() {
77                Ok(TraversalNode::Node(FsNode::new(abs_path, name)))
78            } else {
79                let file = File::open(entry.path()).map_err(FsTreeError::Open)?;
80                let content = unsafe { Mmap::map(&file) }.map_err(FsTreeError::Open)?;
81                let kind = detect_file_kind(&abs_path, &content);
82                let leaf = FileLeaf {
83                    name,
84                    kind,
85                    content: Arc::new(content),
86                };
87                Ok(TraversalNode::Leaf(leaf))
88            }
89        }))
90    }
91}
92
93fn detect_file_kind(path: &Path, body: &[u8]) -> Mime {
94    if let Some(kind) = infer::get(body)
95        && let Ok(mime) = kind.mime_type().parse()
96    {
97        mime
98    } else if let Some(mime) = mime_guess::from_path(path).first() {
99        mime
100    } else {
101        mime::APPLICATION_OCTET_STREAM
102    }
103}