facet-styx 4.0.0

Facet integration for the Styx configuration language
Documentation
use std::collections::HashMap;
use std::sync::Once;

use facet::Facet;

use crate::{Documented, from_str};

fn init_tracing() {
    static ONCE: Once = Once::new();
    ONCE.call_once(|| {
        let _ = tracing_subscriber::fmt()
            .with_env_filter(
                tracing_subscriber::EnvFilter::from_default_env()
                    .add_directive("facet_styx=trace".parse().unwrap())
                    .add_directive("facet_format=trace".parse().unwrap()),
            )
            .with_test_writer()
            .try_init();
    });
}

#[derive(Facet, Debug, PartialEq)]
#[facet(untagged)]
#[repr(u8)]
enum ExprPayload {
    Scalar(String),
    Seq(Vec<Expr>),
    Object(ExprObject),
}

#[derive(Facet, Debug, PartialEq)]
struct ExprObject {
    #[facet(flatten)]
    fields: HashMap<Documented<String>, Expr>,
}

#[derive(Facet, Debug, PartialEq)]
#[facet(rename_all = "snake_case")]
#[repr(u8)]
enum Expr {
    Template(Box<TemplateDecl>),
    Regex(Vec<Expr>),
    #[facet(rename = "Any")]
    Any,
    #[facet(other)]
    Other {
        #[facet(tag)]
        tag: Option<String>,
        #[facet(content)]
        content: Option<ExprPayload>,
    },
}

#[derive(Facet, Debug, PartialEq)]
struct TemplateDecl {
    params: Vec<Expr>,
    body: Box<TemplateBody>,
}

#[derive(Facet, Debug, PartialEq)]
struct TemplateBody {
    syntax: Box<Expr>,
    highlight: Option<String>,
}

#[derive(Facet, Debug, PartialEq)]
struct Doc {
    v: Expr,
}

#[test]
fn mixed_expr_accepts_any_tag() {
    init_tracing();
    let parsed: Doc = from_str("v @Any").unwrap();
    assert_eq!(parsed.v, Expr::Any);
}

#[test]
fn mixed_expr_accepts_sequence_with_object_binding() {
    init_tracing();
    let parsed: Doc = from_str("v ({text @Any})").unwrap();
    match parsed.v {
        Expr::Other {
            tag: None,
            content: Some(ExprPayload::Seq(items)),
        } => assert_eq!(items.len(), 1),
        other => panic!("expected untagged sequence, got {other:?}"),
    }
}

#[test]
fn mixed_expr_accepts_template_tagged_struct() {
    init_tracing();
    let parsed: Doc = from_str(
        r#"
v @template{
    params ({text @Any})
    body {
        syntax text
        highlight keyword
    }
}
"#,
    )
    .unwrap();

    match parsed.v {
        Expr::Template(template) => {
            assert_eq!(template.params.len(), 1);
            assert_eq!(template.body.highlight.as_deref(), Some("keyword"));
        }
        other => panic!("expected template, got {other:?}"),
    }
}