irmf_include_resolver/
lib.rs

1//! Utility for resolving `#include` directives in IRMF shaders.
2//!
3//! This crate provides logic to fetch remote includes from `lygia.xyz`
4//! and `github.com`, supporting both GLSL and WGSL shaders.
5//!
6//! For more information about the IRMF format and its capabilities, visit the
7//! [official IRMF website](https://irmf.io).
8
9use regex::Regex;
10use thiserror::Error;
11
12/// Error type for include resolution operations.
13#[derive(Error, Debug)]
14pub enum ResolverError {
15    /// A network error occurred while fetching a remote include.
16    #[error("Network error: {0}")]
17    NetworkError(#[from] reqwest::Error),
18}
19
20/// Resolves `#include` directives in the given shader source.
21///
22/// Currently supports:
23/// - `lygia.xyz/` or `lygia/` prefix (maps to `https://lygia.xyz`)
24/// - `github.com/` prefix (maps to `https://raw.githubusercontent.com`)
25///
26/// Only `.glsl` and `.wgsl` files are supported.
27pub async fn resolve_includes(source: &str) -> Result<String, ResolverError> {
28    let mut resolved_source = String::new();
29    let include_re = Regex::new("^#include\\s+\"([^\"]+)\"").unwrap();
30
31    for line in source.lines() {
32        let trimmed = line.trim();
33        if let Some(caps) = include_re.captures(trimmed) {
34            let inc = &caps[1];
35            if let Some(url) = parse_include_url(inc) {
36                if let Ok(content) = fetch_url(&url).await {
37                    resolved_source.push_str(&content);
38                    resolved_source.push('\n');
39                }
40                continue;
41            }
42        }
43        resolved_source.push_str(line);
44        resolved_source.push('\n');
45    }
46
47    Ok(resolved_source)
48}
49
50/// Parses an include path into a full URL.
51fn parse_include_url(inc: &str) -> Option<String> {
52    if !inc.ends_with(".glsl") && !inc.ends_with(".wgsl") {
53        return None;
54    }
55
56    const LYGIA_BASE_URL: &str = "https://lygia.xyz";
57    const GITHUB_RAW_PREFIX: &str = "https://raw.githubusercontent.com/";
58
59    if let Some(stripped) = inc.strip_prefix("lygia.xyz/") {
60        Some(format!("{}/{}", LYGIA_BASE_URL, stripped))
61    } else if let Some(stripped) = inc.strip_prefix("lygia/") {
62        Some(format!("{}/{}", LYGIA_BASE_URL, stripped))
63    } else if let Some(stripped) = inc.strip_prefix("github.com/") {
64        let location = stripped.replace("/blob/", "/");
65        Some(format!("{}{}", GITHUB_RAW_PREFIX, location))
66    } else {
67        None
68    }
69}
70
71/// Fetches the content of a URL.
72async fn fetch_url(url: &str) -> Result<String, ResolverError> {
73    println!("Fetching {}", url);
74    let content = reqwest::get(url).await?.text().await?;
75    Ok(content)
76}