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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::{
    fs::Metadata,
    io,
    path::{Path, PathBuf},
};

use tokio::fs::{self};

pub fn get_basename_ext_name(path: &str) -> (String, String) {
    let path = path.strip_prefix("./").unwrap_or(path);
    let (basename, ext) = path.split_at(path.rfind('.').unwrap_or(path.len()));
    (basename.to_string(), ext.to_string())
}

pub static JS_EXTENSIONS: &[&str] = &[".js", ".mjs", ".cjs"];

pub fn get_js_path(path: &str) -> Option<PathBuf> {
    let (mut basename, ext) = get_basename_ext_name(path);

    let filepath = Path::new(path);

    let exists = filepath.exists();

    if !ext.is_empty() && exists {
        return Some(filepath.to_owned());
    }

    if filepath.is_dir() && exists {
        basename = format!("{}/index", &basename);
    }

    for ext in JS_EXTENSIONS {
        let path = &format!("{}{}", &basename, ext);

        let path = Path::new(path);
        if path.exists() {
            return Some(path.to_owned());
        }
    }

    None
}

pub struct DirectoryWalker<T>
where
    T: Fn(&str) -> bool,
{
    stack: Vec<(PathBuf, Option<Metadata>)>,
    filter: T,
    recursive: bool,
    eat_root: bool,
}

impl<T> DirectoryWalker<T>
where
    T: Fn(&str) -> bool,
{
    pub fn new(root: PathBuf, filter: T) -> Self {
        Self {
            stack: vec![(root, None)],
            filter,
            recursive: false,
            eat_root: true,
        }
    }

    pub fn set_recursive(&mut self, recursive: bool) {
        self.recursive = recursive;
    }

    pub async fn walk(&mut self) -> io::Result<Option<(PathBuf, Metadata)>> {
        if self.eat_root {
            self.eat_root = false;
            let (dir, _) = self.stack.pop().unwrap();
            self.append_stack(&dir).await?;
        }
        if let Some((dir, metadata)) = self.stack.pop() {
            let metadata = metadata.unwrap();
            if self.recursive && metadata.is_dir() {
                self.append_stack(&dir).await?;
            }

            Ok(Some((dir, metadata)))
        } else {
            Ok(None)
        }
    }

    pub fn walk_sync(&mut self) -> io::Result<Option<(PathBuf, Metadata)>> {
        if self.eat_root {
            self.eat_root = false;
            let (dir, _) = self.stack.pop().unwrap();
            self.append_stack_sync(&dir)?;
        }
        if let Some((dir, metadata)) = self.stack.pop() {
            let metadata = metadata.unwrap();
            if self.recursive && metadata.is_dir() {
                self.append_stack_sync(&dir)?;
            }

            Ok(Some((dir, metadata)))
        } else {
            Ok(None)
        }
    }

    async fn append_stack(&mut self, dir: &PathBuf) -> io::Result<()> {
        let mut stream = fs::read_dir(dir).await?;

        while let Some(entry) = stream.next_entry().await? {
            let entry_path = entry.path();

            let name = entry.file_name().to_string_lossy().to_string();
            if !(self.filter)(&name) {
                continue;
            }

            let metadata = fs::symlink_metadata(&entry_path).await?;

            self.stack.push((entry_path, Some(metadata)));
        }
        Ok(())
    }

    fn append_stack_sync(&mut self, dir: &PathBuf) -> io::Result<()> {
        let dir = std::fs::read_dir(dir)?;

        for entry in dir.flatten() {
            let name = entry.file_name().to_string_lossy().to_string();
            if !(self.filter)(&name) {
                continue;
            }
            let entry_path = entry.path();
            let metadata = entry_path.symlink_metadata()?;
            self.stack.push((entry_path, Some(metadata)))
        }

        Ok(())
    }
}