shadowplay 0.16.3

Utility for checking puppet syntax, a puppet manifest linter, a pretty printer, and a utility for exploring the Hiera.
Documentation
use crate::puppet_pp_printer::Printer;
use pretty::RcDoc;

pub fn statement_block_to_doc<EXTRA>(
    elt: &crate::puppet_lang::List<EXTRA, crate::puppet_lang::statement::Statement<EXTRA>>,
    with_parens: bool,
) -> RcDoc<()> {
    let inner = RcDoc::intersperse(elt.value.iter().map(|x| x.to_doc()), RcDoc::hardline())
        .append(crate::puppet_pp_printer::comment::to_doc(&elt.last_comment));

    if with_parens {
        RcDoc::text("{")
            .append(RcDoc::hardline())
            .append(inner)
            .nest(2)
            .append(RcDoc::hardline())
            .append(RcDoc::text("}"))
    } else {
        inner
    }
}

fn condition_and_statement_to_doc<'a, EXTRA>(
    keyword: RcDoc<'a, ()>,
    elt: &'a crate::puppet_lang::statement::ConditionAndStatement<EXTRA>,
) -> RcDoc<'a, ()> {
    crate::puppet_pp_printer::comment::comment_or(
        &elt.comment_before_elsif_word,
        RcDoc::hardline(),
        RcDoc::nil(),
    )
    .append(keyword)
    .append(RcDoc::softline())
    .append(crate::puppet_pp_printer::expression::to_doc(&elt.condition, false).nest(2))
    .append(RcDoc::softline())
    .append(statement_block_to_doc(&elt.body, true))
}

impl<EXTRA> Printer for crate::puppet_lang::statement::IfElse<EXTRA> {
    fn to_doc(&self) -> RcDoc<()> {
        let r = condition_and_statement_to_doc(RcDoc::text("if"), &self.condition);

        let r = r.append(RcDoc::intersperse(
            self.elsif_list.iter().map(|elt| {
                RcDoc::softline().append(condition_and_statement_to_doc(RcDoc::text("elsif"), elt))
            }),
            RcDoc::nil(),
        ));

        match &self.else_block {
            None => r,
            Some(elt) => r
                .append(crate::puppet_pp_printer::comment::comment_or(
                    &self.comment_before_else_word,
                    RcDoc::hardline(),
                    RcDoc::softline(),
                ))
                .append(RcDoc::text("else"))
                .append(crate::puppet_pp_printer::comment::comment_or(
                    &self.comment_before_else_body,
                    RcDoc::hardline(),
                    RcDoc::softline(),
                ))
                .append(statement_block_to_doc(elt, true)),
        }
    }
}

impl<EXTRA> Printer for crate::puppet_lang::expression::CaseVariant<EXTRA> {
    fn to_doc(&self) -> RcDoc<()> {
        match self {
            crate::puppet_lang::expression::CaseVariant::Term(v) => {
                crate::puppet_pp_printer::term::to_doc(v, false)
            }
            crate::puppet_lang::expression::CaseVariant::Default(_) => RcDoc::text("default"),
        }
    }
}

impl<EXTRA> Printer for crate::puppet_lang::statement::CaseElement<EXTRA> {
    fn to_doc(&self) -> RcDoc<()> {
        let matches_list = if self.matches.len() == 1 {
            self.matches.first().unwrap().to_doc()
        } else {
            RcDoc::text("[")
                .append(RcDoc::softline())
                .append(RcDoc::intersperse(
                    self.matches.iter().map(|x| x.to_doc()),
                    RcDoc::softline(),
                ))
                .append(RcDoc::softline())
                .append(RcDoc::softline())
                .append(RcDoc::text("]"))
        };

        crate::puppet_pp_printer::comment::comment_or(
            &self.comment,
            RcDoc::hardline(),
            RcDoc::nil(),
        )
        .append(matches_list)
        .append(RcDoc::softline_())
        .append(RcDoc::text(":"))
        .append(RcDoc::softline())
        .append(statement_block_to_doc(&self.body, true))
        .group()
    }
}

impl<EXTRA> Printer for crate::puppet_lang::statement::Case<EXTRA> {
    fn to_doc(&self) -> RcDoc<()> {
        let inner = RcDoc::intersperse(
            self.elements.value.iter().map(|x| x.to_doc()),
            RcDoc::hardline(),
        )
        .append(crate::puppet_pp_printer::comment::to_doc(
            &self.elements.last_comment,
        ));

        RcDoc::text("case")
            .append(RcDoc::softline())
            .append(crate::puppet_pp_printer::expression::to_doc(&self.condition, false).nest(2))
            .append(RcDoc::softline())
            .append(RcDoc::text("{"))
            .append(RcDoc::hardline())
            .append(inner)
            .nest(2)
            .append(RcDoc::hardline())
            .append(RcDoc::text("}"))
    }
}

