darklua_core/rules/require/
path_locator.rs

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
92
93
94
95
96
97
98
99
100
101
use std::path::{Path, PathBuf};

use super::{path_iterator, PathRequireMode};
use crate::{utils, DarkluaError, Resources};

#[derive(Debug)]
pub(crate) struct RequirePathLocator<'a, 'b, 'resources> {
    path_require_mode: &'a PathRequireMode,
    extra_module_relative_location: &'b Path,
    resources: &'resources Resources,
}

impl<'a, 'b, 'c> RequirePathLocator<'a, 'b, 'c> {
    pub(crate) fn new(
        path_require_mode: &'a PathRequireMode,
        extra_module_relative_location: &'b Path,
        resources: &'c Resources,
    ) -> Self {
        Self {
            path_require_mode,
            extra_module_relative_location,
            resources,
        }
    }

    pub(crate) fn find_require_path(
        &self,
        path: impl Into<PathBuf>,
        source: &Path,
    ) -> Result<PathBuf, DarkluaError> {
        let mut path: PathBuf = path.into();
        log::trace!(
            "find require path for `{}` from `{}`",
            path.display(),
            source.display()
        );

        if is_require_relative(&path) {
            let mut new_path = source.to_path_buf();
            new_path.pop();
            new_path.push(path);
            path = new_path;
        } else if !path.is_absolute() {
            {
                let mut components = path.components();
                let root = components.next().ok_or_else(|| {
                    DarkluaError::invalid_resource_path(path.display().to_string(), "path is empty")
                })?;
                let source_name = utils::convert_os_string(root.as_os_str()).map_err(|err| {
                    err.context(format!(
                        "cannot convert source name to utf-8 in `{}`",
                        path.display(),
                    ))
                })?;

                let mut extra_module_location = self.extra_module_relative_location.join(
                    self.path_require_mode
                        .get_source(source_name)
                        .ok_or_else(|| {
                            DarkluaError::invalid_resource_path(
                                path.display().to_string(),
                                format!("unknown source name `{}`", source_name),
                            )
                        })?,
                );
                extra_module_location.extend(components);
                path = extra_module_location;
            }
        }
        // else: the path is absolute so darklua should attempt to require it directly

        let normalized_path = utils::normalize_path_with_current_dir(&path);
        for potential_path in path_iterator::find_require_paths(
            &normalized_path,
            self.path_require_mode.module_folder_name(),
        ) {
            if self.resources.is_file(&potential_path)? {
                return Ok(utils::normalize_path_with_current_dir(potential_path));
            }
        }

        Err(
            DarkluaError::resource_not_found(&normalized_path).context(format!(
                "tried `{}`",
                path_iterator::find_require_paths(
                    &normalized_path,
                    self.path_require_mode.module_folder_name(),
                )
                .map(|potential_path| potential_path.display().to_string())
                .collect::<Vec<_>>()
                .join("`, `")
            )),
        )
    }
}

// the `is_relative` method from std::path::Path is not what darklua needs
// to consider a require relative, which are paths that starts with `.` or `..`
fn is_require_relative(path: &Path) -> bool {
    path.starts_with(Path::new(".")) || path.starts_with(Path::new(".."))
}