#![allow(dead_code, unused)] #![allow( clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo, clippy::print_stdout, clippy::print_stderr, clippy::empty_structs_with_brackets, clippy::semicolon_outside_block, clippy::unreachable, clippy::undocumented_unsafe_blocks, clippy::needless_return, clippy::collapsible_if )] use heck::ToPascalCase; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use rayon::prelude::*; use std::{ fs, io::Write, path::Path, process::{Command, Stdio}, }; mod advancement; mod attributes; mod bedrock_biome; mod bedrock_creative; mod biome; mod bitsets; mod block; mod block_transformer; mod carver; mod chunk_status; mod chunk_view_lut; mod composter_increase_chance; mod configured_feature; mod damage_type; mod data_component; mod dimension; mod dye_color; mod effect; mod enchantments; mod entity_pose; mod entity_status; mod entity_type; mod flower_pot_transformations; mod fluid; mod fuels; mod game_event; mod game_rules; mod item; mod jukebox_song; pub mod loot_table; mod map_color; mod map_decoration; mod material_rule; mod message_type; mod meta_data_type; mod noise_parameter; mod noise_router; mod noise_settings; mod packet; mod particle; mod placed_feature; mod potion; mod potion_brewing; mod recipe_remainder; mod recipes; mod registry; mod remap; mod scoreboard_slot; mod screen; mod sdk; mod sound; mod sound_category; mod spawn_egg; mod statistic; mod structures; mod tag; mod tracked_data; mod translations; mod version; mod villager; mod wit; mod world_event; /// Output directory where all generated Rust source files are written. pub const OUT_DIR: &str = "../../crates/pumpkin-data/src/generated"; /// Entry point for the code generator. Runs all registered builder functions in parallel /// and writes their output to [`OUT_DIR`]. pub fn main() { let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); std::env::set_current_dir(manifest_dir).expect("Failed to set current dir to manifest dir"); type BuilderFn = fn() -> TokenStream; fs::create_dir_all(OUT_DIR).expect("Failed to create output directory"); let mut build_functions: Vec<(BuilderFn, &str)> = vec![ (advancement::build, "advancement.rs"), (bedrock_biome::build, "bedrock_biome.rs"), (bedrock_creative::build, "bedrock_creative.rs"), (packet::build, "packet.rs"), (screen::build, "screen.rs"), (particle::build, "particle.rs"), (sound::build, "sound.rs"), (meta_data_type::build, "meta_data_type.rs"), (tracked_data::build, "tracked_data.rs"), (chunk_status::build, "chunk_status.rs"), (chunk_view_lut::build, "chunk_view_lut.rs"), (game_event::build, "game_event.rs"), (game_rules::build, "game_rules.rs"), (registry::build, "registry.rs"), (dimension::build, "dimension.rs"), (translations::build, "translation.rs"), (jukebox_song::build, "jukebox_song.rs"), (sound_category::build, "sound_category.rs"), (entity_pose::build, "entity_pose.rs"), (scoreboard_slot::build, "scoreboard_slot.rs"), (world_event::build, "world_event.rs"), (entity_type::build, "entity_type.rs"), (statistic::build, "statistic.rs"), (noise_parameter::build, "noise_parameter.rs"), (biome::build, "biome.rs"), (damage_type::build, "damage_type.rs"), (message_type::build, "message_type.rs"), (spawn_egg::build, "spawn_egg.rs"), (block::build, "block.rs"), (item::build, "item.rs"), (structures::build, "structures.rs"), (material_rule::build, "material_rule.rs"), (noise_settings::build, "noise_settings.rs"), (fluid::build, "fluid.rs"), (entity_status::build, "entity_status.rs"), (tag::build, "tag.rs"), (noise_router::build, "noise_router.rs"), (villager::build, "villager.rs"), ( flower_pot_transformations::build, "flower_pot_transformations.rs", ), ( composter_increase_chance::build, "composter_increase_chance.rs", ), (recipes::build, "recipes.rs"), (enchantments::build, "enchantment.rs"), (fuels::build, "fuels.rs"), (data_component::build, "data_component.rs"), (attributes::build, "attributes.rs"), (effect::build, "effect.rs"), (potion::build, "potion.rs"), (potion_brewing::build, "potion_brewing.rs"), (recipe_remainder::build, "recipe_remainder.rs"), (placed_feature::build_enum, "placed_feature.rs"), (placed_feature::build, "placed_features_generated.rs"), (configured_feature::build_enum, "configured_feature.rs"), ( configured_feature::build, "configured_features_generated.rs", ), (carver::build, "carver.rs"), (loot_table::build, "loot_table.rs"), (map_color::build, "map_color.rs"), (map_decoration::build, "map_decoration.rs"), (dye_color::build, "dye_color.rs"), (block_transformer::build, "block_transformer.rs"), ]; build_functions.extend(remap::build()); // If any arguments are given, treat them as file-stem filters. // e.g. `cargo run -- chest_loot` only regenerates chest_loot.rs. let filters: Vec = std::env::args().skip(1).collect(); let build_functions: Vec<_> = if filters.is_empty() { wit::main(); sdk::main(); build_functions } else { build_functions .into_iter() .filter(|(_, file)| { let stem = file.trim_end_matches(".rs"); filters.iter().any(|f| f == stem || f == *file) }) .collect() }; build_functions.par_iter().for_each(|(build_fn, file)| { println!("Parsing {}", file); let raw_code = build_fn().to_string(); let header = "/* This file is generated. Do not edit manually. */\n"; let final_code = format_code(&raw_code).map_or_else( |_| format!("{header}{raw_code}"), |formatted| format!("{header}{formatted}"), ); write_generated_file(&final_code, file); }); println!("Done") } /// Converts a slice of strings into a `TokenStream` of PascalCase enum variants. /// /// # Arguments /// - `array` – Slice of raw name strings to convert into variant identifiers. #[must_use] pub fn array_to_tokenstream(array: &[String]) -> TokenStream { let variants = array.iter().map(|item| { let name = format_ident!("{}", item.to_pascal_case()); quote! { #name, } }); quote! { #(#variants)* } } /// Writes generated source code to a file in [`OUT_DIR`], skipping the write if the /// content is unchanged. /// /// # Arguments /// - `new_code` – The formatted source code string to write. /// - `out_file` – The filename (relative to [`OUT_DIR`]) to write into. pub fn write_generated_file(new_code: &str, out_file: &str) { let path = Path::new(OUT_DIR).join(out_file); if path.exists() && let Ok(existing_code) = fs::read_to_string(&path) && existing_code == new_code { return; } fs::write(&path, new_code) .unwrap_or_else(|_| panic!("Failed to write to file: {}", path.display())); } /// Error returned when `rustfmt` is unavailable or fails to format code. #[derive(Debug)] pub struct RustFmtError; /// Formats a Rust source string by piping it through `rustfmt`. /// /// # Arguments /// - `unformatted_code` – Raw Rust source code to format. /// /// # Returns /// The formatted source string, or `Err(RustFmtError)` if `rustfmt` is not available /// or formatting fails. pub fn format_code(unformatted_code: &str) -> Result { let child_result = Command::new("rustfmt") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::null()) .spawn(); let Ok(mut child) = child_result else { return Err(RustFmtError); }; // Write the code to rustfmt's stdin if let Some(mut stdin) = child.stdin.take() && stdin.write_all(unformatted_code.as_bytes()).is_err() { return Err(RustFmtError); } match child.wait_with_output() { Ok(output) if output.status.success() => { String::from_utf8(output.stdout).map_err(|_| RustFmtError) } _ => Err(RustFmtError), } }