workout_note_parser/
lib.rs1use pest::{iterators::Pair, Parser};
8use serde::{Deserialize, Serialize};
9
10#[macro_use]
11extern crate pest_derive;
12
13pub mod error;
14use error::Error;
15
16#[derive(Parser)]
17#[grammar = "workout.pest"]
18struct WorkoutParser;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Set {
23 pub weight: f32,
25 pub n_reps: i32,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct Exercise {
32 pub name: String,
34 pub sets: Vec<Set>,
36 pub comment: Option<String>,
38}
39
40pub fn parse_set(name: String, pair: Pair<'_, Rule>) -> Result<Exercise, Error> {
51 if pair.as_rule() != Rule::set {
52 return Err(Error::ExpectedRule(Rule::set));
53 }
54 let mut pairella = Some(pair);
55
56 let mut sets = Vec::new();
57 let mut comment = None;
58 while let Some(pair) = pairella {
59 match pair.as_rule() {
60 Rule::comment => {
61 comment = Some(pair.as_str().trim().to_string());
62 pairella = pair.into_inner().next();
63 }
64 Rule::set => {
65 let mut pairs = pair.into_inner();
66 let weight = pairs
67 .next()
68 .ok_or(Error::ExpectedRule(Rule::weight))?
69 .as_str()
70 .trim()
71 .parse::<f32>()?;
72 let n_reps = pairs
73 .next()
74 .ok_or(Error::ExpectedRule(Rule::reps))?
75 .as_str()
76 .trim()
77 .parse::<i32>()?;
78
79 sets.push(Set { weight, n_reps });
80
81 pairella = pairs.next();
82 }
83 _ => return Err(Error::UnexpectedRule(pair.as_rule())),
84 }
85 }
86
87 Ok(Exercise {
88 name,
89 sets,
90 comment,
91 })
92}
93
94pub fn get_exercise_from_pairs(pair: Pair<'_, Rule>) -> Result<Vec<Exercise>, Error> {
104 if pair.as_rule() != Rule::exercise {
105 return Err(Error::ExpectedRule(Rule::exercise));
106 }
107
108 let mut rules = pair.into_inner();
109 let rule = rules.next().ok_or(Error::ExpectedRule(Rule::name))?;
110 if rule.as_rule() != Rule::name {
111 return Err(Error::ExpectedRule(Rule::name));
112 }
113
114 let name = rule.as_str().to_string();
115
116 rules
117 .filter(|r| r.as_rule() == Rule::set)
118 .map(|r| parse_set(name.clone(), r))
119 .collect()
120}
121
122pub fn parse_workout(input: &str) -> Result<Vec<Exercise>, Error> {
132 let mut parsed = WorkoutParser::parse(Rule::workout, input).map_err(Box::new)?;
133
134 let workout = parsed.next().ok_or(Error::ExpectedRule(Rule::workout))?;
135 if workout.as_rule() != Rule::workout {
136 return Err(Error::ExpectedRule(Rule::workout));
137 }
138
139 let vec = workout
140 .into_inner()
141 .filter(|r| r.as_rule() == Rule::exercise)
142 .map(get_exercise_from_pairs)
143 .collect::<Result<Vec<_>, _>>()?;
144 let vec = vec.into_iter().flatten().collect::<Vec<_>>();
145
146 Ok(vec)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use anyhow::Result;
153 use pest::Parser;
154
155 #[test]
156 fn weight() -> Result<()> {
157 let parsed = WorkoutParser::parse(Rule::weight, "35 ")?;
158 assert!(parsed.as_str() == "35");
159
160 Ok(())
161 }
162
163 #[test]
164 fn set() -> Result<()> {
165 WorkoutParser::parse(Rule::set, "35.5 x 10")?;
166
167 Ok(())
168 }
169
170 #[test]
171 fn extended_set() -> Result<()> {
172 WorkoutParser::parse(Rule::set, "35.5 x 10 + 40 x 8")?;
173
174 WorkoutParser::parse(Rule::set, "35.5 x 10 + 40 x 8 # this was really tough")?;
175
176 Ok(())
177 }
178
179 #[test]
180 fn workout() -> Result<()> {
181 let input = r#"
182 vert block
183 35.5 x 10
184 35.5 x 10 + 40 x 8
185 35.5 x 10 + 40 x 8 this was really tough
186
187 vert block
188 35.5 x 10
189 35.5 x 10 + 40 x 8
190 35.5 x 10 + 40 x 8 this was really tough
191
192 vert block
193 35.5 x 10
194 35.5 x 10 + 40 x 8
195 35.5 x 10 + 40 x 8 this was really tough
196 "#;
197
198 let parsed = WorkoutParser::parse(Rule::workout, input)?;
199 println!("Parsed: {:#?}", parsed);
200
201 Ok(())
202 }
203
204 #[test]
205 #[should_panic]
206 fn bad_workout() {
207 let input = r#"
208 vert block
209 35.5 x
210 35.5 x 10 + 40 x 8
211 35.5 x 10 + 40 x 8 this was really tough
212 "#;
213
214 WorkoutParser::parse(Rule::workout, input).unwrap();
215 }
216}