use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use toml::{Parser, Table, Value};
use data::Container;
use enums::{Error, ExtractResult};
use issues::Issues;
use item_def::ItemDef;
use notify::Notify;
use toml_builder::TomlBuilder;
use toml_data::TomlData;
use toml_iter::{Next, TomlIter};
use item_value::ItemValue;
pub struct TomlDef
{
name : String,
optional : bool,
items : BTreeMap<String, Box<ItemDef>>,
notify : BTreeMap<String, Box<Notify>>
}
impl TomlDef
{
pub fn new() -> TomlDef
{
TomlDef {
name : String::from(""),
optional : false,
items : BTreeMap::new(),
notify : BTreeMap::new()
}
}
pub fn with_name<T:AsRef<str>>(
name : T
) -> TomlDef
{
TomlDef {
name : String::from(name.as_ref()),
optional : false,
items : BTreeMap::new(),
notify : BTreeMap::new()
}
}
pub fn optional(
mut self,
) -> Self
{
self.optional = true;
self
}
pub fn add<T : ItemDef>(
mut self,
item : T
) -> Self
{
self.ref_add(item);
self
}
pub fn ref_add<T : ItemDef>(
&mut self,
item : T
)
{
if self.items.contains_key(item.name())
{
panic!("Item [{}] already defined.", item.name());
}
let name = String::from(item.name());
self.items.insert(name, Box::new(item));
}
fn process<T:AsRef<str>>(
&self,
name : T,
input : &Table
) -> TomlBuilder
{
let name = name.as_ref();
let mut builder = TomlBuilder::new(name);
let mut iter = TomlIter::new(&input, &self.items, &self.notify);
while let Some(next) = iter.next()
{
match next
{
Next::NextItem(name, value, definition, notify) => {
builder.add(
name,
definition.extract(value),
notify
);
},
Next::UnknownInput(name, _) => {
builder.undefined_item(name);
},
Next::MissingInput(name, definition, notify) => {
match definition.default()
{
Some(value) => builder.add(name, ExtractResult::Item(value), notify),
None => {
builder.missing_item(name, definition.is_optional())
}
}
}
}
}
builder
}
pub fn parse_toml<T:AsRef<str>>(
&mut self,
input : T
) -> Result<TomlData, Issues>
{
let mut parser = Parser::new(input.as_ref());
if let Some(table) = parser.parse()
{
self.process("", &table).result()
}
else
{
let mut issues = Issues::new();
for error in parser.errors.iter()
{
let (row, col) = parser.to_linecol(error.lo);
issues.errors.push_back(
Error::Parse(row, col, error.desc.clone())
);
}
Err(issues)
}
}
pub fn load_toml<P : AsRef<Path>>(
&mut self,
file : P
) -> Result<TomlData, Issues>
{
match File::open(file)
{
Ok(mut file) => {
let mut buf = String::with_capacity(8192);
match file.read_to_string(&mut buf)
{
Ok(_) => {
self.parse_toml(buf)
},
Err(err) => {
let mut issues = Issues::new();
issues.errors.push_back(Error::FileError(err));
Err(issues)
}
}
},
Err(err) => {
let mut issues = Issues::new();
issues.errors.push_back(Error::FileError(err));
Err(issues)
}
}
}
}
impl Container for TomlDef
{
fn add_notify(
&mut self,
name : String,
notify : Box<Notify>
)
{
self.notify.insert(name, notify);
}
}
impl ItemDef for TomlDef
{
fn name(&self) -> &str
{
self.name.as_str()
}
fn extract(
&self,
value : &Value
) -> ExtractResult
{
if let Some(table) = value.as_table()
{
ExtractResult::Group(self.process(&self.name, table))
}
else
{
ExtractResult::incorrect_type("group")
}
}
fn is_optional(&self) -> bool
{
self.optional
}
fn default(&self) -> Option<ItemValue>
{
None
}
}