use crate::assemble::{Loc, line_no};
use std::path::{Path, PathBuf};
pub fn parse(line: &str) -> Option<&str> {
let rest = line.trim_start().strip_prefix("#include")?;
if !rest.starts_with([' ', '\t']) {
return None;
}
let rest = rest.trim_start();
let close = match rest.as_bytes().first()? {
b'"' => '"',
b'<' => '>',
_ => return None,
};
let inner = &rest[1..];
let end = inner.find(close)?;
Some(&inner[..end])
}
pub fn expand(line: &str, dir: &Path, seen: &mut Vec<PathBuf>) -> Option<Vec<(String, Loc)>> {
let rel = parse(line)?;
let canon = dir.join(rel).canonicalize().ok()?;
if seen.contains(&canon) {
return Some(Vec::new());
}
let content = std::fs::read_to_string(&canon).ok()?;
let inc_dir = canon.parent().unwrap_or(Path::new(".")).to_path_buf();
seen.push(canon.clone());
let mut out = Vec::new();
for (i, l) in content.lines().enumerate() {
match expand(l, &inc_dir, seen) {
Some(nested) => out.extend(nested),
None => out.push((
l.to_string(),
Loc {
path: canon.clone(),
line: line_no(i),
},
)),
}
}
seen.pop();
Some(out)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn parse_extracts_quoted_and_angled_paths() {
assert_eq!(parse("#include \"common.glsl\""), Some("common.glsl"));
assert_eq!(parse(" #include <lib/util.glsl>"), Some("lib/util.glsl"));
assert_eq!(parse("#include\t\"a.glsl\""), Some("a.glsl"));
assert_eq!(parse("#version 300 es"), None);
assert_eq!(parse("#includexyz \"a\""), None);
assert_eq!(parse("// #include \"a.glsl\"".trim_start()), None);
}
#[test]
fn expand_splices_recursively_and_breaks_cycles() {
static N: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
let dir = std::env::temp_dir().join(format!(
"glslint-inc-{}-{}",
std::process::id(),
N.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
));
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(
dir.join("a.glsl"),
"float a() { return 1.0; }\n#include \"b.glsl\"\n",
)
.unwrap();
std::fs::write(
dir.join("b.glsl"),
"float b() { return 2.0; }\n#include \"a.glsl\"\n",
)
.unwrap();
let mut seen = Vec::new();
let out = expand("#include \"a.glsl\"", &dir, &mut seen).unwrap();
let text: Vec<&str> = out.iter().map(|(l, _)| l.as_str()).collect();
assert!(text.iter().any(|l| l.contains("float a()")));
assert!(text.iter().any(|l| l.contains("float b()")));
assert_eq!(text.iter().filter(|l| l.contains("float a()")).count(), 1);
let b_line = out.iter().find(|(l, _)| l.contains("float b()")).unwrap();
assert!(b_line.1.path.ends_with("b.glsl"));
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn unresolvable_include_is_left_for_the_caller() {
let mut seen = Vec::new();
assert!(
expand(
"#include \"nope-does-not-exist.glsl\"",
Path::new("/tmp"),
&mut seen
)
.is_none()
);
}
}