use smol_str::SmolStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AutoloadEntry {
pub name: SmolStr,
pub path: SmolStr,
pub is_singleton: bool,
}
#[must_use]
pub fn parse_autoloads(text: &str) -> Vec<AutoloadEntry> {
let mut entries = Vec::new();
let mut in_autoload = false;
for raw_line in text.lines() {
let line = raw_line.trim();
if line.is_empty() || line.starts_with(';') || line.starts_with('#') {
continue;
}
if let Some(inner) = line.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
in_autoload = inner.trim() == "autoload";
continue;
}
if !in_autoload {
continue;
}
let Some((name, value)) = line.split_once('=') else {
continue;
};
let name = name.trim();
if name.is_empty() {
continue;
}
let value = dequote(value.trim());
let (is_singleton, path) = match value.strip_prefix('*') {
Some(rest) => (true, rest),
None => (false, value),
};
if path.is_empty() {
continue;
}
entries.push(AutoloadEntry {
name: SmolStr::new(name),
path: SmolStr::new(path),
is_singleton,
});
}
entries
}
fn dequote(s: &str) -> &str {
let bytes = s.as_bytes();
if bytes.len() >= 2
&& (bytes[0] == b'"' || bytes[0] == b'\'')
&& bytes[bytes.len() - 1] == bytes[0]
{
&s[1..s.len() - 1]
} else {
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_singleton_and_strips_star() {
let e = parse_autoloads("[autoload]\nGame=\"*res://game.gd\"\n");
assert_eq!(e.len(), 1);
assert_eq!(e[0].name, "Game");
assert_eq!(e[0].path, "res://game.gd");
assert!(e[0].is_singleton);
}
#[test]
fn non_star_is_not_a_singleton() {
let e = parse_autoloads("[autoload]\nHelper=\"res://helper.gd\"\n");
assert_eq!(e.len(), 1);
assert_eq!(e[0].path, "res://helper.gd");
assert!(!e[0].is_singleton, "no leading * → loaded-but-not-global");
}
#[test]
fn only_the_autoload_section_is_read() {
let src = "config_version=5\n\
[application]\n\
config/name=\"Demo\"\n\
config/features=PackedStringArray(\"4.6\")\n\
\n\
[autoload]\n\
; a comment\n\
Log=\"*res://utils/system_log.gd\"\n\
Music=\"*res://music.tscn\"\n\
\n\
[rendering]\n\
renderer/rendering_method=\"gl_compatibility\"\n";
let e = parse_autoloads(src);
assert_eq!(e.len(), 2);
assert_eq!(e[0].name, "Log");
assert_eq!(e[0].path, "res://utils/system_log.gd");
assert!(e[0].is_singleton);
assert_eq!(e[1].name, "Music");
assert_eq!(e[1].path, "res://music.tscn");
}
#[test]
fn empty_or_no_autoload_section_is_empty() {
assert!(parse_autoloads("").is_empty());
assert!(parse_autoloads("[application]\nconfig/name=\"X\"\n").is_empty());
}
}