reexport/
lib.rs

1use std::path::{Path, PathBuf};
2use std::{ffi::OsString, fs};
3
4#[derive(clap::Parser)]
5#[command(author, version, about, long_about = None)]
6#[command(group(clap::ArgGroup::new("traversal").args(["recursive", "depth"])))]
7pub struct CLI {
8    /// List of paths to be reexported
9    #[arg(required = true)]
10    pub paths: Vec<PathBuf>,
11
12    /// Ignore files with matching names
13    #[arg(short, long)]
14    pub ignore: Vec<OsString>,
15
16    /// Ignore .js or .jsx files
17    #[arg(long)]
18    pub only_ts: bool,
19
20    /// Reexport subfolders within N depth
21    #[arg(short, long, default_value_t = 0)]
22    pub depth: usize,
23
24    /// Reexport all subfolders recursively
25    #[arg(short, long)]
26    pub recursive: bool,
27}
28
29pub enum Entry {
30    Folder { path: PathBuf, entries: Vec<Entry> },
31    File(PathBuf),
32}
33
34pub fn write_files(root: &Path, entries: &Vec<Entry>) {
35    let mut buffer = Vec::new();
36
37    for entry in entries {
38        match entry {
39            Entry::File(path) => {
40                let os_str = path.file_stem().unwrap_or_default();
41                let name = os_str.to_str().unwrap_or_default();
42                buffer.push(format!("export * from './{}';", name));
43            }
44            Entry::Folder { path, entries } => {
45                let os_str = path.file_stem().unwrap_or_default();
46                let name = os_str.to_str().unwrap_or_default();
47                buffer.push(format!("export * from './{}';", name));
48                write_files(&path, entries);
49            }
50        }
51    }
52
53    if buffer.len() > 0 {
54        let mut output_path = root.to_owned();
55        output_path.push("index.ts");
56        fs::write(output_path, buffer.join("\n") + "\n").unwrap();
57    }
58}
59
60pub fn read_path(
61    root: &Path,
62    ignore: &Vec<OsString>,
63    only_ts: bool,
64    recursive: bool,
65    max_depth: usize,
66    depth: usize,
67) -> Vec<Entry> {
68    let rd = fs::read_dir(root).unwrap();
69
70    let output = rd
71        .filter_map(|r| r.ok())
72        .filter(|dir_entry| filter_dir_entry(dir_entry, only_ts, ignore))
73        .map(|entry| {
74            let path = entry.path();
75
76            if path.is_file() {
77                return Entry::File(path);
78            }
79
80            let entries = if recursive || depth + 1 <= max_depth {
81                read_path(&path, ignore, only_ts, recursive, max_depth, depth + 1)
82            } else {
83                Vec::new()
84            };
85
86            return Entry::Folder { path, entries };
87        })
88        .collect::<Vec<Entry>>();
89
90    return output;
91}
92
93fn filter_dir_entry(entry: &fs::DirEntry, only_ts: bool, ignore: &Vec<OsString>) -> bool {
94    let path = entry.path();
95    let name = path
96        .file_name()
97        .unwrap_or_default()
98        .to_str()
99        .unwrap_or_default();
100
101    let should_exclude = name.contains("index")
102        || ignore
103            .iter()
104            .any(|t| name.contains(t.to_str().unwrap_or_default()));
105
106    if path.is_file() {
107        let should_include = if let Some(extension) = path.extension() {
108            let allowed_extensions = if only_ts {
109                vec!["ts", "tsx"]
110            } else {
111                vec!["ts", "tsx", "js", "jsx"]
112            };
113
114            let extension = extension.to_str().unwrap_or_default();
115            allowed_extensions.iter().any(|ext| extension == *ext)
116        } else {
117            false
118        };
119        return !should_exclude && should_include;
120    }
121    return !should_exclude;
122}