use std::fmt;
use nom::{
IResult, Parser, branch::alt, bytes::complete::tag_no_case, character::complete::char,
multi::separated_list0, sequence::delimited,
};
use crate::lexer::{identifier::clause_label_name, whitespace::ws_opt};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GroupingAction {
By,
Without,
}
impl fmt::Display for GroupingAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GroupingAction::By => write!(f, "by"),
GroupingAction::Without => write!(f, "without"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Grouping {
pub action: GroupingAction,
pub labels: Vec<String>,
}
impl fmt::Display for Grouping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (", self.action)?;
for (i, label) in self.labels.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", label)?;
}
write!(f, ")")
}
}
pub fn grouping(input: &str) -> IResult<&str, Grouping> {
(
alt((
tag_no_case("by").map(|_| GroupingAction::By),
tag_no_case("without").map(|_| GroupingAction::Without),
)),
delimited(
(ws_opt, char('('), ws_opt),
separated_list0(
(ws_opt, char(','), ws_opt),
clause_label_name.map(String::from),
),
(ws_opt, char(')')),
),
)
.map(|(action, labels)| Grouping { action, labels })
.parse(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grouping_by() {
let (rest, g) = grouping("by (job)").unwrap();
assert!(rest.is_empty());
assert_eq!(g.action, GroupingAction::By);
assert_eq!(g.labels, vec!["job"]);
}
#[test]
fn test_grouping_without() {
let (rest, g) = grouping("without (instance)").unwrap();
assert!(rest.is_empty());
assert_eq!(g.action, GroupingAction::Without);
assert_eq!(g.labels, vec!["instance"]);
}
#[test]
fn test_grouping_multiple_labels() {
let (rest, g) = grouping("by (job, instance, method)").unwrap();
assert!(rest.is_empty());
assert_eq!(g.labels, vec!["job", "instance", "method"]);
}
#[test]
fn test_grouping_empty() {
let (rest, g) = grouping("by ()").unwrap();
assert!(rest.is_empty());
assert!(g.labels.is_empty());
}
#[test]
fn test_grouping_case_insensitive() {
let (rest, g) = grouping("BY (job)").unwrap();
assert!(rest.is_empty());
assert_eq!(g.action, GroupingAction::By);
let (rest, g) = grouping("WITHOUT (job)").unwrap();
assert!(rest.is_empty());
assert_eq!(g.action, GroupingAction::Without);
}
#[test]
fn test_grouping_display() {
let g = Grouping {
action: GroupingAction::By,
labels: vec!["job".to_string(), "instance".to_string()],
};
assert_eq!(format!("{}", g), "by (job, instance)");
let g = Grouping {
action: GroupingAction::Without,
labels: vec!["job".to_string()],
};
assert_eq!(format!("{}", g), "without (job)");
}
}