list_modules/
lib.rs

1//! # List-Modules Procedural Macro
2//!
3//! This macro creates a constant string slice list of all the module names
4//! which are children of an indicated crate module folder. Paths are specified
5//! relative to the cargo manifest directory.
6//!
7//! For example, calling this macro from `mod.rs` in the following file tree
8//! with `list_modules::here!("parent/");`...
9//!
10//! ```none
11//! parent/
12//!     mod.rs
13//!     child_1.rs
14//!     child_2/
15//!         mod.rs
16//!         internal.rs
17//!         other_internal/
18//!             ...
19//!         ...
20//!     child_3.rs
21//!     child_4.rs
22//!     ...
23//!     child_N/
24//!         mod.rs
25//! ```
26//!
27//! ...will result in the following list expansion:
28//!
29//! ```rust
30//! pub const LIST: [&str; N] = [
31//!     "child_1",
32//!     "child_2",
33//!     "child_3",
34//!     ...
35//!     "child_N",
36//! ];
37//! ```
38//!
39//! Note that this is the only guaranteed behavior.
40
41extern crate proc_macro;
42
43use proc_macro::TokenStream;
44use quote::quote;
45use std::env;
46use std::fs;
47
48/// # List-Modules Procedural Macro
49///
50/// This macro creates a constant string slice list of all the module names
51/// which are children of an indicated crate module folder. Paths are specified
52/// relative to the cargo manifest directory.
53///
54/// For example, calling this macro from `mod.rs` in the following file tree
55/// with `list_modules::here!("parent/");`...
56///
57/// ```none
58/// parent/
59///     mod.rs
60///     child_1.rs
61///     child_2/
62///         mod.rs
63///         internal.rs
64///         other_internal/
65///             ...
66///         ...
67///     child_3.rs
68///     child_4.rs
69///     ...
70///     child_N/
71///         mod.rs
72/// ```
73///
74/// ...will result in the following list expansion:
75///
76/// ```rust
77/// pub const LIST: [&str; N] = [
78///     "child_1",
79///     "child_2",
80///     "child_3",
81///     ...
82///     "child_N",
83/// ];
84/// ```
85///
86/// Note that this is the only guaranteed behavior.
87#[proc_macro]
88pub fn here(__input: TokenStream) -> TokenStream {
89    // Get the absolute path of the directory where the macro was called from
90    let __manifest_dir =
91        env::var("CARGO_MANIFEST_DIR").expect("Failed to get Cargo manifest directory");
92    let mut __macro_call_path =
93        fs::canonicalize(__manifest_dir).expect("Failed to canonicalize Cargo manifest directory");
94    __macro_call_path.push(__input.to_string().trim_matches('"'));
95
96    // Collect the module names by iterating over the entries in the full base directory
97    let __internal_module_names: Vec<String> = fs::read_dir(__macro_call_path)
98        .expect("Failed to read directory")
99        .filter_map(|entry| {
100            if let Ok(entry) = entry {
101                if let Some(entry_name) = entry.file_name().to_str() {
102                    if entry_name.ends_with(".rs") && entry_name != "mod.rs" {
103                        return Some(entry_name[..entry_name.len() - 3].to_owned());
104                    } else if entry_name != "mod.rs" {
105                        return Some(entry_name.to_owned());
106                    }
107                }
108            }
109            None
110        })
111        .collect();
112
113    // Generate array of module names
114    let __internal_module_array = quote! {
115        [
116            #(#__internal_module_names),*
117        ]
118    };
119
120    // Get number of iterms in array to later insert them
121    let __number_of_modules_in_array = __internal_module_names.len();
122
123    // Generate the static list of string slices with the custom list name
124    let __internal_macro_output: proc_macro2::TokenStream = quote! {
125        pub const LIST: [&str; #__number_of_modules_in_array] = #__internal_module_array;
126    };
127
128    __internal_macro_output.into()
129}