sloop/sources/
markdown.rs1use std::fs;
2use std::io;
3use std::path::{Path, PathBuf};
4
5use crate::flow::Flow;
6
7use super::{FlowSource, SourceError};
8
9pub struct MarkdownFlowSource {
10 root: PathBuf,
11}
12
13impl MarkdownFlowSource {
14 pub fn new(root: impl Into<PathBuf>) -> Self {
15 Self { root: root.into() }
16 }
17}
18
19impl FlowSource for MarkdownFlowSource {
20 fn pull(&self) -> Result<Vec<Flow>, SourceError> {
21 let directory = self.root.join(".agents/sloop/flows");
22 let entries = match fs::read_dir(&directory) {
23 Ok(entries) => entries,
24 Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
25 Err(error) => return Err(io_error(&directory, error)),
26 };
27 let mut paths = entries
28 .map(|entry| {
29 entry
30 .map(|entry| entry.path())
31 .map_err(|error| io_error(&directory, error))
32 })
33 .collect::<Result<Vec<_>, _>>()?;
34 paths.retain(|path| path.is_file() && path.extension().is_some_and(|ext| ext == "yaml"));
35 paths.sort();
36
37 paths
38 .into_iter()
39 .map(|path| {
40 let name = path
41 .file_stem()
42 .and_then(|stem| stem.to_str())
43 .ok_or_else(|| {
44 SourceError::new(format!(
45 "{}: flow filename must be valid UTF-8",
46 path.display()
47 ))
48 })?;
49 let contents = fs::read_to_string(&path).map_err(|error| io_error(&path, error))?;
50 crate::flow::parse(name, &contents)
51 .map_err(|message| SourceError::new(format!("{}: {message}", path.display())))
52 })
53 .collect()
54 }
55}
56
57fn io_error(path: &Path, error: io::Error) -> SourceError {
58 SourceError::new(format!("{}: {error}", path.display()))
59}