1use anyhow::anyhow;
23use pest::iterators::Pair;
24use pest::Parser;
25use pest_derive::Parser;
26use serde::{Deserialize, Serialize};
27use std::fs;
28use std::fs::File;
29use std::io::{self, Read, Write};
30
31#[derive(Parser)]
32#[grammar = "./grammar.pest"]
33pub struct Grammar;
34
35#[derive(Serialize, Deserialize, Debug)]
36pub struct Product {
37 pub product_name: String,
38 pub category: String,
39 pub price_per_unit: f64,
40 pub unit: String,
41 pub calories: f64,
42 pub proteins: f64,
43 pub carbohydrates: f64,
44 pub fats: f64,
45}
46
47#[derive(Debug)]
48pub struct ShoppingItem {
49 pub product_name: String,
50 pub quantity: f64,
51 pub unit: String,
52}
53
54impl Product {
55 pub fn from_pair(pair: Pair<Rule>) -> Self {
56 let mut product_name = String::new();
57 let mut category = String::new();
58 let mut price_per_unit = 0.0;
59 let mut unit = String::new();
60 let mut calories = 0.0;
61 let mut proteins = 0.0;
62 let mut carbohydrates = 0.0;
63 let mut fats = 0.0;
64
65 for field in pair.into_inner() {
66 match field.as_rule() {
67 Rule::product_name => {
68 product_name = field
69 .into_inner()
70 .find(|inner| inner.as_rule() == Rule::name)
71 .map(|name| name.as_str().to_string())
72 .unwrap_or_default();
73 }
74 Rule::category => {
75 category = field.as_str().replace("Category:", "").trim().to_string();
76 }
77 Rule::price => {
78 for inner in field.into_inner() {
79 match inner.as_rule() {
80 Rule::currency_amount => {
81 price_per_unit =
82 inner.as_str().trim().parse::<f64>().unwrap_or(0.0);
83 }
84 Rule::currency => {
85 unit = inner.as_str().to_string();
86 }
87 Rule::unit => {
88 unit.push('/');
89 unit.push_str(inner.as_str());
90 }
91 _ => {}
92 }
93 }
94 }
95 Rule::calories => {
96 let calories_str = field
97 .as_str()
98 .replace("Calories:", "")
99 .replace("cal", "")
100 .trim()
101 .to_string();
102 calories = calories_str.parse::<f64>().unwrap_or(0.0);
103 }
104 Rule::proteins => {
105 let proteins_str = field
106 .as_str()
107 .replace("Proteins:", "")
108 .replace("g", "")
109 .trim()
110 .to_string();
111 proteins = proteins_str.parse::<f64>().unwrap_or(0.0);
112 }
113 Rule::carbohydrates => {
114 let carbohydrates_str = field
115 .as_str()
116 .replace("Carbohydrates:", "")
117 .replace("g", "")
118 .trim()
119 .to_string();
120 carbohydrates = carbohydrates_str.parse::<f64>().unwrap_or(0.0);
121 }
122 Rule::fats => {
123 let fats_str = field
124 .as_str()
125 .replace("Fats:", "")
126 .replace("g", "")
127 .trim()
128 .to_string();
129 fats = fats_str.parse::<f64>().unwrap_or(0.0);
130 }
131 _ => {}
132 }
133 }
134
135 Product {
136 product_name,
137 category,
138 price_per_unit,
139 unit,
140 calories,
141 proteins,
142 carbohydrates,
143 fats,
144 }
145 }
146}
147
148pub fn parse_shopping_list(input: &str) -> Result<Vec<ShoppingItem>, anyhow::Error> {
149 let pairs = Grammar::parse(Rule::shopping_list, input)
150 .map_err(|e| anyhow!("Parsing shopping list failed: {}", e))?;
151
152 let mut items = Vec::new();
153
154 for pair in pairs {
155 match pair.as_rule() {
156 Rule::shopping_item => {
157 let mut product_name = String::new();
158 let mut quantity = 0.0;
159 let mut unit = String::new();
160
161 for field in pair.into_inner() {
162 match field.as_rule() {
163 Rule::name => {
164 product_name = field.as_str().to_string();
165 }
166 Rule::currency_amount => {
167 quantity = field.as_str().parse::<f64>().unwrap_or(0.0);
168 }
169 Rule::unit => {
170 unit = field.as_str().to_string();
171 }
172 _ => {}
173 }
174 }
175
176 items.push(ShoppingItem {
177 product_name,
178 quantity,
179 unit,
180 });
181 }
182 _ => {}
183 }
184 }
185
186 Ok(items)
187}
188
189
190
191pub fn parse_products_file(file_path: &str) -> Result<Vec<Product>, anyhow::Error> {
192 let content = fs::read_to_string(file_path)?;
193 let pairs = Grammar::parse(Rule::products, &content)
194 .map_err(|e| anyhow!("Parsing failed: {}", e))?
195 .next()
196 .ok_or_else(|| anyhow!("No products found in input file"))?;
197
198 let products: Vec<Product> = pairs
199 .into_inner()
200 .filter_map(|pair| {
201 if pair.as_rule() == Rule::product {
202 Some(Product::from_pair(pair))
203 } else {
204 None
205 }
206 })
207 .collect();
208
209 Ok(products)
210}
211
212pub fn save_products_to_json(products: &[Product], output_path: &str) -> io::Result<()> {
213 let json_data = serde_json::to_string_pretty(products)?;
214 let mut file = File::create(output_path)?;
215 file.write_all(json_data.as_bytes())?;
216 Ok(())
217}
218
219pub fn load_products_from_json(input_path: &str) -> Result<Vec<Product>, io::Error> {
220 let mut file = File::open(input_path)?;
221 let mut data = String::new();
222 file.read_to_string(&mut data)?;
223 let products: Vec<Product> = serde_json::from_str(&data)?;
224 Ok(products)
225}
226