kmeans-color-gpu-preprocessor 0.1.0

A shader preprocessor, to replace #includes in shaders by appropriate files used for the kmeans-color-gpu crate.
Documentation
use std::{error::Error, fs, path::PathBuf};

use codespan_reporting::{
    diagnostic::{Diagnostic, Label},
    files::SimpleFile,
    term::{
        self,
        termcolor::{ColorChoice, StandardStream},
    },
};
use naga::{valid::ValidationFlags, WithSpan};

pub fn validate_shaders(shaders: &[PathBuf]) -> anyhow::Result<()> {
    for shader_path in shaders {
        let shader = String::from_utf8(fs::read(shader_path)?)?;

        let result = naga::front::wgsl::parse_str(&shader);

        let module = match result {
            Ok(v) => Some(v),
            Err(e) => {
                let path = shader_path.to_string_lossy();
                e.emit_to_stderr_with_path(&shader, &path);
                None
            }
        };

        if let Some(module) = module {
            let validation_caps = naga::valid::Capabilities::all()
                & !(naga::valid::Capabilities::CLIP_DISTANCE
                    | naga::valid::Capabilities::CULL_DISTANCE);

            let validation_flags = ValidationFlags::all();

            let filename = shader_path
                .file_name()
                .and_then(std::ffi::OsStr::to_str)
                .unwrap_or("input");

            match naga::valid::Validator::new(validation_flags, validation_caps).validate(&module) {
                Ok(_) => println!("Shader {filename} is valid"),
                Err(e) => {
                    eprintln!("Shader {filename} invalid");
                    emit_annotated_error(&e, filename, &shader);
                }
            }
        }
    }

    Ok(())
}

pub fn emit_annotated_error<E: Error>(ann_err: &WithSpan<E>, filename: &str, source: &str) {
    let files = SimpleFile::new(filename, source);
    let config = codespan_reporting::term::Config::default();
    let writer = StandardStream::stderr(ColorChoice::Auto);

    let diagnostic = Diagnostic::error().with_labels(
        ann_err
            .spans()
            .map(|(span, desc)| {
                Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned())
            })
            .collect(),
    );

    term::emit(&mut writer.lock(), &config, &files, &diagnostic).expect("cannot write error");
}