impl<EXTRA> Printer for crate::puppet_lang::statement::ResourceDefaults<EXTRA> {
    fn to_doc(&self) -> RcDoc<()> {
        let inner = RcDoc::intersperse(
            self.args.value.iter().map(|(k, v)| {
                crate::puppet_pp_printer::term::to_doc(k, false)
                    .append(RcDoc::softline())
                    .append(RcDoc::text("=>"))
                    .append(RcDoc::softline())
                    .append(crate::puppet_pp_printer::expression::to_doc(v, false))
                    .append(RcDoc::text(","))
            }),
            RcDoc::hardline(),
        )
        .append(crate::puppet_pp_printer::comment::to_doc(
            &self.args.last_comment,
        ));

        RcDoc::text(&self.name)
            .append(RcDoc::softline())
            .append(RcDoc::text("{"))
            .append(RcDoc::hardline())
            .append(inner)
            .nest(2)
            .append(RcDoc::hardline())
            .append(RcDoc::text("}"))
    }
}

impl<EXTRA> Printer for crate::puppet_lang::statement::Statement<EXTRA> {
    fn to_doc(&self) -> RcDoc<()> {
        let v = match &self.value {
            crate::puppet_lang::statement::StatementVariant::Expression(v) => {
                crate::puppet_pp_printer::expression::to_doc(v, false)
            }
            crate::puppet_lang::statement::StatementVariant::RelationList(v) => v.to_doc(),
            crate::puppet_lang::statement::StatementVariant::IfElse(v) => v.to_doc(),
            crate::puppet_lang::statement::StatementVariant::Unless(v) => {
                condition_and_statement_to_doc(RcDoc::text("unless"), v)
            }
            crate::puppet_lang::statement::StatementVariant::Case(v) => v.to_doc(),
            crate::puppet_lang::statement::StatementVariant::Toplevel(v) => v.data.to_doc(),
            crate::puppet_lang::statement::StatementVariant::ResourceDefaults(v) => v.to_doc(),
        };

        crate::puppet_pp_printer::comment::comment_or(
            &self.comment,
            RcDoc::hardline(),
            RcDoc::nil(),
        )
        .append(v)
    }
}

#[test]
fn test_idempotence_short() {
    let cases = vec![
        "unless !$a {\n  $a = 1\n  $b = $a + 1\n}",
        "unless !$a {\n  $a = 1\n  unless (($a + $a + $a))\n  {\n    $b = $a + 1\n    unless (($a + $a\n          + $a)) {\n      $b = $a + 1\n      unless (($a + $a\n            + $a)) {\n        $b = $a + 1\n      }\n    }\n  }\n}",
        "if $a {\n  undef\n}",
        "if $a {\n  undef\n} else {\n  $c\n}",
        "if $a {\n  undef\n}\n#comment1\nelse {\n  1\n}",
        "if $a {\n  undef\n}\n#comment1\nelse\n#comment2\n{\n  1\n}",
        "if $a {\n  undef\n} \n#comment\nelsif !$a {\n  $a\n} elsif !$b {\n  $b\n} else {\n  1\n}",
        "if $a {\n  undef\n} elsif !$a {\n  $a\n}",
        "case $a {\n  \n  #comment\n  1: {\n    $b\n  }\n}",
        "case $a {\n  \n  #comment\n  1: {\n    $b\n  }\n  default: {\n    \n  }\n}",
        "Exec\n{\n  command                      =>\n    test,\n  provider                     =>\n    shell,\n  # comment\n  #line2\n  #line3\n}",
        "require a",
        "require a, b",
    ];

    for case in cases {
        let (_, v) = crate::puppet_parser::statement::parse_statement_list(
            crate::puppet_parser::Span::new(case),
        )
        .unwrap();

        let mut w = Vec::new();
        statement_block_to_doc(&v, false)
            .render(25, &mut w)
            .unwrap();
        let generated = String::from_utf8(w).unwrap();
        println!("{} ==>\n------\n{}\n------", case, generated);

        assert_eq!(&generated, case)
    }
}

#[test]
fn test_idempotence_long() {
    let cases = vec![
        "unless !$a {\n  $a = 1\n  unless (($a + $a + $a)) {\n    $b = $a + 1\n    unless (($a + $a + $a)) {\n      $b = $a + 1\n      unless (($a + $a + $a)) {\n        $b = $a + 1\n      }\n    }\n  }\n}",
        "Exec\n{\n  command                      => test,\n  provider                     => shell,\n  # comment\n  #line2\n  #line3\n}",
    ];

    for case in cases {
        let (_, v) = crate::puppet_parser::statement::parse_statement_list(
            crate::puppet_parser::Span::new(case),
        )
        .unwrap();

        let mut w = Vec::new();
        statement_block_to_doc(&v, false)
            .render(80, &mut w)
            .unwrap();
        let generated = String::from_utf8(w).unwrap();
        println!("{} ==>\n------\n{}\n------", case, generated);

        assert_eq!(&generated, case)
    }
}