Skip to main content

synaptic_parsers/
numbered_list_parser.rs

1use async_trait::async_trait;
2use synaptic_core::{RunnableConfig, SynapticError};
3use synaptic_runnables::Runnable;
4
5use crate::FormatInstructions;
6
7/// Parses numbered lists like `1. item`, `2. item`.
8pub struct NumberedListOutputParser;
9
10impl FormatInstructions for NumberedListOutputParser {
11    fn get_format_instructions(&self) -> String {
12        "Your response should be a numbered list (e.g., `1. item`, `2. item`).".to_string()
13    }
14}
15
16#[async_trait]
17impl Runnable<String, Vec<String>> for NumberedListOutputParser {
18    async fn invoke(
19        &self,
20        input: String,
21        _config: &RunnableConfig,
22    ) -> Result<Vec<String>, SynapticError> {
23        let items: Vec<String> = input
24            .lines()
25            .filter_map(|line| {
26                let trimmed = line.trim();
27                if trimmed.is_empty() {
28                    return None;
29                }
30                // Find the first '.' and check that everything before it is digits
31                let dot_pos = trimmed.find('.')?;
32                let prefix = &trimmed[..dot_pos];
33                if prefix.is_empty() || !prefix.chars().all(|c| c.is_ascii_digit()) {
34                    return None;
35                }
36                // After the dot, expect at least one whitespace char
37                let after_dot = &trimmed[dot_pos + 1..];
38                let rest = after_dot
39                    .strip_prefix(' ')
40                    .or_else(|| after_dot.strip_prefix('\t'))?;
41                let item = rest.trim().to_string();
42                if item.is_empty() {
43                    None
44                } else {
45                    Some(item)
46                }
47            })
48            .collect();
49
50        Ok(items)
51    }
52}