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 nom::{
    branch::alt,
    bytes::complete::tag,
    character::complete::{alpha1, alphanumeric1, anychar, digit1},
    combinator::{map, opt, recognize, verify},
    multi::many0,
    sequence::{pair, tuple},
};
use crate::puppet_lang::identifier::LowerIdentifier;

use crate::puppet_parser::{range::Range, IResult, Span};

pub fn char_lower(input: Span) -> IResult<char> {
    verify(anychar, |c| c.is_ascii_lowercase() || *c == '_')(input)
}

pub fn char_upper(input: Span) -> IResult<char> {
    verify(anychar, |c| c.is_ascii_uppercase())(input)
}

pub fn identifier(input: Span) -> IResult<Span> {
    recognize(tuple((
        alt((alpha1, tag("_"))),
        many0(alt((alphanumeric1, tag("_")))),
    )))(input)
}

pub fn lowercase_identifier(input: Span) -> IResult<Span> {
    recognize(tuple((
        alt((recognize(char_lower), tag("_"))),
        many0(alt((
            recognize(char_lower),
            recognize(char_upper),
            digit1,
            tag("_"),
        ))),
    )))(input)
}

pub fn camel_case_identifier(input: Span) -> IResult<Span> {
    recognize(tuple((char_upper, many0(alt((alphanumeric1, tag("_")))))))(input)
}

pub fn lower_identifier_with_ns(input: Span) -> IResult<Vec<Span>> {
    nom::multi::separated_list1(tag("::"), lowercase_identifier)(input)
}

pub fn camelcase_identifier_with_ns(input: Span) -> IResult<Vec<Span>> {
    nom::multi::separated_list1(tag("::"), camel_case_identifier)(input)
}

pub fn camelcase_identifier_with_ns_located(
    input: Span,
) -> IResult<crate::puppet_lang::identifier::CamelIdentifier<Range>> {
    map(camelcase_identifier_with_ns, |name| {
        crate::puppet_lang::identifier::CamelIdentifier {
            extra: Range::from((name.first().unwrap(), name.last().unwrap())),
            name: name.iter().map(|v| v.to_string()).collect(),
        }
    })(input)
}

pub fn anycase_identifier_with_ns(input: Span) -> IResult<LowerIdentifier<Range>> {
    map(
        pair(
            opt(tag("::")),
            nom::multi::separated_list1(
                tag("::"),
                alt((camel_case_identifier, lowercase_identifier)),
            ),
        ),
        |(toplevel_tag, name)| {
            let first = toplevel_tag
                .as_ref()
                .unwrap_or_else(|| name.first().unwrap());
            LowerIdentifier {
                extra: Range::from((first, name.last().unwrap())),
                name: name.iter().map(|v| v.to_string()).collect(),
                is_toplevel: toplevel_tag.is_some(),
            }
        },
    )(input)
}

pub fn identifier_with_toplevel(input: Span) -> IResult<LowerIdentifier<Range>> {
    let (input, toplevel_mark) = opt(tag("::"))(input)?;
    map(lower_identifier_with_ns, move |name| {
        let extra = match &toplevel_mark {
            Some(v) => Range::from((v, name.last().unwrap())),
            None => Range::from((name.first().unwrap(), name.last().unwrap())),
        };
        LowerIdentifier {
            name: name.iter().map(|v| v.to_string()).collect(),
            is_toplevel: toplevel_mark.is_some(),
            extra,
        }
    })(input)
}

#[test]
fn test_identifier_with_toplevel() {
    assert_eq!(
        identifier_with_toplevel(Span::new("::asd")).unwrap().1,
        LowerIdentifier {
            name: vec!["asd".to_owned()],
            is_toplevel: true,
            extra: Range::new(0, 1, 1, 4, 1, 5)
        }
    );
    assert_eq!(
        identifier_with_toplevel(Span::new("asd")).unwrap().1,
        LowerIdentifier {
            name: vec!["asd".to_owned()],
            is_toplevel: false,
            extra: Range::new(0, 1, 1, 2, 1, 3)
        }
    );
    assert_eq!(
        identifier_with_toplevel(Span::new("asd::def")).unwrap().1,
        LowerIdentifier {
            name: vec!["asd".to_owned(), "def".to_owned()],
            is_toplevel: false,
            extra: Range::new(0, 1, 1, 7, 1, 8)
        }
    );
}