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
//! # List-Modules Procedural Macro
//!
//! **WARNING: This crate is domain-specific. The only thing that makes it so
//! is that you cannot name one of the directory items you are trying to list
//! "archetypes.rs" (a module folder named "archetypes" is fine). I will try
//! to fix this ASAP.**
//!
//! This macro creates a constant string slice list of all the module names
//! which are children of an indicated crate module folder. Paths are specified
//! relative to the cargo manifest directory.
//!
//! For example, calling this macro from `mod.rs` in the following file tree
//! with `list_modules::here!("parent/");`...
//!
//! ```none
//! parent/
//!     mod.rs
//!     child_1.rs
//!     child_2/
//!         mod.rs
//!         internal.rs
//!         other_internal/
//!             ...
//!         ...
//!     child_3.rs
//!     child_4.rs
//!     ...
//!     child_N/
//!         mod.rs
//! ```
//!
//! ...will result in the following list expansion:
//!
//! ```rust
//! pub const LIST: [&str; N] = [
//!     "child_1",
//!     "child_2",
//!     "child_3",
//!     ...
//!     "child_N",
//! ];
//! ```
//!
//! Note that this is the only guaranteed behavior.

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use std::env;
use std::fs;

/// # List-Modules Procedural Macro
///
/// **WARNING: This crate is domain-specific. The only thing that makes it so
/// is that you cannot name one of the directory items you are trying to list
/// "archetypes.rs" (a module folder named "archetypes" is fine). I will try
/// to fix this ASAP.**
///
/// This macro creates a constant string slice list of all the module names
/// which are children of an indicated crate module folder. Paths are specified
/// relative to the cargo manifest directory.
///
/// For example, calling this macro from `mod.rs` in the following file tree
/// with `list_modules::here!("parent/");`...
///
/// ```none
/// parent/
///     mod.rs
///     child_1.rs
///     child_2/
///         mod.rs
///         internal.rs
///         other_internal/
///             ...
///         ...
///     child_3.rs
///     child_4.rs
///     ...
///     child_N/
///         mod.rs
/// ```
///
/// ...will result in the following list expansion:
///
/// ```rust
/// pub const LIST: [&str; N] = [
///     "child_1",
///     "child_2",
///     "child_3",
///     ...
///     "child_N",
/// ];
/// ```
///
/// Note that this is the only guaranteed behavior.
#[proc_macro]
pub fn here(__input: TokenStream) -> TokenStream {
    // Get the absolute path of the directory where the macro was called from
    let __manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("Failed to get Cargo manifest directory");
    let mut __macro_call_path = fs::canonicalize(__manifest_dir)
        .expect("Failed to canonicalize Cargo manifest directory");
    __macro_call_path.push(__input.to_string().trim_matches('"'));

    // Collect the module names by iterating over the entries in the full base directory
    let __internal_module_names: Vec<String> = fs::read_dir(__macro_call_path)
        .expect("Failed to read directory")
        .filter_map(|entry| {
            if let Ok(entry) = entry {
                if let Some(entry_name) = entry.file_name().to_str() {
                    if entry_name.ends_with(".rs")
                        && entry_name != "mod.rs"
                        && entry_name != "archetypes.rs"
                    {
                        return Some(entry_name[..entry_name.len() - 3].to_owned());
                    } else if entry_name != "mod.rs" && entry_name != "archetypes.rs" {
                        return Some(entry_name.to_owned());
                    }
                }
            }
            None
        })
        .collect();

    // Generate array of module names
    let __internal_module_array = quote! {
        [
            #(#__internal_module_names),*
        ]
    };

    // Get number of iterms in array to later insert them
    let __number_of_modules_in_array = __internal_module_names.len();

    // Generate the static list of string slices with the custom list name
    let __internal_macro_output: proc_macro2::TokenStream = quote! {
        pub const LIST: [&str; #__number_of_modules_in_array] = #__internal_module_array;
    };

    __internal_macro_output.into()
}