my_gym_data_rust_parser/
lib.rs1use chrono::NaiveDate;
2use pest::Parser;
3use pest_derive::Parser;
4use std::io;
5use thiserror::Error;
6
7#[derive(Error, Debug)]
8pub enum GymDataParserError {
9 #[error("IO error: {0}")]
10 IOError(#[from] io::Error),
11
12 #[error("Parse error: {0}")]
13 ParseError(#[from] Box<pest::error::Error<Rule>>),
14
15 #[error("Date parsing error: {0}")]
16 DateParseError(#[from] chrono::ParseError),
17
18 #[error("File content parse error")]
19 FileContentParseError,
20
21 #[error("Exercise name parse error")]
22 ExerciseNameParseError,
23
24 #[error("Target parse error")]
25 TargetParseError,
26
27 #[error("Set group parse error")]
28 SetGroupParseError,
29
30 #[error("Missing data error")]
31 MissingDateError,
32
33 #[error("Invalid number format: {0}")]
34 InvalidNumberFormat(#[from] std::num::ParseIntError),
35}
36
37#[derive(Debug)]
40pub struct ExerciseRecord {
41 pub date: NaiveDate,
43
44 pub exercise_name: String,
46
47 pub target: TargetReps,
49
50 pub sets: Vec<Set>,
52}
53
54#[derive(Debug)]
60pub struct TargetReps {
61 pub sets_count: u32,
63
64 pub min_reps: u32,
66
67 pub max_reps: u32,
70}
71
72#[derive(Debug)]
75pub struct Set {
76 pub attempts: Vec<Attempt>,
80}
81
82#[derive(Debug)]
86pub struct Attempt {
87 pub weight: u32,
89
90 pub reps: u32,
92}
93
94#[derive(Parser)]
95#[grammar = "grammar.pest"]
96pub struct Grammar;
97
98pub fn parse_exercise_log(input: &str) -> Result<Vec<ExerciseRecord>, GymDataParserError> {
99 let mut records = Vec::new();
100
101 let mut parsed = Grammar::parse(Rule::file, input)
102 .map_err(|e| GymDataParserError::ParseError(Box::from(e)))?;
103
104 let file_rule = parsed
105 .next()
106 .ok_or(GymDataParserError::FileContentParseError)?;
107
108 for record in file_rule.into_inner() {
109 if record.as_rule() == Rule::record {
110 let mut date: Option<NaiveDate> = None;
111 let mut exercise_name: Option<String> = None;
112 let mut target: Option<TargetReps> = None;
113 let mut sets: Vec<Set> = Vec::new();
114
115 for field in record.into_inner() {
116 match field.as_rule() {
117 Rule::date => {
118 let date_str = field.as_str();
119 let parsed_date = NaiveDate::parse_from_str(date_str, "%d.%m.%Y")?;
120 date = Some(parsed_date);
121 }
122 Rule::exercise_name => {
123 let name = field.as_str().trim().to_string();
124 exercise_name = Some(name);
125 }
126 Rule::target => {
127 let mut parts = field.into_inner();
128 let sets_count_str = parts
129 .next()
130 .ok_or(GymDataParserError::TargetParseError)?
131 .as_str()
132 .trim();
133 let min_reps_str = parts
134 .next()
135 .ok_or(GymDataParserError::TargetParseError)?
136 .as_str()
137 .trim();
138 let max_reps_str = parts
139 .next()
140 .ok_or(GymDataParserError::TargetParseError)?
141 .as_str()
142 .trim();
143
144 let sets_count = sets_count_str.parse::<u32>()?;
145 let min_reps = min_reps_str.parse::<u32>()?;
146 let max_reps = max_reps_str.parse::<u32>()?;
147
148 target = Some(TargetReps {
149 sets_count,
150 min_reps,
151 max_reps,
152 });
153 }
154 Rule::set_group => {
155 for set_group in field.into_inner() {
156 let mut attempts = Vec::new();
157 for set in set_group.into_inner() {
158 let mut set_parts = set.into_inner();
159 let weight_str = set_parts
160 .next()
161 .ok_or(GymDataParserError::SetGroupParseError)?
162 .as_str()
163 .trim();
164 let reps_str = set_parts
165 .next()
166 .ok_or(GymDataParserError::SetGroupParseError)?
167 .as_str()
168 .trim();
169
170 let weight = weight_str.parse::<u32>()?;
171 let reps = reps_str.parse::<u32>()?;
172
173 attempts.push(Attempt { weight, reps });
174 }
175 sets.push(Set { attempts });
176 }
177 }
178 _ => {}
179 }
180 }
181
182 let date = date.ok_or(GymDataParserError::MissingDateError)?;
183 let exercise_name = exercise_name.ok_or(GymDataParserError::ExerciseNameParseError)?;
184 let target = target.ok_or(GymDataParserError::TargetParseError)?;
185
186 records.push(ExerciseRecord {
187 date,
188 exercise_name,
189 target,
190 sets,
191 });
192 }
193 }
194
195 Ok(records)
196}