diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..a471c17 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,72 @@ +max_width = 160 +hard_tabs = false +tab_spaces = 4 +newline_style = "Unix" +indent_style = "Block" +use_small_heuristics = "Off" +chain_width = 85 +wrap_comments = true +format_code_in_doc_comments = false +comment_width = 80 +normalize_comments = true +normalize_doc_attributes = true +format_strings = true +format_macro_matchers = true +format_macro_bodies = true +hex_literal_case = "Preserve" +empty_item_single_line = true +struct_lit_single_line = true +struct_lit_width = 18 +fn_single_line = true +where_single_line = false +imports_indent = "Block" +imports_layout = "Mixed" +imports_granularity = "Module" +group_imports = "StdExternalCrate" +reorder_imports = true +reorder_modules = true +reorder_impl_items = true +type_punctuation_density = "Wide" +space_before_colon = false +space_after_colon = true +spaces_around_ranges = false +binop_separator = "Front" +remove_nested_parens = true +combine_control_expr = true +short_array_element_width_threshold = 16 +overflow_delimited_expr = true +struct_field_align_threshold = 0 +enum_discrim_align_threshold = 0 +match_arm_blocks = true +match_arm_leading_pipes = "Never" +force_multiline_blocks = false +fn_args_layout = "Tall" +brace_style = "SameLineWhere" +control_brace_style = "AlwaysSameLine" +trailing_semicolon = true +trailing_comma = "Vertical" +match_block_trailing_comma = true +blank_lines_upper_bound = 1 +blank_lines_lower_bound = 0 +edition = "2021" +version = "One" +inline_attribute_width = 0 +format_generated_files = false +merge_derives = true +use_try_shorthand = true +use_field_init_shorthand = true +force_explicit_abi = true +condense_wildcard_suffixes = false +color = "Auto" +required_version = "1.5.1" +unstable_features = false +disable_all_formatting = false +skip_children = false +hide_parse_errors = false +error_on_line_overflow = false +error_on_unformatted = false +ignore = [ + "crates/block_parser" +] +emit_mode = "Files" +make_backup = false diff --git a/src/blocks/intermediary/collisions.rs b/src/blocks/intermediary/collisions.rs index 107ad8e..a5c5f28 100644 --- a/src/blocks/intermediary/collisions.rs +++ b/src/blocks/intermediary/collisions.rs @@ -1,10 +1,9 @@ use ahash::{AHashMap, AHashSet}; +use serde::de::{DeserializeSeed, IgnoredAny, Visitor}; use serde::Deserialize; -use serde::de::{Visitor, IgnoredAny, DeserializeSeed}; - -use crate::blocks::raw::property::{EnumProperty, PropertyKind}; use super::rules::ModernPropertyRules; +use crate::blocks::raw::property::{EnumProperty, PropertyKind}; /// A collection of possible property collisions in raw data. /// @@ -27,9 +26,7 @@ pub struct CollisionList<'raw> { } impl<'raw> CollisionList<'raw> { - pub fn should_exit(&self) -> bool { - !self.by_name.is_empty() - } + pub fn should_exit(&self) -> bool { !self.by_name.is_empty() } /// Displays a summary of the different collisions found in the raw data. pub fn display(&self) { @@ -62,41 +59,42 @@ pub struct CollisionRuleProvider<'a, 'raw>(Option<&'a ModernPropertyRules<'raw>> impl<'a, 'raw> CollisionRuleProvider<'a, 'raw> { /// Constructs a new `CollisionRuleProvider` given a set of rules - pub fn new(rules: Option<&'a ModernPropertyRules<'raw>>) -> Self { - Self(rules) - } + pub fn new(rules: Option<&'a ModernPropertyRules<'raw>>) -> Self { Self(rules) } /// This transformation does two checks: - /// - First it makes sure the property name is not `"type"`, this will get transformed into `"kind"` - /// - Secondly it uses the rules, if present, to replace the property name if there's a match based on the property values + /// - First it makes sure the property name is not `"type"`, this will get + /// transformed into `"kind"` + /// - Secondly it uses the rules, if present, to replace the property name + /// if there's a match based on the property values pub fn transform<'b, I>(&'b self, properties: I) -> impl Iterator)> + 'b where I: IntoIterator)> + 'b, { - properties.into_iter() - .map(|(name, property)| { - let name = if name == "type" { "kind" } else { name }; - if let Some(rules) = self.0 { - rules.transform(name, property) - } else { - (name, property) - } - }) + properties.into_iter().map(|(name, property)| { + let name = if name == "type" { + "kind" + } else { + name + }; + if let Some(rules) = self.0 { + rules.transform(name, property) + } else { + (name, property) + } + }) } } impl<'a, 'raw, 'de: 'raw> Visitor<'de> for CollisionRuleProvider<'a, 'raw> { type Value = CollisionList<'raw>; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a 1.13+ minecraft-generated block list") - } + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("a 1.13+ minecraft-generated block list") } /// Simply collect all the properties and keep the ones /// that share either name or values fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, + where + A: serde::de::MapAccess<'de>, { let mut by_name = AHashMap::<&'raw str, AHashSet>::new(); let mut by_values = AHashMap::, AHashSet<&'raw str>>::new(); @@ -115,8 +113,8 @@ impl<'a, 'raw, 'de: 'raw> Visitor<'de> for CollisionRuleProvider<'a, 'raw> { } else { by_name.insert(name, AHashSet::from([property])); } - } - _ => {} + }, + _ => {}, } } } @@ -124,10 +122,7 @@ impl<'a, 'raw, 'de: 'raw> Visitor<'de> for CollisionRuleProvider<'a, 'raw> { by_name.retain(|_, set| set.len() > 1); by_values.retain(|_, set| set.len() > 1); - Ok(CollisionList { - by_name, - by_values, - }) + Ok(CollisionList { by_name, by_values }) } } @@ -136,7 +131,7 @@ impl<'a, 'raw, 'de: 'raw> DeserializeSeed<'de> for CollisionRuleProvider<'a, 'ra fn deserialize(self, deserializer: D) -> Result where - D: serde::Deserializer<'de> + D: serde::Deserializer<'de>, { deserializer.deserialize_map(self) } @@ -149,4 +144,3 @@ struct RawBlockData<'raw> { #[serde(rename = "states")] _states: IgnoredAny, } - diff --git a/src/blocks/intermediary/data.rs b/src/blocks/intermediary/data.rs index 22262d3..845d8d7 100644 --- a/src/blocks/intermediary/data.rs +++ b/src/blocks/intermediary/data.rs @@ -1,6 +1,8 @@ use ahash::RandomState; use hashlink::LinkedHashMap; use serde::{Deserialize, Serialize}; + +use super::MetaData; use crate::blocks::raw::property::EnumProperty; use crate::util::identifier::Identifier; @@ -14,23 +16,27 @@ pub type BlockList<'raw> = LinkedHashMap, ModernBlockData<'raw> /// The compact blockstates format. /// /// In this format there are two lists: -/// - The first list is a list of all possible properties. -/// i.e. a property name mapped to more than one property value. -/// - The second list is a list of all the blocks. -/// i.e. a collection of blockstates, if the block has one or more properties -/// it will have these listed referring to the first list when it's an enum property. -/// If the block has more than one blockstate, there will also be a `default_id` field. +/// - The first list is a list of all possible properties. i.e. a property name +/// mapped to more than one property value. +/// - The second list is a list of all the blocks. i.e. a collection of +/// blockstates, if the block has one or more properties it will have these +/// listed referring to the first list when it's an enum property. If the +/// block has more than one blockstate, there will also be a `default_id` +/// field. #[derive(Debug, Serialize, Deserialize)] pub struct ModernBlockList<'raw> { + #[serde(borrow, skip_serializing_if = "Option::is_none")] + pub metadata: Option>, #[serde(borrow)] - properties: PropertyList<'raw>, + pub properties: PropertyList<'raw>, #[serde(borrow)] - blocks: BlockList<'raw>, + pub blocks: BlockList<'raw>, } impl<'raw> ModernBlockList<'raw> { - pub(crate) fn new(properties: PropertyList<'raw>, blocks: BlockList<'raw>) -> Self { + pub(crate) fn new(metadata: Option>, properties: PropertyList<'raw>, blocks: BlockList<'raw>) -> Self { ModernBlockList { + metadata, properties, blocks, } @@ -39,17 +45,18 @@ impl<'raw> ModernBlockList<'raw> { /// Compact way of identifying block data. /// -/// Only if the block has one ore more properties, the [`ModernBlockData::kinds`] -/// will be serialized. If the block has more than one blockstate, -/// a default_id field will be serialized as well. +/// Only if the block has one ore more properties, the +/// [`ModernBlockData::kinds`] will be serialized. If the block has more than +/// one blockstate, a default_id field will be serialized as well. #[derive(Debug, Serialize, Deserialize)] pub struct ModernBlockData<'raw> { #[serde(borrow, skip_serializing_if = "LinkedHashMap::is_empty", rename = "properties")] - kinds: LinkedHashMap<&'raw str, PropertyValue<'raw>, RandomState>, + #[serde(default)] + pub kinds: LinkedHashMap<&'raw str, PropertyValue<'raw>, RandomState>, #[serde(rename = "base")] - base_id: i32, + pub base_id: i32, #[serde(skip_serializing_if = "Option::is_none", rename = "default")] - default_id: Option, + pub default_id: Option, } impl<'raw> ModernBlockData<'raw> { @@ -57,7 +64,7 @@ impl<'raw> ModernBlockData<'raw> { ModernBlockData { kinds, base_id, - default_id + default_id, } } } @@ -65,22 +72,15 @@ impl<'raw> ModernBlockData<'raw> { #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum PropertyValue<'raw> { - Bool(&'raw str), Range([u8; 2]), #[serde(borrow)] - Enum(&'raw str), + Text(&'raw str), } impl<'raw> PropertyValue<'raw> { - pub fn bool() -> Self { - Self::Bool("bool") - } + pub fn bool() -> Self { Self::Text("bool") } - pub fn enum_name(value: &'raw str) -> Self { - Self::Enum(value) - } - - pub fn range(start: u8, end: u8) -> Self { - Self::Range([start, end]) - } + pub fn enum_name(value: &'raw str) -> Self { Self::Text(value) } + + pub fn range(start: u8, end: u8) -> Self { Self::Range([start, end]) } } diff --git a/src/blocks/intermediary/metadata.rs b/src/blocks/intermediary/metadata.rs new file mode 100644 index 0000000..44ca444 --- /dev/null +++ b/src/blocks/intermediary/metadata.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct MetaData<'raw> { + pub id: i32, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option<&'raw str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub note: Option<&'raw str>, +} + +impl<'raw> MetaData<'raw> { + pub fn new(id: i32, name: Option<&'raw str>, note: Option<&'raw str>) -> Self { Self { id, name, note } } +} diff --git a/src/blocks/intermediary/mod.rs b/src/blocks/intermediary/mod.rs index e814dc2..231e83d 100644 --- a/src/blocks/intermediary/mod.rs +++ b/src/blocks/intermediary/mod.rs @@ -1,4 +1,6 @@ pub mod collisions; pub mod data; +pub mod metadata; pub mod rules; +pub use metadata::MetaData; diff --git a/src/blocks/intermediary/rules.rs b/src/blocks/intermediary/rules.rs index 4e52853..9ac188c 100644 --- a/src/blocks/intermediary/rules.rs +++ b/src/blocks/intermediary/rules.rs @@ -15,7 +15,7 @@ impl<'raw> ModernPropertyRules<'raw> { pub fn transform(&self, name: &'raw str, property: PropertyKind<'raw>) -> (&'raw str, PropertyKind<'raw>) { match &property { PropertyKind::Enum(enum_property) => (self.rule_data.get(&enum_property).cloned().unwrap_or(name), property), - _ => (name, property) + _ => (name, property), } } } @@ -23,10 +23,7 @@ impl<'raw> ModernPropertyRules<'raw> { impl<'raw> From, RandomState>> for ModernPropertyRules<'raw> { fn from(other: LinkedHashMap<&'raw str, EnumProperty<'raw>, RandomState>) -> Self { Self { - rule_data: other.into_iter() - .map(|(name, values)| (values, name)) - .collect(), + rule_data: other.into_iter().map(|(name, values)| (values, name)).collect(), } } } - diff --git a/src/blocks/raw/de.rs b/src/blocks/raw/de.rs index 0cb2405..69acc33 100644 --- a/src/blocks/raw/de.rs +++ b/src/blocks/raw/de.rs @@ -1,54 +1,56 @@ use ahash::RandomState; use hashlink::LinkedHashMap; -use serde::de::{Visitor, DeserializeSeed}; +use serde::de::{DeserializeSeed, Visitor}; -use crate::blocks::intermediary::data::{ModernBlockList, PropertyValue, ModernBlockData}; +use super::property::PropertyKind; +use super::RawBlockData; +use crate::blocks::intermediary::data::{ModernBlockData, ModernBlockList, PropertyValue}; use crate::blocks::intermediary::rules::ModernPropertyRules; +use crate::blocks::intermediary::MetaData; use crate::util::identifier::Identifier; -use super::RawBlockData; -use super::property::PropertyKind; - -pub struct CompactRuleProvider<'a, 'raw>(Option<&'a ModernPropertyRules<'raw>>); +pub struct CompactRuleProvider<'a, 'raw> { + pub rules: Option<&'a ModernPropertyRules<'raw>>, + pub metadata: Option>, +} impl<'a, 'raw> CompactRuleProvider<'a, 'raw> { - pub fn new(rules: Option<&'a ModernPropertyRules<'raw>>) -> Self { - Self(rules) - } + pub fn new(rules: Option<&'a ModernPropertyRules<'raw>>, metadata: Option>) -> Self { Self { rules, metadata } } /// This transformation does two checks: /// - First it makes sure the property name is not `"type"`, this will get - /// transformed into `"kind"` + /// transformed into `"kind"` /// - Secondly it uses the rules, if present, to replace the property name - /// if there's a match based on the property values + /// if there's a match based on the property values pub fn transform<'b, I>(&'b self, properties: I) -> impl Iterator)> + 'b where I: IntoIterator)> + 'b, { - properties.into_iter() - .map(|(name, property)| { - let name = if name == "type" { "kind" } else { name }; - if let Some(rules) = self.0 { - rules.transform(name, property) - } else { - (name, property) - } - }) + properties.into_iter().map(|(name, property)| { + let name = if name == "type" { + "kind" + } else { + name + }; + if let Some(rules) = self.rules { + rules.transform(name, property) + } else { + (name, property) + } + }) } } impl<'a, 'raw, 'de: 'raw> Visitor<'de> for CompactRuleProvider<'a, 'raw> { type Value = ModernBlockList<'raw>; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a 1.13+ minecraft-generated block list") - } + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("a 1.13+ minecraft-generated block list") } /// This implementation verifies the data /// and makes sure it follows the expected pattern. fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, + where + A: serde::de::MapAccess<'de>, { // Overall lists that will be passed to ModernBlockList let mut properties = LinkedHashMap::with_hasher(RandomState::default()); @@ -57,10 +59,10 @@ impl<'a, 'raw, 'de: 'raw> Visitor<'de> for CompactRuleProvider<'a, 'raw> { while let Some((identifier, block)) = map.next_entry::, RawBlockData<'raw>>()? { let property_count = block.property_count(); let state_count = block.state_count(); - - // make sure there is the correct amound of blockstates + + // make sure there is the correct amount of blockstates if block.states.len() != state_count { - return Err(serde::de::Error::invalid_length(block.states.len(), &format!("{} entries", state_count).as_str())) + return Err(serde::de::Error::invalid_length(block.states.len(), &format!("{} entries", state_count).as_str())); } let base_id = block.states[0].id; @@ -68,9 +70,7 @@ impl<'a, 'raw, 'de: 'raw> Visitor<'de> for CompactRuleProvider<'a, 'raw> { for state in &block.states { // Make sure the blockstate has the correct amount of property values if state.properties.len() != property_count { - return Err(serde::de::Error::invalid_length( - state.properties.len(), &format!("{} entries", property_count).as_str(), - )); + return Err(serde::de::Error::invalid_length(state.properties.len(), &format!("{} entries", property_count).as_str())); } // Check the network values for consistency, @@ -81,38 +81,33 @@ impl<'a, 'raw, 'de: 'raw> Visitor<'de> for CompactRuleProvider<'a, 'raw> { let mut prev_factor = 1; let mut offset = 0; for (&name, &value) in state.properties.iter().rev() { - let values = block.properties.iter().find_map(|(&a, b)| if a == name { Some(b) } else { None }) - .ok_or(serde::de::Error::custom( - format!("found a non-matching a property \"{}\" for \"{}\"", name, identifier) - ))?; + let values = block + .properties + .iter() + .find_map(|(&a, b)| { + if a == name { + Some(b) + } else { + None + } + }) + .ok_or(serde::de::Error::custom(format!("found a non-matching a property \"{}\" for \"{}\"", name, identifier)))?; match values.iter().position(|&x| x == value) { Some(index) => { factor *= prev_factor; prev_factor = values.len(); offset += factor * index; - } + }, None => { - return Err(serde::de::Error::custom( - format!( - "invalid property value \"{}\" found while deserializing \"{}\"", - value, - identifier - ), - )); - } + return Err(serde::de::Error::custom(format!("invalid property value \"{}\" found while deserializing \"{}\"", value, identifier))); + }, } } // Step 2: make sure that the base id plus offset equals the given blockstate id let id = base_id + offset as i32; if id != state.id { - return Err(serde::de::Error::custom( - format!("incorrect id match for \"{}\", found {} while expecting {}", - identifier, - state.id, - id - ), - )); + return Err(serde::de::Error::custom(format!("incorrect id match for \"{}\", found {} while expecting {}", identifier, state.id, id))); } // try to find a default blockstate other than base id if state.default { @@ -121,45 +116,43 @@ impl<'a, 'raw, 'de: 'raw> Visitor<'de> for CompactRuleProvider<'a, 'raw> { } // only if base id is different from default_id should default_id be remembered // (see below when adding new block) - let default_id = default_id.and_then(|id| if id == base_id { - None - } else { - Some(id) + let default_id = default_id.and_then(|id| { + if id == base_id { + None + } else { + Some(id) + } }); // Extend the list of properties with the properties of this block - properties.extend( - self.transform(block.properties()) - .filter_map(|(name, property)| { - match property { - PropertyKind::Enum(property) => Some((name, property)), - _ => None, + properties.extend(self.transform(block.properties()).filter_map(|(name, property)| match property { + PropertyKind::Enum(property) => Some((name, property)), + _ => None, + })); + + // Collect the property types to add to the compacted form of this block (see + // below) + let properties = block + .properties() + .map(|(name, kind)| { + if name == "type" { + ("kind", kind) + } else { + (name, kind) } }) - ); - - // Collect the property types to add to the compacted form of this block (see below) - let properties = block.properties() - .map(|(name, kind)| if name == "type" { - ("kind", kind) - } else { - (name, kind) - }) .map(|(name, kind)| { - ( - name, - match kind { - PropertyKind::Bool => PropertyValue::bool(), - PropertyKind::Int([start, end]) => PropertyValue::range(start, end), - PropertyKind::Enum(_) => { - if let Some(rules) = self.0 { - PropertyValue::enum_name(rules.transform(name, kind).0) - } else { - PropertyValue::enum_name(name) - } + (name, match kind { + PropertyKind::Bool => PropertyValue::bool(), + PropertyKind::Int([start, end]) => PropertyValue::range(start, end), + PropertyKind::Enum(_) => { + if let Some(rules) = self.rules { + PropertyValue::enum_name(rules.transform(name, kind).0) + } else { + PropertyValue::enum_name(name) } }, - ) + }) }) .collect(); @@ -167,7 +160,7 @@ impl<'a, 'raw, 'de: 'raw> Visitor<'de> for CompactRuleProvider<'a, 'raw> { blocks.insert(identifier, ModernBlockData::new(properties, base_id, default_id)); } - Ok(ModernBlockList::new(properties, blocks)) + Ok(ModernBlockList::new(self.metadata, properties, blocks)) } } @@ -176,9 +169,8 @@ impl<'a, 'raw, 'de: 'raw> DeserializeSeed<'de> for CompactRuleProvider<'a, 'raw> fn deserialize(self, deserializer: D) -> Result where - D: serde::Deserializer<'de> + D: serde::Deserializer<'de>, { deserializer.deserialize_map(self) } } - diff --git a/src/blocks/raw/mod.rs b/src/blocks/raw/mod.rs index c054e01..e159473 100644 --- a/src/blocks/raw/mod.rs +++ b/src/blocks/raw/mod.rs @@ -17,9 +17,7 @@ pub struct RawBlockData<'raw> { } impl<'raw> RawBlockData<'raw> { - pub fn property_count(&self) -> usize { - self.properties.len() - } + pub fn property_count(&self) -> usize { self.properties.len() } pub fn state_count(&self) -> usize { let mut number = 1; @@ -29,12 +27,10 @@ impl<'raw> RawBlockData<'raw> { number } - pub fn properties<'b>(&'b self) -> impl Iterator)> + 'b { + pub fn properties<'b>(&'b self) -> impl Iterator)> + 'b { self.properties .iter() - .filter_map(|(name, values)| { - PropertyKind::try_from(values.as_slice()).ok().map(|property| (*name, property)) - }) + .filter_map(|(name, values)| PropertyKind::try_from(values.as_slice()).ok().map(|property| (*name, property))) } } @@ -46,4 +42,3 @@ pub struct RawBlockState<'raw> { #[serde(default)] default: bool, } - diff --git a/src/blocks/raw/property.rs b/src/blocks/raw/property.rs index 4a2bb1b..a4dd18b 100644 --- a/src/blocks/raw/property.rs +++ b/src/blocks/raw/property.rs @@ -18,7 +18,7 @@ pub enum PropertyKindParseError { #[error("First value was a boolean but second value was not")] BooleanInvalidType, #[error("Invalid integer value")] - IntError(#[from] ParseIntError) + IntError(#[from] ParseIntError), } impl<'b, 'raw: 'b> TryFrom<&'b [&'raw str]> for PropertyKind<'raw> { @@ -71,7 +71,7 @@ impl<'raw> PropertyKind<'raw> { match self { PropertyKind::Bool => 2, PropertyKind::Int(range) => (range[1] - range[0] + 1) as usize, - PropertyKind::Enum(values) => values.fields().len() + PropertyKind::Enum(values) => values.fields().len(), } } } @@ -91,23 +91,15 @@ impl<'raw> EnumProperty<'raw> { } } - pub fn fields<'b>(&'b self) -> &'b [&'raw str] { - &self.values - } + pub fn fields<'b>(&'b self) -> &'b [&'raw str] { &self.values } } impl<'raw> From> for EnumProperty<'raw> { - fn from(values: Vec<&'raw str>) -> Self { - Self { - values, - } - } + fn from(values: Vec<&'raw str>) -> Self { Self { values } } } impl<'raw> Into> for EnumProperty<'raw> { - fn into(self) -> Vec<&'raw str> { - self.values - } + fn into(self) -> Vec<&'raw str> { self.values } } #[cfg(test)] @@ -121,11 +113,6 @@ mod tests { let values = vec!["value1", "value2"]; let enum_property = EnumProperty::new(&values); - assert_de_tokens(&enum_property, &[ - Token::Seq { len: Some(2) }, - Token::BorrowedStr("value1"), - Token::BorrowedStr("value2"), - Token::SeqEnd, - ]); + assert_de_tokens(&enum_property, &[Token::Seq { len: Some(2) }, Token::BorrowedStr("value1"), Token::BorrowedStr("value2"), Token::SeqEnd]); } } diff --git a/src/cmd/info.rs b/src/cmd/info.rs new file mode 100644 index 0000000..2bae08a --- /dev/null +++ b/src/cmd/info.rs @@ -0,0 +1,103 @@ +use anyhow::Result; +use clap::Args; + +use crate::blocks::intermediary::data::{ModernBlockData, ModernBlockList, PropertyValue}; +use crate::util::file::InputFile; + +#[derive(Args, Debug)] +pub struct InfoCommand { + /// File containing intermediary data from mc-data + input: InputFile, +} + +impl InfoCommand { + pub fn display_info(&self) -> Result<()> { + let data: ModernBlockList = self.input.deserialized()?; + + if let Some(meta) = data.metadata { + if let Some(name) = meta.name { + println!("Minecraft blockdata version {} ({})", meta.id, name); + } else { + println!("Minecraft blockdata version {}", meta.id); + } + + if let Some(note) = meta.note { + println!("Note: {}", note); + } + } else { + println!("Minecraft blockdata version UNKNOWN"); + } + + println!("Loaded {} blocks successfully \u{2705}", data.blocks.len()); + println!("There are {} enum properties present \u{2705}\n------", data.properties.len()); + + // healthcheck + let mut blocks: Vec = data.blocks.into_iter().map(|(_, value)| value).collect(); + blocks.sort_by_key(|data| data.base_id); + + let mut unknown = Vec::new(); + let mut missing = Vec::new(); + let mut counter = 0; + for block in blocks { + if counter != block.base_id { + let difference = block.base_id - counter; + if difference == 1 { + missing.push(format!("{}", counter)); + } else { + missing.push(format!("{}..{}", counter, block.base_id)); + } + counter = block.base_id; + } + + let mut state_count = 1; + for (_, property) in &block.kinds { + match property { + PropertyValue::Range(range) => state_count *= (range[1] - range[0] + 1) as usize, + PropertyValue::Text(text) => { + if text == &"bool" { + state_count *= 2; + } else { + match data.properties.get(text) { + Some(property) => state_count *= property.fields().len(), + None => unknown.push(text.to_string()), + } + } + }, + } + } + counter += state_count as i32; + } + + if unknown.is_empty() && missing.is_empty() { + println!("Healthcheck success! \u{2705}"); + } else { + println!("Healthcheck failed: \u{274C}"); + if !unknown.is_empty() { + println!("The follow properties could not be found: \u{274C}"); + unknown.into_iter().fold(true, |first, elem| { + if !first { + print!(", {}", elem); + } else { + print!("{}", elem); + } + false + }); + println!(); + } + if !missing.is_empty() { + println!("The following blockstates could not be found: \u{274C}"); + missing.into_iter().fold(true, |first, elem| { + if !first { + print!(", {}", elem); + } else { + print!("{}", elem); + } + false + }); + println!(); + } + } + + Ok(()) + } +} diff --git a/src/cmd/intermediary.rs b/src/cmd/intermediary.rs index 7da79fb..ab79735 100644 --- a/src/cmd/intermediary.rs +++ b/src/cmd/intermediary.rs @@ -1,11 +1,12 @@ -use clap::Args; use anyhow::Result; +use clap::Args; use serde::de::DeserializeSeed; use serde_json::Deserializer; -use crate::blocks::intermediary::collisions::{CollisionRuleProvider, CollisionList}; +use crate::blocks::intermediary::collisions::{CollisionList, CollisionRuleProvider}; use crate::blocks::intermediary::data::ModernBlockList; use crate::blocks::intermediary::rules::ModernPropertyRules; +use crate::blocks::intermediary::MetaData; use crate::blocks::raw::de::CompactRuleProvider; use crate::util::file::{InputFile, OutputFile}; @@ -24,7 +25,15 @@ pub struct IntermediaryCommand { rules: Option, #[clap(short, long)] output: Option, - #[clap(long = "no-pretty")] + /// The ID of the minecraft version the raw data comes from (e.g 2730) + #[clap(long)] + id: Option, + /// The pretty version number (e.g 1.17.1) + #[clap(short = 'd', long, requires = "id")] + display_name: Option, + #[clap(long, requires = "id")] + note: Option, + #[clap(long)] /// Does not pretty-print the resulting json data no_pretty: bool, } @@ -35,24 +44,27 @@ impl IntermediaryCommand { let data = self.input.data(); // Load rules - let rules: Option = self.rules - .as_ref() - .map(|rules| rules.deserialized()).transpose()?; + let rules: Option = self.rules.as_ref().map(|rules| rules.deserialized()).transpose()?; // Property collisions eprintln!("Checking for property collisions..."); let collisions = CollisionRuleProvider::new(rules.as_ref()); - let collisions: CollisionList = collisions.deserialize(&mut Deserializer::from_str(data))?; + let collisions: CollisionList = collisions.deserialize(&mut Deserializer::from_str(data))?; collisions.display(); if collisions.should_exit() { eprintln!("Could not continue due to one or more collisions in block properties.\nPlease specify a rules file to resolve these"); - return Ok(()) + return Ok(()); } eprintln!("No serious collisions found, slight inefficiencies will have been signaled by now. \u{2705}"); // Compact data and print to output - let compacter = CompactRuleProvider::new(rules.as_ref()); + let metadata = if let Some(id) = self.id { + Some(MetaData::new(id, self.display_name.as_deref(), self.note.as_deref())) + } else { + None + }; + let compacter = CompactRuleProvider::new(rules.as_ref(), metadata); let modern_data: ModernBlockList = compacter.deserialize(&mut Deserializer::from_str(data))?; match &self.output { @@ -67,7 +79,7 @@ impl IntermediaryCommand { } else { eprintln!("Aborted"); } - } + }, None => { let result = if self.no_pretty { serde_json::to_string(&modern_data)? @@ -76,10 +88,9 @@ impl IntermediaryCommand { }; println!("{}", result); eprintln!("========\nSuccessfully compacted data \u{2705}"); - } + }, } Ok(()) } } - diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index ff4737c..35d4067 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,3 +1,5 @@ -pub mod intermediary; +mod info; +mod intermediary; +pub use info::InfoCommand; pub use intermediary::IntermediaryCommand; diff --git a/src/main.rs b/src/main.rs index 291ebac..fe3ce4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use anyhow::Context; use clap::{Parser, Subcommand}; -use cmd::IntermediaryCommand; +use cmd::{InfoCommand, IntermediaryCommand}; pub mod blocks; pub mod cmd; @@ -16,14 +16,14 @@ pub struct Cli { #[derive(Subcommand, Debug)] pub enum SubCommands { Intermediary(IntermediaryCommand), + Info(InfoCommand), } fn main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { - SubCommands::Intermediary(cmd) => { - cmd.generate_intermediate().with_context(|| "Error while generating data") - } + SubCommands::Intermediary(cmd) => cmd.generate_intermediate().context("Error while generating data"), + SubCommands::Info(cmd) => cmd.display_info().context("Error while displaying info"), } } diff --git a/src/util/file/input.rs b/src/util/file/input.rs index 9ac94a4..b966f3e 100644 --- a/src/util/file/input.rs +++ b/src/util/file/input.rs @@ -1,13 +1,13 @@ use std::io; use std::path::PathBuf; use std::str::FromStr; -use anyhow::Context; +use anyhow::Context; use serde::Deserialize; /// This is the representation of an /// input file to be parsed by clap. Upon -/// parsing, this will open and read the specified file if posssible. +/// parsing, this will open and read the specified file if possible. #[derive(Debug)] pub struct InputFile { name: PathBuf, @@ -16,20 +16,16 @@ pub struct InputFile { impl InputFile { /// Returns the name of the file - pub fn name(&self) -> &PathBuf { - &self.name - } + pub fn name(&self) -> &PathBuf { &self.name } /// Convenience function to automatically deserialize pub fn deserialized<'raw, T: Deserialize<'raw>>(&'raw self) -> anyhow::Result { - let result = serde_json::from_str::(&self.contents).with_context(|| "Could not deserialize to the requested format")?; + let result = serde_json::from_str::(&self.contents).context("Could not deserialize to the requested format")?; Ok(result) } /// Returns raw content of the file - pub fn data(&self) -> &str { - &self.contents - } + pub fn data(&self) -> &str { &self.contents } } impl FromStr for InputFile { @@ -38,9 +34,6 @@ impl FromStr for InputFile { fn from_str(s: &str) -> Result { let name = PathBuf::from(s); let contents = std::fs::read_to_string(&name)?; - Ok(Self { - name, - contents, - }) + Ok(Self { name, contents }) } } diff --git a/src/util/file/mod.rs b/src/util/file/mod.rs index 7b3a403..94a92d5 100644 --- a/src/util/file/mod.rs +++ b/src/util/file/mod.rs @@ -2,4 +2,4 @@ mod input; mod output; pub use input::InputFile; -pub use output::OutputFile; \ No newline at end of file +pub use output::OutputFile; diff --git a/src/util/file/output.rs b/src/util/file/output.rs index ab0428b..d6e1128 100644 --- a/src/util/file/output.rs +++ b/src/util/file/output.rs @@ -33,4 +33,4 @@ impl FromStr for OutputFile { output: PathBuf::from(s), }) } -} \ No newline at end of file +} diff --git a/src/util/identifier.rs b/src/util/identifier.rs index 4d9f733..f0f885c 100644 --- a/src/util/identifier.rs +++ b/src/util/identifier.rs @@ -1,4 +1,5 @@ use std::fmt::{Display, Formatter}; + use nom::branch::alt; use nom::bytes::complete::take_while; use nom::character::complete::char; @@ -14,9 +15,7 @@ pub struct Identifier<'a> { } impl<'a> Display for Identifier<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}", self.namespace, self.location) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{}", self.namespace, self.location) } } impl<'a> Identifier<'a> { @@ -34,13 +33,9 @@ impl<'a> Identifier<'a> { } } - pub fn namespace(&self) -> &'a str { - self.namespace - } + pub fn namespace(&self) -> &'a str { self.namespace } - pub fn location(&self) -> &'a str { - self.location - } + pub fn location(&self) -> &'a str { self.location } } impl<'a> TryFrom<&'a str> for Identifier<'a> { @@ -50,10 +45,8 @@ impl<'a> TryFrom<&'a str> for Identifier<'a> { let namespace_domain = take_while::<_, _, Error<&'a str>>(|i| "0123456789abcdefghijklmnopqrstuvwxyz-_.".contains(i)); let location_domain = take_while::<_, _, Error<&'a str>>(|i| "0123456789abcdefghijklmnopqrstuvwxyz-_./".contains(i)); let namespace_location = separated_pair(namespace_domain, char(':'), location_domain); - let location_only = map( - take_while::<_, _, Error<&'a str>>(|i| "0123456789abcdefghijklmnopqrstuvwxyz-_./".contains(i)), - |location: &'a str| ("minecraft", location) - ); + let location_only = + map(take_while::<_, _, Error<&'a str>>(|i| "0123456789abcdefghijklmnopqrstuvwxyz-_./".contains(i)), |location: &'a str| ("minecraft", location)); let (input, (namespace, location)) = alt((namespace_location, location_only))(input).unwrap(); if !input.is_empty() { Err(location.len()) @@ -69,16 +62,20 @@ impl<'a> TryFrom<&'a str> for Identifier<'a> { impl<'de: 'a, 'a> Deserialize<'de> for Identifier<'a> { fn deserialize(deserializer: D) -> Result where - D: serde::Deserializer<'de> + D: serde::Deserializer<'de>, { let ident = <&'a str as Deserialize>::deserialize(deserializer)?; - ident.try_into() + ident + .try_into() .map_err(|e| serde::de::Error::custom(format!("invalid character at position {}", e))) } } impl<'a> Serialize for Identifier<'a> { - fn serialize(&self, serializer: S) -> Result where S: Serializer { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { let mut result = String::with_capacity(self.namespace.len() + 1 + self.location.len()); result.push_str(self.namespace); result.push(':'); @@ -89,28 +86,19 @@ impl<'a> Serialize for Identifier<'a> { #[cfg(test)] mod tests { - use serde_test::{assert_de_tokens, Token, assert_de_tokens_error}; + use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; use super::*; #[test] fn test_identifier_de() { let identifier = Identifier::from_location("test1"); - assert_de_tokens(&identifier, &[ - Token::BorrowedStr("test1"), - ]); - + assert_de_tokens(&identifier, &[Token::BorrowedStr("test1")]); + let identifier = Identifier::from_full("test_2", "other/value"); - assert_de_tokens(&identifier, &[ - Token::BorrowedStr("test_2:other/value"), - ]); + assert_de_tokens(&identifier, &[Token::BorrowedStr("test_2:other/value")]); } #[test] - fn test_identifier_de_error() { - assert_de_tokens_error::( - &[ - Token::BorrowedStr("test/2:other"), - ], "invalid character at position 6") - } + fn test_identifier_de_error() { assert_de_tokens_error::(&[Token::BorrowedStr("test/2:other")], "invalid character at position 6") } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 604730e..9615704 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,2 +1,2 @@ pub mod file; -pub mod identifier; \ No newline at end of file +pub mod identifier;