module_path_extractor/
lib.rs

1#![feature(proc_macro_span)]
2
3use proc_macro::Span;
4extern crate proc_macro;
5
6use std::fs;
7
8/// Extracts the file path and line number where the macro was invoked
9pub fn get_source_info() -> Option<(String, usize)> {
10    let span = Span::call_site();
11    let file_path = span.source_file().path();
12    let file_path = file_path.to_string_lossy().to_string();
13    let line_number = span.start().line(); // Get the line number
14    Some((file_path, line_number))
15}
16
17/// Reads the file and extracts the module path
18pub fn find_module_path(file_path: &str, line_number: usize) -> Option<String> {
19    let content = fs::read_to_string(file_path).ok()?; // Read file contents
20
21    let mut module_path = Vec::new();
22    let mut last_inline_module: Option<String> = None;
23
24    for (i, line) in content.lines().enumerate() {
25        if i >= line_number {
26            break;
27        }
28
29        let trimmed = line.trim();
30
31        if let Some(mod_name) = trimmed.strip_prefix("mod ") {
32            if let Some(mod_name) = mod_name.strip_suffix('{') {
33                let mod_name = mod_name.trim().to_string();
34                last_inline_module = Some(mod_name.clone());
35                module_path.clear();
36                module_path.push(mod_name);
37            }
38        } else if let Some(pub_mod_name) = trimmed.strip_prefix("pub mod ") {
39            if let Some(pub_mod_name) = pub_mod_name.strip_suffix('{') {
40                let pub_mod_name = pub_mod_name.trim().to_string();
41                last_inline_module = Some(pub_mod_name.clone());
42                module_path.clear();
43                module_path.push(pub_mod_name);
44            }
45        }
46    }
47
48    if !module_path.is_empty() {
49        return Some(module_path.join("::").to_string());
50    }
51
52    // Otherwise, use the file path
53    Some(generate_pseudo_module_path(file_path, last_inline_module))
54}
55
56/// Converts a file path into a pseudo-Rust module path
57pub fn generate_pseudo_module_path(file_path: &str, inline_module: Option<String>) -> String {
58    let filename = file_path.rsplit('/').next().unwrap_or(file_path);
59    let filename = filename.split('.').next().unwrap_or(filename);
60
61    let parent_dir = file_path.rsplit_once('/').map(|(dir, _)| dir).unwrap_or("");
62
63    let parent_dir = parent_dir.replace("/", "::");
64
65    let parent_dir = parent_dir.strip_prefix("src::").unwrap_or(&parent_dir);
66
67    if filename == "main" || filename == "lib" {
68        return "crate".to_string();
69    }
70
71    if let Some(inline_mod) = inline_module {
72        return inline_mod.to_string();
73    }
74
75    if parent_dir.is_empty() {
76        filename.to_string()
77    } else {
78        format!("{}::{}", parent_dir, filename)
79    }
80}
81
82/// Gets the full pseudo-absolute module path of the current macro invocation
83pub fn get_pseudo_module_path() -> String {
84    get_source_info()
85        .and_then(|(file, line)| find_module_path(&file, line))
86        .unwrap_or_else(|| "unknown".to_string())
87}