fluent4rs 2.3.1

Parser / codec for [Fluent FTL files](https://github.com/projectfluent/fluent/blob/master/spec/fluent.ebnf), written for [lingora](https://github.com/nigeleke/lingora) (a localization management program), and may be found to be useful outside of that context. It is not intended to replace any aspects of the [fluent-rs](https://github.com/projectfluent/fluent-rs) crate implemented by [Project Fluent](https://projectfluent.org/), and, for the majority of language translation needs, the reader is referred back to that crate.
Documentation
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use super::{Entry, Junk};
#[cfg(feature = "walker")]
use crate::walker::{Visitor, Walkable, Walker};

// [ResourceItem](crate::ast::ResourceItem) ::= ([Entry](crate::ast::Entry) | blank_block | [Junk](crate::ast::Junk))*
//
// Note: This is not defined in the fluent EBNF.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "hash", derive(Eq, PartialOrd, Ord, Hash))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum ResourceItem {
    Entry(Entry),
    BlankBlock(String),
    Junk(Junk),
}

#[cfg(feature = "walker")]
impl Walkable for ResourceItem {
    fn walk(&self, visitor: &mut dyn Visitor) {
        match self {
            Self::Entry(entry) => Walker::walk(entry, visitor),
            Self::Junk(junk) => Walker::walk(junk, visitor),
            _ => {}
        }
    }
}

impl std::fmt::Display for ResourceItem {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let value = match self {
            Self::Entry(entry) => entry.to_string(),
            Self::BlankBlock(block) => block.to_string(),
            Self::Junk(junk) => junk.to_string(),
        };
        write!(f, "{value}")
    }
}

/// [Resource](crate::ast::Resource) ::= ([Entry](crate::ast::Entry) | blank_block | [Junk](crate::ast::Junk))*
///
/// An FTL file defines a [Resource](crate::ast::Resource) consisting of entries.
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "hash", derive(Eq, PartialOrd, Ord, Hash))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Resource(Vec<ResourceItem>);

impl Resource {
    /// Return the parsed entries.
    /// ```rust
    /// # use fluent4rs::ast::*;
    /// # use fluent4rs::prelude::*;
    /// let test_text = r#"message = A message
    /// -term = A term
    /// "#;
    /// let resource = Parser::parse(test_text).unwrap();
    /// let entries = resource.entries();
    /// assert!(matches!(entries[0], Entry::Message(_)));
    /// assert!(matches!(entries[1], Entry::Term(_)));
    /// ```
    pub fn entries(&self) -> Vec<&Entry> {
        self.0
            .iter()
            .filter_map(|ri| match ri {
                ResourceItem::Entry(entry) => Some(entry),
                _ => None,
            })
            .collect::<Vec<_>>()
    }

    /// Return the parsed entries.
    /// ```rust
    /// # use fluent4rs::prelude::*;
    /// let test_garbage = r#"!@#$ garbage )(*&
    /// !@#$ more garbage )(*&
    /// "#;
    /// let resource = Parser::parse_with_junk(test_garbage).unwrap();
    /// let junk = resource.junk();
    /// let junk_item = junk[0];
    /// assert_eq!(junk_item.to_string(), String::from(test_garbage));
    /// ```
    pub fn junk(&self) -> Vec<&Junk> {
        self.0
            .iter()
            .filter_map(|ri| match ri {
                ResourceItem::Junk(junk) => Some(junk),
                _ => None,
            })
            .collect::<Vec<_>>()
    }
}

impl From<Vec<ResourceItem>> for Resource {
    fn from(value: Vec<ResourceItem>) -> Self {
        Self(value)
    }
}

impl From<Vec<Entry>> for Resource {
    fn from(value: Vec<Entry>) -> Self {
        let items = Vec::from_iter(value.into_iter().map(ResourceItem::Entry));
        Self(items)
    }
}

#[cfg(feature = "walker")]
impl Walkable for Resource {
    fn walk(&self, visitor: &mut dyn Visitor) {
        visitor.visit_resource(self);
        self.0.iter().for_each(|e| Walker::walk(e, visitor));
    }
}

impl std::fmt::Display for Resource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let stringified = self
            .0
            .iter()
            .map(|i| i.to_string())
            .collect::<Vec<_>>()
            .join("");
        write!(f, "{stringified}")
    }
}