use regex::Regex;
use std::sync::LazyLock;
static ALL_DECL: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r#"__all__\s*=\s*\[([^\]]*)\]"#).unwrap());
static TOP_LEVEL_DECL: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?m)^(?:def|class|async def)\s+(\w+)").unwrap());
static QUOTED: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"["'](\w+)["']"#).unwrap());
pub fn extract_exports(content: &str) -> Vec<String> {
if let Some(caps) = ALL_DECL.captures(content)
&& let Some(list) = caps.get(1)
{
let mut symbols = Vec::new();
for name_cap in QUOTED.captures_iter(list.as_str()) {
if let Some(name) = name_cap.get(1) {
symbols.push(name.as_str().to_string());
}
}
return symbols;
}
let mut symbols = Vec::new();
for caps in TOP_LEVEL_DECL.captures_iter(content) {
if let Some(name) = caps.get(1) {
let n = name.as_str();
if !n.starts_with('_') {
symbols.push(n.to_string());
}
}
}
symbols
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_python_all() {
let src = r#"
__all__ = ["create_auth", "AuthService", "DEFAULT_TTL"]
def create_auth(config):
pass
class AuthService:
pass
def _internal():
pass
DEFAULT_TTL = 3600
"#;
let symbols = extract_exports(src);
assert_eq!(symbols, vec!["create_auth", "AuthService", "DEFAULT_TTL"]);
}
#[test]
fn test_python_no_all() {
let src = r#"
def create_auth(config):
pass
class AuthService:
pass
def _internal():
pass
async def fetch_token():
pass
"#;
let symbols = extract_exports(src);
assert_eq!(symbols, vec!["create_auth", "AuthService", "fetch_token"]);
}
}