From ba911a8a207eb47df7a055410570767b2e33c2ae Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 11 Dec 2021 15:17:42 -0600 Subject: correct minecraft-chat :tada: --- minecraft-chat/src/component.rs | 234 +++++++++++++++++++++++----------------- 1 file changed, 138 insertions(+), 96 deletions(-) (limited to 'minecraft-chat/src/component.rs') diff --git a/minecraft-chat/src/component.rs b/minecraft-chat/src/component.rs index 300a1228..2ff6111a 100644 --- a/minecraft-chat/src/component.rs +++ b/minecraft-chat/src/component.rs @@ -1,8 +1,11 @@ -use serde_json; +use serde::{ + de::{self, Error}, + Deserialize, Deserializer, +}; use crate::{ base_component::BaseComponent, - style::Style, + style::{ChatFormatting, Style}, text_component::TextComponent, translatable_component::{StringOrComponent, TranslatableComponent}, }; @@ -13,9 +16,105 @@ pub enum Component { Translatable(TranslatableComponent), } +lazy_static! { + pub static ref DEFAULT_STYLE: Style = Style { + color: Some(ChatFormatting::WHITE.try_into().unwrap()), + ..Style::default() + }; +} + /// A chat component impl Component { - pub fn new(json: &serde_json::Value) -> Result { + // TODO: is it possible to use a macro so this doesn't have to be duplicated? + + pub fn get_base_mut(&mut self) -> &mut BaseComponent { + match self { + Self::Text(c) => &mut c.base, + Self::Translatable(c) => &mut c.base, + } + } + + pub fn get_base(&self) -> &BaseComponent { + match self { + Self::Text(c) => &c.base, + Self::Translatable(c) => &c.base, + } + } + + /// Add a component as a sibling of this one + fn append(&mut self, sibling: Component) { + self.get_base_mut().siblings.push(sibling); + } + + /// Get the "separator" component from the json + fn parse_separator(json: &serde_json::Value) -> Result, serde_json::Error> { + if json.get("separator").is_some() { + return Ok(Some(Component::deserialize( + json.get("separator").unwrap(), + )?)); + } + Ok(None) + } + + /// Convert this component into an ansi string + pub fn to_ansi(&self, default_style: Option<&Style>) -> String { + // default the default_style to white if it's not set + let default_style: &Style = default_style.unwrap_or_else(|| &DEFAULT_STYLE); + + // this contains the final string will all the ansi escape codes + let mut built_string = String::new(); + // this style will update as we visit components + let mut running_style = Style::default(); + + for component in self.clone().into_iter() { + let component_text = match &component { + Self::Text(c) => &c.text, + Self::Translatable(c) => &c.key, + }; + let component_style = &component.get_base().style; + + let ansi_text = running_style.compare_ansi(component_style, default_style); + built_string.push_str(&ansi_text); + built_string.push_str(component_text); + + running_style.apply(component_style); + } + + if !running_style.is_empty() { + built_string.push_str("\u{1b}[m"); + } + + built_string + } +} + +impl IntoIterator for Component { + /// Recursively call the function for every component in this component + fn into_iter(self) -> Self::IntoIter { + let base = self.get_base(); + let siblings = base.siblings.clone(); + let mut v: Vec = Vec::with_capacity(siblings.len() + 1); + v.push(self); + for sibling in siblings { + v.extend(sibling.into_iter()); + } + + v.into_iter() + } + + type Item = Component; + type IntoIter = std::vec::IntoIter; +} + +impl<'de> Deserialize<'de> for Component { + fn deserialize(de: D) -> Result + where + D: Deserializer<'de>, + { + println!("deserializing component"); + let json: serde_json::Value = serde::Deserialize::deserialize(de)?; + println!("made json"); + // we create a component that we might add siblings to let mut component: Component; @@ -38,7 +137,7 @@ impl Component { for i in 0..with.len() { // if it's a string component with no styling and no siblings, just add a string to with_array // otherwise add the component to the array - let c = Component::new(&with[i])?; + let c = Component::deserialize(&with[i]).map_err(de::Error::custom)?; if let Component::Text(text_component) = c { if text_component.base.siblings.is_empty() && text_component.base.style.is_empty() @@ -47,7 +146,9 @@ impl Component { break; } } - with_array.push(StringOrComponent::Component(Component::new(&with[i])?)); + with_array.push(StringOrComponent::Component( + Component::deserialize(&with[i]).map_err(de::Error::custom)?, + )); } component = Component::Translatable(TranslatableComponent::new(translate, with_array)); @@ -62,33 +163,41 @@ impl Component { // if (!object.has("name") || !object.has("objective")) throw new JsonParseException("A score component needs a least a name and an objective"); // ScoreComponent scoreComponent = new ScoreComponent(GsonHelper.getAsString((JsonObject)object, "name"), GsonHelper.getAsString((JsonObject)object, "objective")); if score_json.get("name").is_none() || score_json.get("objective").is_none() { - return Err( - "A score component needs at least a name and an objective".to_string() - ); + return Err(de::Error::missing_field( + "A score component needs at least a name and an objective", + )); } // TODO - return Err("score text components aren't yet supported".to_string()); + return Err(de::Error::custom( + "score text components aren't yet supported", + )); // component = ScoreComponent } else if json.get("selector").is_some() { // } else if (jsonObject.has("selector")) { // object = this.parseSeparator(type, jsonDeserializationContext, jsonObject); // SelectorComponent selectorComponent = new SelectorComponent(GsonHelper.getAsString(jsonObject, "selector"), (Optional)object); - return Err("selector text components aren't yet supported".to_string()); + return Err(de::Error::custom( + "selector text components aren't yet supported", + )); // } else if (jsonObject.has("keybind")) { // KeybindComponent keybindComponent = new KeybindComponent(GsonHelper.getAsString(jsonObject, "keybind")); } else if json.get("keybind").is_some() { - return Err("keybind text components aren't yet supported".to_string()); + return Err(de::Error::custom( + "keybind text components aren't yet supported", + )); } else { // } else { // if (!jsonObject.has("nbt")) throw new JsonParseException("Don't know how to turn " + jsonElement + " into a Component"); if json.get("nbt").is_none() { - return Err(format!("Don't know how to turn {} into a Component", json)); + return Err(de::Error::custom( + format!("Don't know how to turn {} into a Component", json).as_str(), + )); } // object = GsonHelper.getAsString(jsonObject, "nbt"); let _nbt = json.get("nbt").unwrap().to_string(); // Optional optional = this.parseSeparator(type, jsonDeserializationContext, jsonObject); - let _separator = Component::parse_separator(json)?; + let _separator = Component::parse_separator(&json).map_err(de::Error::custom)?; let _interpret = match json.get("interpret") { Some(v) => v.as_bool().ok_or(Some(false)).unwrap(), @@ -97,7 +206,9 @@ impl Component { // boolean bl = GsonHelper.getAsBoolean(jsonObject, "interpret", false); // if (jsonObject.has("block")) { if json.get("block").is_some() {} - return Err("nbt text components aren't yet supported".to_string()); + return Err(de::Error::custom( + "nbt text components aren't yet supported", + )); // NbtComponent.BlockNbtComponent blockNbtComponent = new NbtComponent.BlockNbtComponent((String)object, bl, GsonHelper.getAsString(jsonObject, "block"), optional); // } else if (jsonObject.has("entity")) { // NbtComponent.EntityNbtComponent entityNbtComponent = new NbtComponent.EntityNbtComponent((String)object, bl, GsonHelper.getAsString(jsonObject, "entity"), optional); @@ -120,106 +231,37 @@ impl Component { if json.get("extra").is_some() { let extra = match json.get("extra").unwrap().as_array() { Some(r) => r, - None => return Err("Extra isn't an array".to_string()), + None => return Err(de::Error::custom("Extra isn't an array")), }; if extra.is_empty() { - return Err("Unexpected empty array of components".to_string()); + return Err(de::Error::custom("Unexpected empty array of components")); } for extra_component in extra { - component.append(Component::new(extra_component)?); + let sibling = + Component::deserialize(extra_component).map_err(de::Error::custom)?; + component.append(sibling); } } - let style = Style::deserialize(json); + let style = Style::deserialize(&json); component.get_base_mut().style = style; return Ok(component); } // ok so it's not an object, if it's an array deserialize every item else if !json.is_array() { - return Err(format!("Don't know how to turn {} into a Component", json)); + return Err(de::Error::custom( + format!("Don't know how to turn {} into a Component", json).as_str(), + )); } let json_array = json.as_array().unwrap(); // the first item in the array is the one that we're gonna return, the others are siblings - let mut component = Component::new(&json_array[0])?; + let mut component = Component::deserialize(&json_array[0]).map_err(de::Error::custom)?; for i in 1..json_array.len() { - component.append(Component::new(json_array.get(i).unwrap())?); + component.append( + Component::deserialize(json_array.get(i).unwrap()).map_err(de::Error::custom)?, + ); } Ok(component) } - - // TODO: is it possible to use a macro so this doesn't have to be duplicated? - - pub fn get_base_mut(&mut self) -> &mut BaseComponent { - match self { - Self::Text(c) => &mut c.base, - Self::Translatable(c) => &mut c.base, - } - } - - pub fn get_base(&self) -> &BaseComponent { - match self { - Self::Text(c) => &c.base, - Self::Translatable(c) => &c.base, - } - } - - /// Add a component as a sibling of this one - fn append(&mut self, sibling: Component) { - self.get_base_mut().siblings.push(sibling); - } - - /// Get the "separator" component from the json - fn parse_separator(json: &serde_json::Value) -> Result, String> { - if json.get("separator").is_some() { - return Ok(Some(Component::new(json.get("separator").unwrap())?)); - } - Ok(None) - } - - /// Convert this component into an ansi string - pub fn to_ansi(&self) -> String { - // this contains the final string will all the ansi escape codes - let mut built_string = String::new(); - // this style will update as we visit components - let mut running_style = Style::default(); - - for component in self.clone().into_iter() { - let component_text = match &component { - Self::Text(c) => &c.text, - Self::Translatable(c) => &c.key, - }; - let component_style = &component.get_base().style; - - let ansi_text = running_style.compare_ansi(component_style); - built_string.push_str(&ansi_text); - built_string.push_str(component_text); - - running_style.apply(component_style); - } - - if !running_style.is_empty() { - built_string.push_str("\u{1b}[m"); - } - - built_string - } -} - -impl IntoIterator for Component { - /// Recursively call the function for every component in this component - fn into_iter(self) -> Self::IntoIter { - let base = self.get_base(); - let siblings = base.siblings.clone(); - let mut v: Vec = Vec::with_capacity(siblings.len() + 1); - v.push(self); - for sibling in siblings { - v.extend(sibling.into_iter()); - } - - v.into_iter() - } - - type Item = Component; - type IntoIter = std::vec::IntoIter; } -- cgit v1.2.3