Merge branch 'storage'
This commit is contained in:
commit
1ceada8115
5 changed files with 351 additions and 16 deletions
|
@ -1,6 +1,7 @@
|
||||||
#[macro_use] extern crate clap;
|
#[macro_use] extern crate clap;
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
extern crate config;
|
extern crate config;
|
||||||
|
extern crate regex;
|
||||||
|
|
||||||
use cli::CliConfig;
|
use cli::CliConfig;
|
||||||
use configuration::Configuration;
|
use configuration::Configuration;
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
pub use std::path::Path;
|
|
||||||
pub use std::fs::File;
|
|
||||||
pub use std::error::Error;
|
|
||||||
|
|
||||||
pub use runtime::Runtime;
|
|
||||||
|
|
||||||
pub trait StorageBackend {
|
|
||||||
|
|
||||||
fn name(&self) -> String;
|
|
||||||
|
|
||||||
fn create(&self, file : File) -> Option<Error>;
|
|
||||||
fn read(&self, path: Path) -> Result<File, Error>;
|
|
||||||
fn update(&self, file : File) -> Option<Error>;
|
|
||||||
fn destroy(&self, path: Path) -> Option<Error>;
|
|
||||||
|
|
||||||
}
|
|
167
src/storage/file.rs
Normal file
167
src/storage/file.rs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FileHeaderSpec {
|
||||||
|
Null,
|
||||||
|
Bool,
|
||||||
|
Integer,
|
||||||
|
UInteger,
|
||||||
|
Float,
|
||||||
|
Text,
|
||||||
|
Key { name: String, value_type: Box<FileHeaderSpec> },
|
||||||
|
Map { keys: Vec<FileHeaderSpec> },
|
||||||
|
Array { allowed_types: Box<Vec<FileHeaderSpec>> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FileHeaderData {
|
||||||
|
Null,
|
||||||
|
Bool(bool),
|
||||||
|
Integer(i64),
|
||||||
|
UInteger(u64),
|
||||||
|
Float(f64),
|
||||||
|
Text(String),
|
||||||
|
Key { name: String, value: Box<FileHeaderData> },
|
||||||
|
Map { keys: Vec<FileHeaderData> },
|
||||||
|
Array { values: Box<Vec<FileHeaderData>> },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FileData : Sized {
|
||||||
|
fn get_fulltext(&self) -> String;
|
||||||
|
fn get_abbrev(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FileHeaderSpec {
|
||||||
|
|
||||||
|
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
&FileHeaderSpec::Null => write!(fmt, "NULL"),
|
||||||
|
&FileHeaderSpec::Bool => write!(fmt, "Bool"),
|
||||||
|
&FileHeaderSpec::Integer => write!(fmt, "Integer"),
|
||||||
|
&FileHeaderSpec::UInteger => write!(fmt, "UInteger"),
|
||||||
|
&FileHeaderSpec::Float => write!(fmt, "Float"),
|
||||||
|
&FileHeaderSpec::Text => write!(fmt, "Text"),
|
||||||
|
&FileHeaderSpec::Key{name: ref n, value_type: ref vt} => {
|
||||||
|
write!(fmt, "Key({:?}) -> {:?}", n, vt)
|
||||||
|
}
|
||||||
|
&FileHeaderSpec::Map{keys: ref ks} => {
|
||||||
|
write!(fmt, "Map -> {:?}", ks)
|
||||||
|
}
|
||||||
|
&FileHeaderSpec::Array{allowed_types: ref at} => {
|
||||||
|
write!(fmt, "Array({:?})", at)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MatchError<'a> {
|
||||||
|
summary: String,
|
||||||
|
expected: &'a FileHeaderSpec,
|
||||||
|
found: &'a FileHeaderData
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MatchError<'a> {
|
||||||
|
|
||||||
|
pub fn new(s: String,
|
||||||
|
ex: &'a FileHeaderSpec,
|
||||||
|
found: &'a FileHeaderData) -> MatchError<'a> {
|
||||||
|
MatchError {
|
||||||
|
summary: s,
|
||||||
|
expected: ex,
|
||||||
|
found: found,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(&self) -> String {
|
||||||
|
format!("MatchError: {:?}\nExpected: {:?}\nFound: {:?}\n",
|
||||||
|
self.summary, self.expected, self.found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Error for MatchError<'a> {
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
&self.summary[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&Error> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Debug for MatchError<'a> {
|
||||||
|
|
||||||
|
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(fmt, "{}", self.format());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for MatchError<'a> {
|
||||||
|
|
||||||
|
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(fmt, "{}", self.format());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_header_spec<'a>(spec: &'a FileHeaderSpec, data: &'a FileHeaderData)
|
||||||
|
-> Option<MatchError<'a>>
|
||||||
|
{
|
||||||
|
match (spec, data) {
|
||||||
|
(&FileHeaderSpec::Null, &FileHeaderData::Null) => { }
|
||||||
|
(&FileHeaderSpec::Bool, &FileHeaderData::Bool(_)) => { }
|
||||||
|
(&FileHeaderSpec::Integer, &FileHeaderData::Integer(_)) => { }
|
||||||
|
(&FileHeaderSpec::UInteger, &FileHeaderData::UInteger(_)) => { }
|
||||||
|
(&FileHeaderSpec::Float, &FileHeaderData::Float(_)) => { }
|
||||||
|
(&FileHeaderSpec::Text, &FileHeaderData::Text(_)) => { }
|
||||||
|
|
||||||
|
(
|
||||||
|
&FileHeaderSpec::Key{name: ref kname, value_type: ref vtype},
|
||||||
|
&FileHeaderData::Key{name: ref n, value: ref val}
|
||||||
|
) => {
|
||||||
|
if kname != n {
|
||||||
|
// error
|
||||||
|
}
|
||||||
|
return match_header_spec(&*vtype, &*val);
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
&FileHeaderSpec::Map{keys: ref sks},
|
||||||
|
&FileHeaderData::Map{keys: ref dks}
|
||||||
|
) => {
|
||||||
|
for (s, d) in sks.iter().zip(dks.iter()) {
|
||||||
|
let res = match_header_spec(s, d);
|
||||||
|
if res.is_some() {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
&FileHeaderSpec::Array{allowed_types: ref vtypes},
|
||||||
|
&FileHeaderData::Array{values: ref vs}
|
||||||
|
) => {
|
||||||
|
for (t, v) in vtypes.iter().zip(vs.iter()) {
|
||||||
|
let res = match_header_spec(t, v);
|
||||||
|
if res.is_some() {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(k, v) => {
|
||||||
|
return Some(MatchError::new(String::from("Expected type does not match found type"),
|
||||||
|
k, v
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
9
src/storage/mod.rs
Normal file
9
src/storage/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
pub use std::path::Path;
|
||||||
|
pub use std::fs::File;
|
||||||
|
pub use std::error::Error;
|
||||||
|
|
||||||
|
pub use runtime::Runtime;
|
||||||
|
|
||||||
|
mod file;
|
||||||
|
mod parser;
|
||||||
|
|
174
src/storage/parser.rs
Normal file
174
src/storage/parser.rs
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
use regex::Regex;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use super::file::*;
|
||||||
|
|
||||||
|
pub struct ParserError {
|
||||||
|
summary: String,
|
||||||
|
parsertext: String,
|
||||||
|
index: i32,
|
||||||
|
explanation: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParserError {
|
||||||
|
fn new(sum: &'static str, text: String, idx: i32, expl: &'static str) -> ParserError {
|
||||||
|
ParserError {
|
||||||
|
summary: String::from(sum),
|
||||||
|
parsertext: text,
|
||||||
|
index: idx,
|
||||||
|
explanation: Some(String::from(expl)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short(sum: &'static str, text: String, idx: i32) -> ParserError {
|
||||||
|
ParserError {
|
||||||
|
summary: String::from(sum),
|
||||||
|
parsertext: text,
|
||||||
|
index: idx,
|
||||||
|
explanation: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ParserError {
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
&self.summary[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&Error> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for ParserError {
|
||||||
|
|
||||||
|
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(fmt, "ParserError: {}\n\n", self.summary);
|
||||||
|
|
||||||
|
if let Some(ref e) = self.explanation {
|
||||||
|
write!(fmt, "{}\n\n", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(fmt, "On position {}\nin\n{}", self.index, self.parsertext);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ParserError {
|
||||||
|
|
||||||
|
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(fmt, "ParserError: {}", self.summary);
|
||||||
|
|
||||||
|
if let Some(ref e) = self.explanation {
|
||||||
|
write!(fmt, "\n\n{}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub trait FileHeaderParser : Sized {
|
||||||
|
fn new(spec: &FileHeaderSpec) -> Self;
|
||||||
|
fn read(&self, string: Option<String>) -> Result<FileHeaderData, ParserError>;
|
||||||
|
fn write(&self, data: &FileHeaderData) -> Result<String, ParserError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FileDataParser<FD: FileData + Sized> : Sized {
|
||||||
|
fn new() -> Self;
|
||||||
|
fn read(&self, string: Option<String>) -> Result<FD, ParserError>;
|
||||||
|
fn write(&self, data: &FD) -> Result<String, ParserError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextTpl = (Option<String>, Option<String>);
|
||||||
|
|
||||||
|
pub struct Parser<HP, DP>
|
||||||
|
{
|
||||||
|
headerp : HP,
|
||||||
|
datap : DP,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<HP, DP> Parser<HP, DP> where
|
||||||
|
HP: FileHeaderParser,
|
||||||
|
{
|
||||||
|
|
||||||
|
fn new(headerp: HP, datap: DP) -> Parser<HP, DP> {
|
||||||
|
Parser {
|
||||||
|
headerp: headerp,
|
||||||
|
datap: datap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read<FD>(&self, s: String) -> Result<(FileHeaderData, FD), ParserError>
|
||||||
|
where FD: FileData + Sized,
|
||||||
|
DP: FileDataParser<FD>
|
||||||
|
{
|
||||||
|
let divided = self.divide_text(&s);
|
||||||
|
|
||||||
|
if divided.is_err() {
|
||||||
|
return Err(divided.err().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (header, data) = divided.ok().unwrap();
|
||||||
|
|
||||||
|
let h_parseres = self.headerp.read(header);
|
||||||
|
let d_parseres = self.datap.read(data);
|
||||||
|
|
||||||
|
if h_parseres.is_err() {
|
||||||
|
return Err(h_parseres.err().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if d_parseres.is_err() {
|
||||||
|
return Err(d_parseres.err().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((h_parseres.ok().unwrap(), d_parseres.ok().unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<FD>(&self, tpl : (FileHeaderData, FD)) -> Result<String, ParserError>
|
||||||
|
where FD: FileData + Sized,
|
||||||
|
DP: FileDataParser<FD>
|
||||||
|
{
|
||||||
|
let (header, data) = tpl;
|
||||||
|
let h_text = self.headerp.write(&header);
|
||||||
|
let d_text = self.datap.write(&data);
|
||||||
|
|
||||||
|
if h_text.is_err() {
|
||||||
|
return Err(h_text.err().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if d_text.is_err() {
|
||||||
|
return Err(d_text.err().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(h_text.ok().unwrap() + &d_text.ok().unwrap()[..])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn divide_text(&self, text: &String) -> Result<TextTpl, ParserError> {
|
||||||
|
let re = Regex::new(r"(?m)^\-\-\-$\n(.*)^\-\-\-$\n(.*)").unwrap();
|
||||||
|
|
||||||
|
let captures = re.captures(&text[..]).unwrap_or(
|
||||||
|
return Err(ParserError::new("Cannot run regex on text",
|
||||||
|
text.clone(), 0,
|
||||||
|
"Cannot run regex on text to divide it into header and content."))
|
||||||
|
);
|
||||||
|
|
||||||
|
if captures.len() != 2 {
|
||||||
|
return Err(ParserError::new("Unexpected Regex output",
|
||||||
|
text.clone(), 0,
|
||||||
|
"The regex to divide text into header and content had an unexpected output."))
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = captures.at(0).map(|s| String::from(s));
|
||||||
|
let content = captures.at(1).map(|s| String::from(s));
|
||||||
|
Ok((header, content))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue