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
//! # List-Modules Procedural Macro
//!
//! 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
///
/// 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" {
                        return Some(entry_name[..entry_name.len() - 3].to_owned());
                    } else if entry_name != "mod.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()
}