diff --git a/Cargo.lock b/Cargo.lock index 24812aa..34a398a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,7 +143,7 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libtbr" -version = "0.1.0" +version = "0.1.1" dependencies = [ "chrono", "log", diff --git a/Cargo.toml b/Cargo.toml index 7cc9e27..00fd772 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libtbr" -version = "0.1.0" +version = "0.1.1" edition = "2024" [dependencies] @@ -8,4 +8,4 @@ chrono = "0.4.41" log = "0.4.27" rayon = "1.10.0" serde = { version = "1.0.219", features = ["derive", "serde_derive"] } -serde_json = "1.0.140" +serde_json = "1.0.140" \ No newline at end of file diff --git a/README.md b/README.md index 8eac080..2625f91 100644 --- a/README.md +++ b/README.md @@ -25,55 +25,6 @@ let recipe_dir = cfg.get("RECIPE_DIR").unwrap(); --- -** Helper functions ** - -This will be included in the next version. So the following functions may just have to implement by yourself for now. - -```rust - -use std::collections::HashMap; -use std::fs::File; -use std::io::{self, Error, Read}; - -/// Get valid country names -fn valid_country_name() -> Vec<&'static str> { - vec!["mys", "sgp", "aus", "tha", "hkg", "dubai", "uae"] -} - -/// Get latest versions of recipes -fn grep_latest_versions(dir_path: &str) -> Result, io::Error> { - let mut vs = HashMap::new(); - - // open dir - let entries = std::fs::read_dir(dir_path)? - .map(|res| res.map(|e| e.path())) - .collect::, io::Error>>()?; - - for e in entries { - let path = e.clone().to_str().unwrap().to_string(); - let mut cl_path = path.clone(); - let path_split = path.split("/").collect::>(); - let dir_name = path_split[path_split.len() - 1]; - - if valid_country_name().contains(&dir_name) { - cl_path.push_str("/version"); - - // read filename - let mut file = File::open(cl_path)?; - let mut data = String::new(); - file.read_to_string(&mut data).unwrap(); - - vs.insert(dir_name.to_string(), data.parse::().unwrap()); - } - - // expect dir with country - } - - Ok(vs) -} - -``` - ### Get recipe from specific country (latest) ```rust ... diff --git a/src/models/recipe.rs b/src/models/recipe.rs index 4fa3045..b98eac8 100644 --- a/src/models/recipe.rs +++ b/src/models/recipe.rs @@ -48,6 +48,7 @@ macro_rules! update_each_field { /// about its structure. /// * `MaterialCode`: MaterialCode is a vector (array) that contains instances of the MaterialCode /// struct. +#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Recipe { pub Timestamp: String, @@ -91,6 +92,8 @@ impl Recipe { } } + /// Get undefined fields from the recipe. This may included newer fields. + /// pub fn get_additional_fields(&self) -> Option { if self.extra.is_empty() { return None; @@ -115,12 +118,14 @@ impl Recipe { .find(|&r| r.productCode == product_code) } + /// Similar to `search_pd` but ignoring exacted matching. pub fn search_pd_by_no_country_code(&self, product_code: String) -> Option<&Recipe01> { self.Recipe01 .iter() .find(|&r| r.productCode.contains(&product_code)) } + /// Mulitple product code searching with parallel and no exact matching. pub fn search_multi_in_parallel(&self, pds: Vec) -> Vec> { let mut found = Vec::new(); @@ -136,6 +141,7 @@ impl Recipe { found } + /// Get position of product code in the recipe's `Recipe01` pub fn get_pd_index(&self, product_code: String) -> usize { self.Recipe01 .iter() @@ -143,26 +149,32 @@ impl Recipe { .unwrap() } + /// Search expected material setting if existed pub fn search_material_settings(&self, material_code: String) -> Option<&MaterialSetting> { self.MaterialSetting .iter() .find(|r| r.id.to_string() == material_code) } + /// Search expected topping list if existed pub fn search_topping_list(&self, id: String) -> Option<&ToppingList> { self.Topping.ToppingList.iter().find(|&r| r.id == id) } + /// Search expected topping group if existed pub fn search_topping_group(&self, id: String) -> Option<&ToppingGroup> { self.Topping.ToppingGroup.iter().find(|&r| r.groupID == id) } + /// Search expected material code if existed pub fn search_material_code(&self, material_code: String) -> Option<&MaterialCode> { self.MaterialCode .iter() .find(|&r| r.materialID == material_code) } + /// Check if expected product code is different in both recipes. + /// This may return `false` if either one did not have this product code. pub fn diff_pd_between_recipes(&self, another_recipe: &Recipe, product_code: String) -> bool { let menu1 = Self::search_pd(self, product_code.clone()); let menu2 = Self::search_pd(another_recipe, product_code.clone()); @@ -177,6 +189,7 @@ impl Recipe { menu1 == menu2 } + /// Similar to `diff_pd_between_recipes` but will not check exacted matching. pub fn diff_pd_between_recipes_ignore_country( &self, another_recipe: &Recipe, @@ -195,6 +208,35 @@ impl Recipe { menu1 == menu2 } + pub fn deep_diff_pd_between_recipes( + &self, + another_recipe: &Recipe, + product_code: String, + ) -> Vec<(String, bool)> { + let mut result = Vec::new(); + + let self_recipe = Self::search_pd_by_no_country_code(self, product_code.clone()); + let another_recipe = + Self::search_pd_by_no_country_code(another_recipe, product_code.clone()); + + if self_recipe.is_some() && another_recipe.is_some() { + let sr = self_recipe.unwrap(); + let ar = another_recipe.unwrap(); + + let diff_field_list = sr.compare(ar); + + for key in sr.to_map().keys() { + if diff_field_list.contains(key) { + result.push((key.to_string(), true)); + } else { + result.push((key.to_string(), false)); + } + } + } + + result + } + pub fn diff_material_setting_between_recipes( &self, another_recipe: &Recipe, @@ -522,6 +564,7 @@ impl Recipe { pub fn add_prefix_country_code(&mut self) {} } +#[allow(non_snake_case)] fn StrShowTextErrorDefault() -> Option> { Some(vec![ Value::String("เต่าบินเกิดเหตุขัดข้อง".into()), @@ -569,6 +612,7 @@ impl PartialRecipe { /// temperature setting for a machine. /// * `temperatureMin`: The `temperatureMin` property is of type `Value`. It represents the minimum /// temperature setting for the machine. +#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct MachineSetting { pub Comment: Option>, @@ -659,26 +703,32 @@ impl CommonRecipeTrait for MachineSetting { } } +#[allow(non_snake_case)] fn BlankString() -> Option { Some("".to_string()) } +#[allow(non_snake_case)] fn BlankBool() -> Option { Some(false) } +#[allow(non_snake_case)] fn BlankVecString() -> Option> { Some(vec![].into()) } +#[allow(non_snake_case)] fn BlankVecRecipe() -> Option> { Some(vec![].into()) } +#[allow(non_snake_case)] fn BlankVecMenuTopping() -> Option> { Some(vec![].into()) } +#[allow(non_snake_case)] fn BlankOtherStrShowTextError() -> Option> { Some(vec![ "".to_string(), @@ -692,14 +742,17 @@ fn BlankOtherStrShowTextError() -> Option> { ]) } +#[allow(non_snake_case)] fn BlankValueString() -> Option { Some(Value::String("".into())) } +#[allow(non_snake_case, dead_code)] fn BlankValueArray() -> Option { Some(Value::Array(vec![].into())) } +#[allow(non_snake_case, dead_code)] fn BlankValueBool() -> Option { Some(Value::Bool(false.into())) } @@ -754,6 +807,7 @@ fn BlankValueBool() -> Option { /// * `useGram`: A boolean value indicating whether the recipe uses grams for measurement or not. /// * `weight_float`: The `weight_float` property is of type `Value` and represents the weight of the /// recipe as a floating-point number. +#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Recipe01 { #[serde(default = "BlankString")] @@ -1371,6 +1425,7 @@ impl Recipe01 { /// water in the recipe. /// * `waterYield`: The `waterYield` property represents the amount of water produced or obtained from /// the recipe. +#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct RecipeList { pub MixOrder: Value, @@ -1465,6 +1520,7 @@ impl RecipeList { /// maximum number of retries for a payment. /// * `RawMaterialUnit`: The `RawMaterialUnit` property is an optional string that represents the unit /// of measurement for the raw material. +#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct MaterialSetting { pub AlarmIDWhenOffline: Value, @@ -1576,6 +1632,7 @@ impl MachineSetting { } } +#[allow(non_snake_case)] fn BlankListGroupID() -> Option> { Some(vec![ Value::String("0".into()), @@ -1585,6 +1642,7 @@ fn BlankListGroupID() -> Option> { ]) } +#[allow(non_snake_case)] fn BlankGroupID() -> Option { Some(Value::String("0".into())) } @@ -1600,6 +1658,7 @@ fn BlankGroupID() -> Option { /// which the menu topping list belongs. /// * `isUse`: The `isUse` property is a boolean value that indicates whether the menu topping list is /// being used or not. +#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct MenuToppingList { #[serde(default = "BlankListGroupID")] @@ -1620,6 +1679,7 @@ pub struct MenuToppingList { /// struct. Each ToppingGroup struct represents a group of toppings. /// * `ToppingList`: The `ToppingList` property is a vector (dynamic array) that contains elements of /// type `ToppingList`. +#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Topping { pub ToppingGroup: Vec, @@ -1643,6 +1703,7 @@ pub struct Topping { /// * `name`: The `name` property is a string that represents the name of the topping group. /// * `otherName`: The `otherName` property is a string that represents an alternative name for the /// `ToppingGroup`. It can be used to provide additional information or a different name for the group. +#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ToppingGroup { pub groupID: Value, @@ -1703,6 +1764,7 @@ impl ToppingGroup { /// * `useGram`: A boolean value indicating whether the topping uses grams for measurement or not. /// * `weight_float`: The `weight_float` property is of type `Value` and represents the weight of the /// topping in float format. +#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct ToppingList { pub ExtendID: Value, @@ -1754,6 +1816,7 @@ impl ToppingList { /// * `materialCode`: The `materialCode` property is an optional field that represents the code /// associated with a material. It is of type `Option`, which means it can either be /// `Some(String)` if a value is present, or `None` if no value is provided. +#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct MaterialCode { #[serde(default = "BlankString")] diff --git a/src/models/recipev2.rs b/src/models/recipev2.rs index 690947e..af9727c 100644 --- a/src/models/recipev2.rs +++ b/src/models/recipev2.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use crate::models::recipe::*; use crate::recipe_functions::common::*; -/// Version 2 of `models::Recipe` +/// Version 2 of `models::Recipe`, WIP /// #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct VersionRecipe {