//! An example showing how you might hook into the Rune build process. //! //! This example achieves a couple things: //! //! 1. After the YAML document is analysed we emit a warning for every [`Model`] //! that it includes //! 2. We implement dotenv-like functionality by checking if any resources (e.g. //! `foo`) have the corresponding environment variable set (e.g. `$FOO`). If //! so, the resource's [`ResourceData`] component is overridden with its //! value. //! 3. Print out some any diagnostics at the end so we can see the effects from //! step 1. use codespan_reporting::diagnostic::{Diagnostic, Severity}; use hotg_rune_compiler::{ BuildContext, Diagnostics, FeatureFlags, hooks::{ AfterCodegenContext, AfterLoweringContext, AfterTypeCheckingContext, Continuation, Hooks, }, lowering::{Model, Name, Resource, ResourceData}, }; use legion::{Entity, IntoQuery, component, systems::CommandBuffer}; use std::{fmt::Write as _, path::Path}; use env_logger::Env; fn main() { env_logger::init_from_env(Env::new().default_filter_or("debug")); let directory = std::env::args().nth(1).expect("Usage: ./extensions "); let mut build_ctx = BuildContext::for_directory(directory) .expect("Couldn't read the Runefile"); build_ctx.working_directory = project_root().join("target").join("extensions-working-dir"); let mut hooks = CustomHooks::default(); let (_world, res) = hotg_rune_compiler::build_with_hooks( build_ctx, FeatureFlags::development(), &mut hooks, ); // Print out all diagnostics. Normally you'd use the codespan_reporting // crate, but println!() is good enough for now. let diags = res.get::().unwrap(); log::info!("Printing {} diagnostics...", diags.len()); for diag in diags.iter() { let level = match diag.severity { Severity::Bug | Severity::Error => log::Level::Error, Severity::Warning => log::Level::Warn, _ => log::Level::Info, }; log::log!(level, "{:?}: {}", diag.severity, diag.message); } } #[derive(Debug, Default)] struct CustomHooks {} impl Hooks for CustomHooks { fn after_lowering( &mut self, ctx: &mut dyn AfterLoweringContext, ) -> Continuation { warn_about_every_model_in_the_rune(ctx); Continuation::Continue } fn after_type_checking( &mut self, ctx: &mut dyn AfterTypeCheckingContext, ) -> Continuation { dotenv(ctx); Continuation::Continue } fn after_codegen( &mut self, ctx: &mut dyn AfterCodegenContext, ) -> Continuation { for file in <&hotg_rune_compiler::codegen::File>::query().iter(ctx.world()) { let mut msg = String::new(); match core::str::from_utf8(&file.data) { Ok(string) => { for line in string.lines() { writeln!(msg, "\t{}", line).unwrap(); } }, Err(_) => writeln!(msg, "\t(binary)").unwrap(), } log::info!("Reading: {}\n{}", file.path.display(), msg); } Continuation::Continue } } fn warn_about_every_model_in_the_rune(ctx: &mut dyn AfterLoweringContext) { let mut diags = ctx.diagnostics_mut(); let mut model_names = <&Name>::query().filter(component::()); for name in model_names.iter(ctx.world()) { let msg = format!("The Rune contains a model called \"{}\"", name); diags.push(Diagnostic::warning().with_message(msg)); } } /// Implement `dotenv`-like behaviour by looking for the environment variable /// that corresponds to a particular [`Resource`] and setting its /// [`ResourceData`] if that variable is set. fn dotenv(ctx: &mut dyn AfterTypeCheckingContext) { let (world, res) = ctx.world_and_resources(); let mut cmd = CommandBuffer::new(world); // create a query which will look for all named entities with a "Resource" // component. let mut query = <(Entity, &Name)>::query().filter(component::()); for (&ent, name) in query.iter(world) { let variable_name = name.to_uppercase(); if let Ok(value) = std::env::var(variable_name) { println!( "Overriding the \"{}\" resource and setting it to \"{}\"", name, value ); cmd.add_component(ent, ResourceData::from(value.into_bytes())); } } cmd.flush(world, res); } fn project_root() -> &'static Path { for ancestor in Path::new(env!("CARGO_MANIFEST_DIR")).ancestors() { if ancestor.join(".git").exists() { return ancestor; } } panic!("Unable to determine the project's root directory"); }