283 lines
8.4 KiB
Rust
283 lines
8.4 KiB
Rust
#![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<String> = 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<String, RustFmtError> {
|
||
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),
|
||
}
|
||
}
|