(&mut self, start_line: usize)
| 145 | Err(format!( |
| 146 | "line {}: missing closing `[/Animation]` block", |
| 147 | start_line |
| 148 | )) |
| 149 | } |
| 150 | |
| 151 | fn parse_objects_block(&mut self, start_line: usize) -> Result<Vec<AnimationObject>, String> { |
| 152 | let mut objects = Vec::new(); |
| 153 | while let Some((line_no, line)) = self.next_line() { |
| 154 | let line = strip_comment(line).trim(); |
| 155 | if line.is_empty() { |
| 156 | continue; |
| 157 | } |
| 158 | if line.eq_ignore_ascii_case("[/Objects]") { |
| 159 | return Ok(objects); |
| 160 | } |
| 161 | |
| 162 | let rest = line.strip_prefix('@').unwrap_or(line); |
| 163 | let Some((name, ty)) = rest.split_once('=') else { |
| 164 | return Err(format!("line {}: expected `Name = NodeType`", line_no)); |
| 165 | }; |
| 166 | let name = name.trim(); |
| 167 | if name.is_empty() { |
| 168 | return Err(format!("line {}: object name cannot be empty", line_no)); |
| 169 | } |
| 170 | if name.starts_with('@') { |
| 171 | return Err(format!( |
| 172 | "line {}: object declarations use `Name = NodeType`; refs use `@Name`", |
| 173 | line_no |
| 174 | )); |
| 175 | } |
| 176 | |
| 177 | let ty_value = self.parse_value_with_vars(ty.trim(), line_no)?; |
| 178 | let ty = as_text(&ty_value) |
| 179 | .ok_or_else(|| format!("line {}: object type must be text", line_no))?; |
| 180 | let node_type = ty |
| 181 | .parse::<NodeType>() |
| 182 | .map_err(|_| format!("line {}: unknown object type `{}`", line_no, ty))?; |
| 183 | objects.push(AnimationObject { |
| 184 | name: name.to_string().into(), |
| 185 | node_type, |
| 186 | }); |
| 187 | } |
| 188 | |
| 189 | Err(format!( |
| 190 | "line {}: missing closing `[/Objects]` block", |
| 191 | start_line |
| 192 | )) |
no test coverage detected