Skip to main content

wowlab_docgen_cli/context/collectors/
ci.rs

1use serde::Serialize;
2
3use super::{ContextEntry, to_value};
4use crate::{HasName, RenderCtx, sort_by_name};
5
6/// A CI workflow entry for template rendering.
7#[derive(Debug, Serialize)]
8pub struct Workflow {
9    pub name: String,
10    pub file: String,
11    pub triggers: Vec<String>,
12}
13
14impl HasName for Workflow {
15    fn name(&self) -> &str {
16        &self.name
17    }
18}
19
20/// Collect CI workflow metadata from `.github/workflows/*.yml` at the workspace root.
21#[must_use]
22pub fn collect(ctx: &RenderCtx<'_>) -> Vec<ContextEntry> {
23    if !ctx.is_root() {
24        return Vec::new();
25    }
26
27    let workflows_dir = ctx.root.join(".github/workflows");
28    let mut workflows: Vec<Workflow> = ctx
29        .workspace
30        .files_in(&workflows_dir)
31        .filter_map(|path| {
32            let ext = path.extension()?.to_str()?;
33
34            if ext != "yml" && ext != "yaml" {
35                return None;
36            }
37
38            let content = ctx.workspace.contents(path)?;
39            let file = path.file_name()?.to_str()?.to_string();
40
41            parse_workflow(content, file)
42        })
43        .collect();
44
45    if workflows.is_empty() {
46        return Vec::new();
47    }
48
49    sort_by_name(&mut workflows);
50
51    vec![("workflows", to_value(&workflows))]
52}
53
54fn parse_workflow(content: &str, file: String) -> Option<Workflow> {
55    let doc: serde_norway::Value = serde_norway::from_str(content).ok()?;
56    let mapping = doc.as_mapping()?;
57    let name = mapping
58        .get(serde_norway::Value::String("name".into()))?
59        .as_str()?
60        .to_string();
61    let triggers = mapping
62        .get(serde_norway::Value::String("on".into()))
63        .and_then(extract_triggers)
64        .unwrap_or_default();
65
66    Some(Workflow {
67        name,
68        file,
69        triggers,
70    })
71}
72
73fn extract_triggers(on: &serde_norway::Value) -> Option<Vec<String>> {
74    match on {
75        serde_norway::Value::String(s) => Some(vec![s.clone()]),
76        serde_norway::Value::Sequence(seq) => {
77            let triggers: Vec<String> = seq
78                .iter()
79                .filter_map(|v| v.as_str().map(String::from))
80                .collect();
81
82            Some(triggers)
83        }
84        serde_norway::Value::Mapping(map) => {
85            let triggers: Vec<String> = map
86                .keys()
87                .filter_map(|k| k.as_str().map(String::from))
88                .collect();
89
90            Some(triggers)
91        }
92        _ => None,
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use googletest::prelude::*;
99
100    use super::*;
101
102    #[gtest]
103    fn extract_triggers_string() -> Result<()> {
104        let val = serde_norway::Value::String("push".into());
105        let triggers = extract_triggers(&val).or_fail()?;
106
107        verify_that!(triggers, elements_are![eq("push")])
108    }
109
110    #[gtest]
111    fn extract_triggers_sequence() -> Result<()> {
112        let val = serde_norway::Value::Sequence(vec![
113            serde_norway::Value::String("push".into()),
114            serde_norway::Value::String("pull_request".into()),
115        ]);
116        let triggers = extract_triggers(&val).or_fail()?;
117
118        verify_that!(triggers, elements_are![eq("push"), eq("pull_request")])
119    }
120
121    #[gtest]
122    fn extract_triggers_mapping() -> Result<()> {
123        let yaml = "push:\n  branches: [main]\npull_request:\n  branches: [main]";
124        let val: serde_norway::Value = serde_norway::from_str(yaml).or_fail()?;
125        let mut triggers = extract_triggers(&val).or_fail()?;
126
127        triggers.sort();
128
129        verify_that!(triggers, elements_are![eq("pull_request"), eq("push")])
130    }
131
132    #[gtest]
133    fn extract_triggers_null() -> Result<()> {
134        let val = serde_norway::Value::Null;
135
136        verify_that!(extract_triggers(&val), none())
137    }
138}