mod read;
mod shape;
pub use shape::{Shape, ValueKind};
use std::fs;
use std::io;
use std::path::Path;
use crate::diagnostics::{Diagnostic, Location};
use crate::doc::Doc;
#[derive(Debug, Clone, PartialEq, Eq)]
enum Stored {
Text(String),
Integer(i64),
Flag(bool),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Table {
values: Vec<(String, Stored, Location, Location)>,
tables: Vec<(String, Table)>,
arrays: Vec<(String, Vec<Table>)>,
}
impl Table {
#[must_use]
pub fn text(&self, key: &str) -> Option<&str> {
match self.stored(key) {
Some(Stored::Text(text)) => Some(text),
_ => None,
}
}
#[must_use]
pub fn integer(&self, key: &str) -> Option<i64> {
match self.stored(key) {
Some(Stored::Integer(number)) => Some(*number),
_ => None,
}
}
#[must_use]
pub fn flag(&self, key: &str) -> Option<bool> {
match self.stored(key) {
Some(Stored::Flag(flag)) => Some(*flag),
_ => None,
}
}
#[must_use]
pub fn table(&self, key: &str) -> Option<&Table> {
self.tables.iter().find(|(name, _)| name == key).map(|(_, table)| table)
}
#[must_use]
pub fn entries(&self, key: &str) -> &[Table] {
self.arrays.iter().find(|(name, _)| name == key).map_or(&[], |(_, entries)| entries.as_slice())
}
#[must_use]
pub fn location(&self, key: &str) -> Option<&Location> {
self.values.iter().find(|(name, _, _, _)| name == key).map(|(_, _, at, _)| at)
}
#[must_use]
pub fn value_location(&self, key: &str) -> Option<&Location> {
self.values.iter().find(|(name, _, _, _)| name == key).map(|(_, _, _, at)| at)
}
fn stored(&self, key: &str) -> Option<&Stored> {
self.values.iter().find(|(name, _, _, _)| name == key).map(|(_, value, _, _)| value)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Document {
root: Table,
diagnostics: Vec<Diagnostic>,
}
impl Document {
#[must_use]
pub fn parse(file: &str, text: &str, shape: &Shape) -> Self {
let doc = Doc::new(file, text);
let (root, mut diagnostics) = doc.parse_recoverable();
let start = doc.locate(&(0..0));
let root = read::table(&doc, &root, shape, "", &start, &mut diagnostics);
Self { root, diagnostics }
}
pub fn open(path: impl AsRef<Path>, shape: &Shape) -> io::Result<Self> {
let path = path.as_ref();
let text = fs::read_to_string(path)?;
let name = path.file_name().and_then(|name| name.to_str());
Ok(match name {
Some(name) => Self::parse(name, &text, shape),
None => Self::parse(&path.display().to_string(), &text, shape),
})
}
#[must_use]
pub fn root(&self) -> &Table {
&self.root
}
#[must_use]
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
#[must_use]
pub fn is_clean(&self) -> bool {
self.diagnostics.is_empty()
}
}
#[cfg(test)]
mod tests;