use crate::plugins::Plugins;
use glue::prelude::*;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
pub type LockableState<'a> = Arc<Mutex<&'a mut State>>;
pub type Attributes = Option<HashMap<String, String>>;
pub type Elements = Option<Vec<Token>>;
pub type Import = Result<Token, ImportError>;
pub type Imports = HashMap<PathBuf, Option<Import>>;
pub type TagDeclarations = HashMap<String, Attributes>;
pub type TagReferences = Option<Vec<String>>;
#[derive(Debug, Clone, PartialEq)]
pub enum ImportError {
IO(String),
Syntax(String),
}
fn read_file(filename: PathBuf) -> Result<String, String> {
match File::open(filename) {
Err(_) => Err("file not found".into()),
Ok(mut file) => {
let mut input = String::new();
match file.read_to_string(&mut input) {
Err(_) => Err("error reading file".into()),
Ok(_) => Ok(input),
}
}
}
}
pub struct State {
pub tags: TagDeclarations,
pub imports: Imports,
pub paths: Vec<PathBuf>,
pub plugins: Plugins,
}
impl Default for State {
fn default() -> Self {
State {
tags: TagDeclarations::default(),
imports: Imports::default(),
paths: vec![std::env::current_dir().unwrap()],
plugins: Plugins::default(),
}
}
}
impl State {
pub fn lockable(&mut self) -> LockableState {
Arc::new(Mutex::new(self))
}
pub fn import_file(&mut self, filename: &str) -> Import {
let filename = PathBuf::from(filename);
let filename = self.current_dir().join(filename);
let is_etch = if let Some(ext) = filename.extension() {
if ext == "etch" {
true
} else {
false
}
} else {
false
};
self.imports.entry(filename.clone()).or_insert(None);
self.paths.push(
filename
.parent()
.expect("No parent directory.")
.to_path_buf(),
);
let input = match read_file(filename.clone()) {
Ok(input) => input,
Err(error) => {
self.imports
.entry(filename.to_path_buf())
.and_modify(|item| *item = Some(Err(ImportError::IO(error))));
return self.imports.get(&filename).unwrap().to_owned().unwrap();
}
};
let input = input.as_str();
if is_etch {
let state = self.lockable();
let result = Some(match crate::block::document(state.clone()).parse(input) {
Ok((data, _)) => Ok(Token::Fragment {
children: Some(data),
}),
Err(error) => Err(ImportError::Syntax(format!(
"{}",
error.to_printable(filename.to_str().unwrap(), input)
))),
});
let mut state = state.lock().unwrap();
state
.imports
.entry(filename.to_path_buf())
.and_modify(|item| {
*item = result;
});
state.paths.pop();
state.imports.get(&filename).unwrap().to_owned().unwrap()
} else {
Ok(Token::Element {
name: "pre".into(),
attributes: None,
tags: None,
children: Some(vec![Token::Text { text: input.into() }]),
})
}
}
pub fn current_dir(&self) -> &PathBuf {
self.paths.last().unwrap()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Fragment {
children: Elements,
},
Element {
name: String,
attributes: Attributes,
tags: TagReferences,
children: Elements,
},
Span {
attributes: Attributes,
tags: TagReferences,
children: Elements,
},
Html {
html: String,
},
Text {
text: String,
},
Placeholder {
id: String,
},
Whitespace,
Removed,
}
impl Token {
pub fn element_name(&self) -> Option<&str> {
match self {
Token::Element { name, .. } => Some(name),
_ => None,
}
}
}
pub trait WithTags {
fn with_tags(self, tags: Vec<String>) -> Self;
}
impl WithTags for Token {
fn with_tags(self, tags: Vec<String>) -> Self {
match self {
Token::Element {
name,
attributes,
children,
..
} => Token::Element {
tags: Some(tags),
name,
attributes,
children,
},
Token::Span {
attributes,
children,
..
} => Token::Span {
tags: Some(tags),
attributes,
children,
},
Token::Text { text } => Token::Span {
tags: Some(tags),
attributes: None,
children: Some(vec![Token::Text { text }]),
},
Token::Placeholder { id } => Token::Span {
tags: Some(tags),
attributes: None,
children: Some(vec![Token::Placeholder { id }]),
},
_ => self,
}
}
}