use crate::macro_impl::derive;
use std::collections::HashMap;
use std::path::PathBuf;
use fluent_syntax::ast::{Entry, Message, Pattern};
use miette::{Diagnostic, NamedSource, SourceSpan};
use thiserror::Error;
mod ast;
mod group;
pub use group::{Group, GroupError};
#[derive(Diagnostic, Debug, Error)]
#[error("couldn't parse Fluent source code")]
pub struct ParserError {
#[label("{kind}")]
span: SourceSpan,
kind: fluent_syntax::parser::ErrorKind,
}
#[derive(Diagnostic, Debug, Error)]
pub enum Error {
#[error("the reference `${fluent_name}` doesn't match a Rust field `{rust_name}`")]
InvalidReference {
fluent_name: String,
rust_name: String,
#[source_code]
source_code: NamedSource<String>,
#[label("This references `{rust_name}` which doesn't exist")]
span: SourceSpan,
#[help]
valid_references: String,
},
#[error(r#"message "{unexpected_key}" from `{locale}` is not in the canonical `{canonical_locale}` locale"#)]
#[help("the canonical locale must include all keys!")]
UnexpectedKey {
unexpected_key: String,
locale: String,
canonical_locale: String,
#[source_code]
source_code: NamedSource<String>,
#[label("This key isn't in the `{canonical_locale}` locale")]
span: SourceSpan,
},
#[error("unable to parse Fluent source code")]
ParserErrors {
#[source_code]
source_code: NamedSource<String>,
#[related]
related: Vec<ParserError>,
},
}
#[derive(Clone, Copy, Debug)]
pub struct MessageContext<'context> {
source: &'context SourceFile,
pattern: &'context Pattern<String>,
derive_context: &'context derive::Context,
}
#[derive(Clone, Debug)]
struct SourceFile {
named_source: NamedSource<String>,
messages: HashMap<String, Message<String>>,
}
impl SourceFile {
fn new(path: PathBuf) -> Result<Self, Error> {
let file_contents = std::fs::read_to_string(&path).unwrap();
let named_source = NamedSource::new(path.to_string_lossy(), file_contents.clone());
let syntax_tree = match fluent_syntax::parser::parse(file_contents.clone()) {
Ok(syntax_tree) => syntax_tree,
Err((_partial_syntax_tree, parser_errors)) => {
// Map the `fluent_syntax` errors to `miette::Diagnostic` errors
let related = parser_errors
.into_iter()
.map(|error| ParserError {
span: SourceSpan::from(error.slice.unwrap_or(error.pos)),
kind: error.kind,
})
.collect();
return Err(Error::ParserErrors {
source_code: NamedSource::new(path.to_string_lossy(), file_contents),
related,
});
}
};
// Keep track of all the messages in our Fluent file
let mut messages = HashMap::with_capacity(syntax_tree.body.len());
for entry in &syntax_tree.body {
match entry {
Entry::Message(message_entry) => {
let message_id = message_entry.id.name.to_owned();
// Insert this message id (and make sure it's unique!)
let previous_value = messages.insert(message_id, message_entry.clone());
assert!(previous_value.is_none());
}
Entry::Term(_) => todo!(),
Entry::Junk { .. } => todo!(),
// Ignore comments
Entry::Comment(_) | Entry::GroupComment(_) | Entry::ResourceComment(_) => continue,
}
}
Ok(Self {
named_source,
messages,
})
}
/// Unique message IDs, to compare with other locales
fn message_ids(&self) -> impl Iterator<Item = &str> {
self.messages.keys().map(String::as_str)
}
fn contains_expression(&self, id: &str) -> bool {
self.messages.contains_key(id)
}
fn try_remove_expression(&mut self, id: &str) -> Option<Message<String>> {
self.messages.remove(id)
}
}