flowcrafter 0.3.0

Create and manage workflows for GitHub Actions
Documentation
use std::fmt::{Display, Formatter};

use crate::error::Error;
use crate::fragment::Fragment;
use crate::workflow::Workflow;

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Renderer<'a> {
    workflow: &'a Fragment,
    jobs: &'a [Fragment],
}

impl<'a> Renderer<'a> {
    pub fn new(workflow: &'a Fragment, jobs: &'a [Fragment]) -> Self {
        Self { workflow, jobs }
    }

    pub fn render(&self) -> Result<Workflow, Error> {
        let mut rendered = Vec::new();

        rendered.push(self.workflow.template().get().into());

        if !self.workflow.template().get().contains("jobs:") {
            rendered.push("jobs:".into());
        }

        let rendered_jobs = self
            .jobs
            .iter()
            .map(|job| self.indent(job.template().get()))
            .collect::<Vec<String>>()
            .join("\n");
        rendered.push(rendered_jobs);

        let rendered = rendered.join("\n");

        Ok(Workflow::new(rendered))
    }

    fn indent(&self, content: &str) -> String {
        let mut indented = String::new();

        for line in content.lines() {
            if line.is_empty() {
                indented.push('\n');
            } else {
                indented.push_str(&format!("  {}\n", line));
            }
        }

        indented
    }
}

impl<'a> Display for Renderer<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Renderer {{ workflow: {}, job_count: {} }}",
            self.workflow,
            self.jobs.len()
        )
    }
}

#[cfg(test)]
mod tests {
    use indoc::indoc;

    use super::*;

    fn fragment(template: &str) -> Fragment {
        Fragment::builder()
            .name("test")
            .template(template.into())
            .build()
    }

    #[test]
    fn render_without_jobs_section() {
        let workflow = fragment(indoc!(
            r#"
            ---
            name: Workflow
            "#
        ));

        let jobs = vec![
            fragment(indoc!(
                r#"
                first:
                  name: First

                  runs-on: ubuntu-latest
                "#
            )),
            fragment(indoc!(
                r#"
                second:
                  name: Second
                "#
            )),
        ];

        let rendered = Renderer::new(&workflow, &jobs).render().unwrap();

        assert_eq!(
            indoc!(
                r#"
                ---
                name: Workflow

                jobs:
                  first:
                    name: First

                    runs-on: ubuntu-latest

                  second:
                    name: Second
                "#
            ),
            rendered.get()
        );
    }

    #[test]
    fn render_with_jobs_section() {
        let workflow = fragment(indoc!(
            r#"
            ---
            name: Workflow

            jobs:
              first:
                name: First
            "#
        ));

        let jobs = vec![fragment(indoc!(
            r#"
            second:
              name: Second
            "#
        ))];

        let rendered = Renderer::new(&workflow, &jobs).render().unwrap();

        assert_eq!(
            indoc!(
                r#"
                ---
                name: Workflow

                jobs:
                  first:
                    name: First

                  second:
                    name: Second
                "#
            ),
            rendered.get()
        );
    }

    #[test]
    fn trait_send() {
        fn assert_send<T: Send>() {}
        assert_send::<Renderer>();
    }

    #[test]
    fn trait_sync() {
        fn assert_sync<T: Sync>() {}
        assert_sync::<Renderer>();
    }

    #[test]
    fn trait_unpin() {
        fn assert_unpin<T: Unpin>() {}
        assert_unpin::<Renderer>();
    }
}