libbrat_workflow/
parser.rs1use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7use crate::error::WorkflowError;
8use crate::schema::WorkflowTemplate;
9
10pub struct WorkflowParser {
12 workflows_dir: PathBuf,
14}
15
16impl WorkflowParser {
17 pub fn new(workflows_dir: impl Into<PathBuf>) -> Self {
19 Self {
20 workflows_dir: workflows_dir.into(),
21 }
22 }
23
24 pub fn from_repo_root(repo_root: impl AsRef<Path>) -> Self {
28 Self::new(repo_root.as_ref().join(".brat").join("workflows"))
29 }
30
31 pub fn workflows_dir(&self) -> &Path {
33 &self.workflows_dir
34 }
35
36 pub fn workflows_dir_exists(&self) -> bool {
38 self.workflows_dir.is_dir()
39 }
40
41 pub fn list_workflows(&self) -> Result<Vec<String>, WorkflowError> {
43 if !self.workflows_dir.is_dir() {
44 return Ok(Vec::new());
45 }
46
47 let mut workflows = Vec::new();
48 for entry in fs::read_dir(&self.workflows_dir)? {
49 let entry = entry?;
50 let path = entry.path();
51 if path.is_file() {
52 if let Some(ext) = path.extension() {
53 if ext == "yaml" || ext == "yml" {
54 if let Some(stem) = path.file_stem() {
55 workflows.push(stem.to_string_lossy().to_string());
56 }
57 }
58 }
59 }
60 }
61 workflows.sort();
62 Ok(workflows)
63 }
64
65 pub fn load(&self, name: &str) -> Result<WorkflowTemplate, WorkflowError> {
67 let path = self.find_workflow_path(name)?;
68 self.load_from_path(&path)
69 }
70
71 pub fn load_from_path(&self, path: &Path) -> Result<WorkflowTemplate, WorkflowError> {
73 let content = fs::read_to_string(path)?;
74 let template: WorkflowTemplate = serde_yaml::from_str(&content)?;
75
76 template
78 .validate()
79 .map_err(WorkflowError::ValidationError)?;
80
81 Ok(template)
82 }
83
84 fn find_workflow_path(&self, name: &str) -> Result<PathBuf, WorkflowError> {
86 let yaml_path = self.workflows_dir.join(format!("{}.yaml", name));
88 if yaml_path.is_file() {
89 return Ok(yaml_path);
90 }
91
92 let yml_path = self.workflows_dir.join(format!("{}.yml", name));
94 if yml_path.is_file() {
95 return Ok(yml_path);
96 }
97
98 Err(WorkflowError::NotFound(name.to_string()))
99 }
100
101 pub fn substitute_vars(template: &str, vars: &HashMap<String, String>) -> String {
105 let mut result = template.to_string();
106 for (key, value) in vars {
107 let pattern = format!("{{{{{}}}}}", key);
108 result = result.replace(&pattern, value);
109 }
110 result
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_substitute_vars() {
120 let vars: HashMap<String, String> = [
121 ("name".to_string(), "Alice".to_string()),
122 ("count".to_string(), "42".to_string()),
123 ]
124 .into_iter()
125 .collect();
126
127 assert_eq!(
128 WorkflowParser::substitute_vars("Hello {{name}}", &vars),
129 "Hello Alice"
130 );
131 assert_eq!(
132 WorkflowParser::substitute_vars("Count: {{count}} items", &vars),
133 "Count: 42 items"
134 );
135 assert_eq!(
136 WorkflowParser::substitute_vars("{{name}} has {{count}}", &vars),
137 "Alice has 42"
138 );
139 assert_eq!(
140 WorkflowParser::substitute_vars("No vars here", &vars),
141 "No vars here"
142 );
143 }
144}