nodejs_resolver/
entry.rs

1use once_cell::sync::OnceCell;
2use std::{
3    borrow::Cow,
4    fs::FileType,
5    path::{Path, PathBuf},
6    sync::Arc,
7    time::SystemTime,
8};
9
10use crate::{description::DescriptionData, Error, RResult, Resolver};
11
12#[derive(Debug, Default, Clone, Copy)]
13pub struct EntryStat {
14    /// `None` for non-existing file
15    file_type: Option<FileType>,
16
17    /// `None` for existing file but without system time.
18    modified: Option<SystemTime>,
19}
20
21impl EntryStat {
22    fn new(file_type: Option<FileType>, modified: Option<SystemTime>) -> Self {
23        Self {
24            file_type,
25            modified,
26        }
27    }
28
29    /// Returns `None` for non-existing file
30    pub fn file_type(&self) -> Option<FileType> {
31        self.file_type
32    }
33
34    /// Returns `None` for existing file but without system time.
35    pub fn modified(&self) -> Option<SystemTime> {
36        self.modified
37    }
38
39    fn stat(path: &Path) -> Self {
40        if let Ok(meta) = path.metadata() {
41            // This field might not be available on all platforms,
42            // and will return an Err on platforms where it is not available.
43            let modified = meta.modified().ok();
44            Self::new(Some(meta.file_type()), modified)
45        } else {
46            Self::new(None, None)
47        }
48    }
49}
50
51#[derive(Debug)]
52pub struct Entry {
53    parent: Option<Arc<Entry>>,
54    path: Box<Path>,
55    // None: package.json does not exist
56    pkg_info: OnceCell<Option<Arc<DescriptionData>>>,
57    stat: OnceCell<EntryStat>,
58    /// None represent the `self.path` is not a symlink
59    symlink: OnceCell<Option<Box<Path>>>,
60    /// If `self.path` is a symlink, then return canonicalized path,
61    /// else return `self.path`
62    real: OnceCell<Box<Path>>,
63}
64
65impl Entry {
66    pub fn path(&self) -> &Path {
67        &self.path
68    }
69
70    pub fn parent(&self) -> Option<&Arc<Entry>> {
71        self.parent.as_ref()
72    }
73
74    pub fn pkg_info(&self, resolver: &Resolver) -> RResult<&Option<Arc<DescriptionData>>> {
75        self.pkg_info.get_or_try_init(|| {
76            let pkg_name = &resolver.options.description_file;
77            let path = self.path();
78            let is_pkg_suffix = path.ends_with(pkg_name);
79            if self.is_dir() || is_pkg_suffix {
80                let pkg_path = if is_pkg_suffix {
81                    Cow::Borrowed(path)
82                } else {
83                    Cow::Owned(path.join(pkg_name))
84                };
85                match resolver
86                    .cache
87                    .fs
88                    .read_description_file(&pkg_path, EntryStat::default())
89                {
90                    Ok(info) => {
91                        return Ok(Some(info));
92                    }
93                    Err(error @ (Error::UnexpectedJson(_) | Error::UnexpectedValue(_))) => {
94                        // Return bad json
95                        return Err(error);
96                    }
97                    Err(Error::Io(_)) => {
98                        // package.json not found
99                    }
100                    _ => unreachable!(),
101                };
102            }
103            if let Some(parent) = &self.parent() {
104                return parent.pkg_info(resolver).cloned();
105            }
106            Ok(None)
107        })
108    }
109
110    pub fn is_file(&self) -> bool {
111        self.cached_stat()
112            .file_type()
113            .map_or(false, |ft| ft.is_file())
114    }
115
116    pub fn is_dir(&self) -> bool {
117        self.cached_stat()
118            .file_type()
119            .map_or(false, |ft| ft.is_dir())
120    }
121
122    pub fn exists(&self) -> bool {
123        self.cached_stat().file_type().is_some()
124    }
125
126    pub fn cached_stat(&self) -> EntryStat {
127        *self.stat.get_or_init(|| EntryStat::stat(&self.path))
128    }
129
130    pub fn real(&self) -> Option<&Path> {
131        self.real.get().map(|p| &**p)
132    }
133
134    pub fn init_real(&self, path: Box<Path>) {
135        self.real.get_or_init(|| path);
136    }
137
138    /// Returns the canonicalized path of `self.path` if it is a symlink.
139    /// Returns None if `self.path` is not a symlink.
140    pub fn symlink(&self) -> &Option<Box<Path>> {
141        self.symlink.get_or_init(|| {
142            debug_assert!(self.path.is_absolute());
143            if self.path.read_link().is_err() {
144                return None;
145            }
146            match dunce::canonicalize(&self.path) {
147                Ok(symlink_path) => Some(Box::from(symlink_path)),
148                Err(_) => None,
149            }
150        })
151    }
152}
153
154impl Resolver {
155    pub(super) fn load_entry(&self, path: &Path) -> Arc<Entry> {
156        if let Some(cached) = self.cache.entries.get(path) {
157            cached.clone()
158        } else {
159            let entry = Arc::new(self.load_entry_uncached(path));
160            self.cache
161                .entries
162                .entry(path.into())
163                .or_insert(entry.clone());
164            entry
165        }
166    }
167
168    fn load_entry_uncached(&self, path: &Path) -> Entry {
169        let parent = if let Some(parent) = path.parent() {
170            let entry = self.load_entry(parent);
171            Some(entry)
172        } else {
173            None
174        };
175        Entry {
176            parent,
177            path: path.into(),
178            pkg_info: OnceCell::default(),
179            stat: OnceCell::default(),
180            symlink: OnceCell::default(),
181            real: OnceCell::default(),
182        }
183    }
184
185    // TODO: should put entries as a parament.
186    pub fn clear_entries(&self) {
187        self.cache.entries.clear();
188    }
189
190    #[must_use]
191    pub fn get_dependency_from_entry(&self) -> (Vec<PathBuf>, Vec<PathBuf>) {
192        todo!("get_dependency_from_entry")
193    }
194}
195
196#[test]
197#[ignore]
198fn dependency_test() {
199    let case_path = super::test_helper::p(vec!["full", "a"]);
200    let request = "package2";
201    let resolver = Resolver::new(Default::default());
202    resolver.resolve(&case_path, request).unwrap();
203    let (file, missing) = resolver.get_dependency_from_entry();
204    assert_eq!(file.len(), 3);
205    assert_eq!(missing.len(), 1);
206}