<commit_before>use token::Token;
use tokenizer::{Tokenizer, LexerError};

pub type Program = Vec<Statement>;

#[derive(PartialEq, Debug)]
pub enum Statement {
    NamedFunction {
        name: String,
        definition: Function,
    },
    AnonymousFunction(Function),
    Assignment {
        name: String,
        value: Expr,
    },
    Expression(Expr),
}

pub type Expr = Vec<ExprItem>;

#[derive(PartialEq, Debug)]
pub enum ExprItem {
    AnonymousFunction(Function),
    SingleTerm(Term),
    Group(Expr),
}

#[derive(PartialEq, Debug)]
pub struct Function {
    params: Vec<String>,
    body: Expr,
}

#[derive(PartialEq, Debug)]
pub enum Term {
    Symbol(String),
    Number(f64),
    StringLiteral(String),
}

pub struct Parser {
    lexer: Tokenizer,
}

pub type ParseResult<T> = Result<T, ParseError>;

#[derive(PartialEq, Debug)]
pub enum ParseError {
    ExpectedToken(Token),
    SyntaxError(LexerError),
}

impl Parser {
    pub fn new(lexer: Tokenizer) -> Parser {
        Parser {
            lexer: lexer,
        }
    }

    pub fn parse(&mut self) -> ParseResult<Program> {
        let mut statements: Program = Vec::new();

        loop {
            let statement = try!(self.statement());
            if statement.is_none() {
                break;
            }

            let statement = statement.unwrap();
            statements.push(statement);
        }

        Ok(statements)
    }

    fn statement(&mut self) -> ParseResult<Option<Statement>> {
        loop {
            let line = try!(self.line());
            if line.is_none() {
                return Ok(None);
            }

            let line = line.unwrap();
            if line.is_empty() {
                continue;
            }

            return Ok(Some(try!(self.dispatch_statement(line))));
        }
    }

    fn line(&mut self) -> ParseResult<Option<Vec<Token>>> {
        let mut line_tokens: Vec<Token> = Vec::new();

        let mut reached_eof = true;
        for next_token in &mut self.lexer {
            let next_token = try!(
                next_token.map_err(|e| ParseError::SyntaxError(e))
            );

            if next_token == Token::EOL {
                reached_eof = false;
                break;
            }

            line_tokens.push(next_token);
        }

        if reached_eof && line_tokens.is_empty() {
            return Ok(None);
        }

        Ok(Some(line_tokens))
    }

    fn dispatch_statement(&self, statement_tokens: Vec<Token>) -> ParseResult<Statement> {
        assert!(statement_tokens.len() > 0, "Got zero tokens to dispatch");
        // looking for either assignment or a named function
        // both are a collection of only symbols followed by equals
        let mut equals = None;
        for (i, token) in statement_tokens.iter().enumerate() {
            if *token == Token::Equals && i > 0 {
                equals = Some(i);
                break;
            }
            else if let Token::Symbol(_) = *token {
                // Symbols are good, continue
                continue;
            }
            else if *token == Token::Backslash && i == 0 {
                // Anonymous functions have backslashes,
                // but only in the first position
                continue;
            }
            else {
                // Found something that is not a symbol, cannot be
                // assignment or named function, stop
                break;
            }
        }

        if equals.is_none() {
            Ok(Statement::Expression(try!(self.expr(&statement_tokens[..]))))
        }
        else {
            let equals = equals.unwrap();

            // The 1 or many assumption being assumed in this
            // match is only okay because of the i > 0 above
            debug_assert!(equals > 0);

            let lhs = &statement_tokens[..equals];
            let rhs = &statement_tokens[(equals+1)..];

            if lhs[0] == Token::Backslash {
                return self.anonymous_function(lhs, rhs);
            }

            match equals {
                // only one argument: name = ...
                1 => self.assignment(lhs, rhs),
                // more than one argument: name arg1 ... = ...
                _ => self.named_function(lhs, rhs),
            }
        }
    }

    fn anonymous_function(&self, lhs: &[Token], rhs: &[Token]) -> ParseResult<Statement> {
        debug_assert!(lhs[0] == Token::Backslash);

        Ok(Statement::AnonymousFunction(try!(self.function(&lhs[0..], rhs))))
    }

    fn named_function(&self, lhs: &[Token], rhs: &[Token]) -> ParseResult<Statement> {
        let name = lhs[0].clone().unwrap_symbol();

        Ok(Statement::NamedFunction {
            name: name.clone(),
            definition: try!(self.function(&lhs[0..], rhs)),
        })
    }

    fn function(&self, params: &[Token], rhs: &[Token]) -> ParseResult<Function> {
        debug_assert!(params.len() > 0);

        Ok(Function {
            params: params.into_iter().map(|ref sym| (*sym).clone().unwrap_symbol()).collect(),
            body: try!(self.expr(rhs)),
        })
    }

    fn assignment(&self, lhs: &[Token], rhs: &[Token]) -> ParseResult<Statement> {
        debug_assert!(lhs.len() == 1);

        Ok(Statement::Assignment {
            name: lhs[0].clone().unwrap_symbol(),
            value: try!(self.expr(rhs)),
        })
    }

    fn expr(&self, tokens: &[Token]) -> ParseResult<Expr> {
        Ok(Vec::new())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use scanner::Scanner;
    use tokenizer::Tokenizer;

    #[test]
    fn complete_program() {
        let mut parser = parser_for(r#"
        defineUnit "km/h"

        my_var = 30 km/h
        f x yy = x km/h * 2 + (my_var / -3.5) * (8 ** yy) - 1.3e-3
        print (f 30)

        \q r = q / (r - 3)
        doubleMe = \e = e * 2
        operator INFIX 6 "$$" (\x y = x * y + x)
        "#);

        let syntax_tree: Program = vec![
            Statement::Expression(vec![
                ExprItem::SingleTerm(
                    Term::Symbol("defineUnit".to_owned())
                ),
                ExprItem::SingleTerm(
                    Term::StringLiteral("km/h".to_owned())
                ),
            ]),
            Statement::Assignment {
                name: "my_var".to_owned(),
                value: vec![
                    ExprItem::SingleTerm(Term::Number(30f64)),
                    ExprItem::SingleTerm(Term::Symbol("km/h".to_owned())),
                ],
            },
            Statement::NamedFunction {
                name: "f".to_owned(),
                definition: Function {
                    params: vec![
                        "x".to_owned(),
                        "yy".to_owned()
                    ],
                    body: vec![
                        ExprItem::SingleTerm(
                            Term::Symbol("x".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Symbol("km/h".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Symbol("*".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Number(2f64)
                        ),
                        ExprItem::SingleTerm(
                            Term::Symbol("+".to_owned())
                        ),
                        ExprItem::Group(vec![
                            ExprItem::SingleTerm(
                                Term::Symbol("my_var".to_owned())
                            ),
                            ExprItem::SingleTerm(
                                Term::Symbol("/".to_owned())
                            ),
                            ExprItem::SingleTerm(
                                Term::Number(-3.5f64)
                            ),
                        ]),
                        ExprItem::SingleTerm(
                            Term::Symbol("*".to_owned())
                        ),
                        ExprItem::Group(vec![
                            ExprItem::SingleTerm(
                                Term::Number(8f64)
                            ),
                            ExprItem::SingleTerm(
                                Term::Symbol("**".to_owned())
                            ),
                            ExprItem::SingleTerm(
                                Term::Symbol("yy".to_owned())
                            ),
                        ]),
                        ExprItem::SingleTerm(
                            Term::Symbol("-".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Number(1.3e-3f64)
                        ),
                    ],
                },
            },
            Statement::Expression(vec![
                ExprItem::SingleTerm(
                    Term::Symbol("print".to_owned())
                ),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(
                        Term::Symbol("f".to_owned())
                    ),
                    ExprItem::SingleTerm(
                        Term::Number(30f64)
                    ),
                ]),
            ]),
            Statement::AnonymousFunction(Function {
                params: vec!["q".to_owned(), "r".to_owned()],
                body: vec![
                    ExprItem::SingleTerm(
                        Term::Symbol("q".to_owned())
                    ),
                    ExprItem::SingleTerm(
                        Term::Symbol("/".to_owned())
                    ),
                    ExprItem::Group(vec![
                        ExprItem::SingleTerm(
                            Term::Symbol("r".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Symbol("-".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Number(3f64)
                        ),
                    ]),
                ],
            }),
            Statement::Assignment {
                name: "doubleMe".to_owned(),
                value: vec![
                    ExprItem::AnonymousFunction(Function {
                        params: vec!["e".to_owned()],
                        body: vec![
                            ExprItem::SingleTerm(
                                Term::Symbol("e".to_owned())
                            ),
                            ExprItem::SingleTerm(
                                Term::Symbol("*".to_owned())
                            ),
                            ExprItem::SingleTerm(
                                Term::Number(2f64)
                            ),
                        ],
                    }),
                ],
            },
            Statement::Expression(vec![
                ExprItem::SingleTerm(
                    Term::Symbol("operator".to_owned())
                ),
                ExprItem::SingleTerm(
                    Term::Symbol("INFIX".to_owned())
                ),
                ExprItem::SingleTerm(
                    Term::Number(6f64)
                ),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(
                        Term::StringLiteral("$$".to_owned())
                    ),
                    ExprItem::Group(vec![
                        ExprItem::AnonymousFunction(
                            Function {
                                params: vec![
                                    "x".to_owned(),
                                    "y".to_owned()
                                ],
                                body: vec![
                                    ExprItem::SingleTerm(
                                        Term::Symbol("x".to_owned())
                                    ),
                                    ExprItem::SingleTerm(
                                        Term::Symbol("*".to_owned())
                                    ),
                                    ExprItem::SingleTerm(
                                        Term::Symbol("y".to_owned())
                                    ),
                                    ExprItem::SingleTerm(
                                        Term::Symbol("+".to_owned())
                                    ),
                                    ExprItem::SingleTerm(
                                        Term::Symbol("x".to_owned())
                                    ),
                                ],
                            },
                        ),
                    ]),
                ]),
            ]),
        ];

        let parsed = parser.parse().unwrap();
        assert_eq!(parsed, syntax_tree);
    }

    fn parser_for(string: &str) -> Parser {
        Parser::new(Tokenizer::new(Scanner::from_str(string)))
    }
}

<commit_msg>Refactoring function matching and partitioning into its own method for reuse<commit_after>use token::Token;
use tokenizer::{Tokenizer, LexerError};

pub type Program = Vec<Statement>;

#[derive(PartialEq, Debug)]
pub enum Statement {
    NamedFunction {
        name: String,
        definition: Function,
    },
    AnonymousFunction(Function),
    Assignment {
        name: String,
        value: Expr,
    },
    Expression(Expr),
}

pub type Expr = Vec<ExprItem>;

#[derive(PartialEq, Debug)]
pub enum ExprItem {
    AnonymousFunction(Function),
    SingleTerm(Term),
    Group(Expr),
}

#[derive(PartialEq, Debug)]
pub struct Function {
    params: Vec<String>,
    body: Expr,
}

#[derive(PartialEq, Debug)]
pub enum Term {
    Symbol(String),
    Number(f64),
    StringLiteral(String),
}

pub struct Parser {
    lexer: Tokenizer,
}

pub type ParseResult<T> = Result<T, ParseError>;

#[derive(PartialEq, Debug)]
pub enum ParseError {
    ExpectedToken(Token),
    SyntaxError(LexerError),
}

impl Parser {
    pub fn new(lexer: Tokenizer) -> Parser {
        Parser {
            lexer: lexer,
        }
    }

    pub fn parse(&mut self) -> ParseResult<Program> {
        let mut statements: Program = Vec::new();

        loop {
            let statement = try!(self.statement());
            if statement.is_none() {
                break;
            }

            let statement = statement.unwrap();
            statements.push(statement);
        }

        Ok(statements)
    }

    fn statement(&mut self) -> ParseResult<Option<Statement>> {
        loop {
            let line = try!(self.line());
            if line.is_none() {
                return Ok(None);
            }

            let line = line.unwrap();
            if line.is_empty() {
                continue;
            }

            return Ok(Some(try!(self.dispatch_statement(line))));
        }
    }

    fn line(&mut self) -> ParseResult<Option<Vec<Token>>> {
        let mut line_tokens: Vec<Token> = Vec::new();

        let mut reached_eof = true;
        for next_token in &mut self.lexer {
            let next_token = try!(
                next_token.map_err(|e| ParseError::SyntaxError(e))
            );

            if next_token == Token::EOL {
                reached_eof = false;
                break;
            }

            line_tokens.push(next_token);
        }

        if reached_eof && line_tokens.is_empty() {
            return Ok(None);
        }

        Ok(Some(line_tokens))
    }

    fn dispatch_statement(&self, statement_tokens: Vec<Token>) -> ParseResult<Statement> {
        assert!(statement_tokens.len() > 0, "Got zero tokens to dispatch");

        let function_parts = self.partition_function(&statement_tokens);

        if function_parts.is_none() {
            Ok(Statement::Expression(try!(self.expr(&statement_tokens[..]))))
        }
        else {
            let (lhs, rhs) = function_parts.unwrap();

            if lhs[0] == Token::Backslash {
                return self.anonymous_function(lhs, rhs);
            }

            match lhs.len() {
                // only one argument: name = ...
                1 => self.assignment(lhs, rhs),
                // more than one argument: name arg1 ... = ...
                _ => self.named_function(lhs, rhs),
            }
        }
    }

    fn anonymous_function(&self, lhs: &[Token], rhs: &[Token]) -> ParseResult<Statement> {
        debug_assert!(lhs[0] == Token::Backslash);

        Ok(Statement::AnonymousFunction(try!(self.function(&lhs[0..], rhs))))
    }

    fn named_function(&self, lhs: &[Token], rhs: &[Token]) -> ParseResult<Statement> {
        let name = lhs[0].clone().unwrap_symbol();

        Ok(Statement::NamedFunction {
            name: name.clone(),
            definition: try!(self.function(&lhs[0..], rhs)),
        })
    }

    fn function(&self, params: &[Token], rhs: &[Token]) -> ParseResult<Function> {
        debug_assert!(params.len() > 0);

        Ok(Function {
            params: params.into_iter().map(|ref sym| (*sym).clone().unwrap_symbol()).collect(),
            body: try!(self.expr(rhs)),
        })
    }

    fn assignment(&self, lhs: &[Token], rhs: &[Token]) -> ParseResult<Statement> {
        debug_assert!(lhs.len() == 1);

        Ok(Statement::Assignment {
            name: lhs[0].clone().unwrap_symbol(),
            value: try!(self.expr(rhs)),
        })
    }

    fn expr(&self, tokens: &[Token]) -> ParseResult<Expr> {
        Ok(Expr::new())
        //if tokens[0] == Token::Backslash {
        //}
    }

    // partitions a function by its Equals token, returning None if no
    // valid function is found
    // Returns (lhs, rhs)
    fn partition_function<'a>(&self, tokens: &'a [Token]) -> Option<(&'a [Token], &'a [Token])> {
        // looking for either assignment or a named function
        // both are a collection of only symbols followed by equals
        let mut equals = None;
        for (i, token) in tokens.iter().enumerate() {
            if *token == Token::Equals && i > 0 {
                equals = Some(i);
                break;
            }
            else if let Token::Symbol(_) = *token {
                // Symbols are good, continue
                continue;
            }
            else if *token == Token::Backslash && i == 0 {
                // Anonymous functions have backslashes,
                // but only in the first position
                continue;
            }
            else {
                // Found something that is not a symbol, cannot be
                // assignment or named function, stop
                break;
            }
        }

        // The 1 or many assumption being assumed in this
        // is only okay because of the i > 0 above
        debug_assert!(!equals.is_none() && equals.unwrap() > 0);

        equals.map(|index| (&tokens[..index], &tokens[(index+1)..]))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use scanner::Scanner;
    use tokenizer::Tokenizer;

    #[test]
    fn complete_program() {
        let mut parser = parser_for(r#"
        defineUnit "km/h"

        my_var = 30 km/h
        f x yy = x km/h * 2 + (my_var / -3.5) * (8 ** yy) - 1.3e-3
        print (f 30)

        \q r = q / (r - 3)
        doubleMe = \e = e * 2
        operator INFIX 6 "$$" (\x y = x * y + x)
        "#);

        let syntax_tree: Program = vec![
            Statement::Expression(vec![
                ExprItem::SingleTerm(
                    Term::Symbol("defineUnit".to_owned())
                ),
                ExprItem::SingleTerm(
                    Term::StringLiteral("km/h".to_owned())
                ),
            ]),
            Statement::Assignment {
                name: "my_var".to_owned(),
                value: vec![
                    ExprItem::SingleTerm(Term::Number(30f64)),
                    ExprItem::SingleTerm(Term::Symbol("km/h".to_owned())),
                ],
            },
            Statement::NamedFunction {
                name: "f".to_owned(),
                definition: Function {
                    params: vec![
                        "x".to_owned(),
                        "yy".to_owned()
                    ],
                    body: vec![
                        ExprItem::SingleTerm(
                            Term::Symbol("x".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Symbol("km/h".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Symbol("*".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Number(2f64)
                        ),
                        ExprItem::SingleTerm(
                            Term::Symbol("+".to_owned())
                        ),
                        ExprItem::Group(vec![
                            ExprItem::SingleTerm(
                                Term::Symbol("my_var".to_owned())
                            ),
                            ExprItem::SingleTerm(
                                Term::Symbol("/".to_owned())
                            ),
                            ExprItem::SingleTerm(
                                Term::Number(-3.5f64)
                            ),
                        ]),
                        ExprItem::SingleTerm(
                            Term::Symbol("*".to_owned())
                        ),
                        ExprItem::Group(vec![
                            ExprItem::SingleTerm(
                                Term::Number(8f64)
                            ),
                            ExprItem::SingleTerm(
                                Term::Symbol("**".to_owned())
                            ),
                            ExprItem::SingleTerm(
                                Term::Symbol("yy".to_owned())
                            ),
                        ]),
                        ExprItem::SingleTerm(
                            Term::Symbol("-".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Number(1.3e-3f64)
                        ),
                    ],
                },
            },
            Statement::Expression(vec![
                ExprItem::SingleTerm(
                    Term::Symbol("print".to_owned())
                ),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(
                        Term::Symbol("f".to_owned())
                    ),
                    ExprItem::SingleTerm(
                        Term::Number(30f64)
                    ),
                ]),
            ]),
            Statement::AnonymousFunction(Function {
                params: vec!["q".to_owned(), "r".to_owned()],
                body: vec![
                    ExprItem::SingleTerm(
                        Term::Symbol("q".to_owned())
                    ),
                    ExprItem::SingleTerm(
                        Term::Symbol("/".to_owned())
                    ),
                    ExprItem::Group(vec![
                        ExprItem::SingleTerm(
                            Term::Symbol("r".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Symbol("-".to_owned())
                        ),
                        ExprItem::SingleTerm(
                            Term::Number(3f64)
                        ),
                    ]),
                ],
            }),
            Statement::Assignment {
                name: "doubleMe".to_owned(),
                value: vec![
                    ExprItem::AnonymousFunction(Function {
                        params: vec!["e".to_owned()],
                        body: vec![
                            ExprItem::SingleTerm(
                                Term::Symbol("e".to_owned())
                            ),
                            ExprItem::SingleTerm(
                                Term::Symbol("*".to_owned())
                            ),
                            ExprItem::SingleTerm(
                                Term::Number(2f64)
                            ),
                        ],
                    }),
                ],
            },
            Statement::Expression(vec![
                ExprItem::SingleTerm(
                    Term::Symbol("operator".to_owned())
                ),
                ExprItem::SingleTerm(
                    Term::Symbol("INFIX".to_owned())
                ),
                ExprItem::SingleTerm(
                    Term::Number(6f64)
                ),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(
                        Term::StringLiteral("$$".to_owned())
                    ),
                    ExprItem::Group(vec![
                        ExprItem::AnonymousFunction(
                            Function {
                                params: vec![
                                    "x".to_owned(),
                                    "y".to_owned()
                                ],
                                body: vec![
                                    ExprItem::SingleTerm(
                                        Term::Symbol("x".to_owned())
                                    ),
                                    ExprItem::SingleTerm(
                                        Term::Symbol("*".to_owned())
                                    ),
                                    ExprItem::SingleTerm(
                                        Term::Symbol("y".to_owned())
                                    ),
                                    ExprItem::SingleTerm(
                                        Term::Symbol("+".to_owned())
                                    ),
                                    ExprItem::SingleTerm(
                                        Term::Symbol("x".to_owned())
                                    ),
                                ],
                            },
                        ),
                    ]),
                ]),
            ]),
        ];

        let parsed = parser.parse().unwrap();
        assert_eq!(parsed, syntax_tree);
    }

    fn parser_for(string: &str) -> Parser {
        Parser::new(Tokenizer::new(Scanner::from_str(string)))
    }
}

<|endoftext|>


<commit_before>use std::vec::Vec;
use std::fs;
use std::fs::File;
use std::fs::DirBuilder;
use std::io::prelude::*;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fmt::{self, Debug};
use walkdir::{DirEntry, WalkDir, WalkDirIterator};
use liquid;
use liquid::{Renderable, Context, Value};
use config::Config;
use pulldown_cmark::{Parser, html, Options};
use template;
use image;
use errors::*;
use rayon::prelude::*;
use filter::{is_markdown, is_hidden, is_directory, is_image, contains_markdown_file,
             contains_markdown_subdir};

#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum MenuCmd {
    OpenLevel,
    CloseLevel,
    CloseOpenLevel,
    None,
}

impl fmt::Display for MenuCmd {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Debug::fmt(self, f)
    }
}

pub fn build(path: &Path, conf: &Config) -> Result<()> {
    let path_comps = path.components().collect::<Vec<_>>();
    let output_dir = PathBuf::from(conf.output_dir.as_ref().unwrap());
    let nav_items = collect_navigation_items(path);
    let entries = WalkDir::new(path)
        .min_depth(1)
        .into_iter()
        .filter_entry(|e| is_hidden(e))
        .filter(|e| e.is_ok() && is_markdown(e.as_ref().unwrap()))
        .collect::<Vec<_>>();
    entries.par_iter()
        .for_each(|e| {
            if e.is_ok() {
                let src = e.as_ref().unwrap();
                let mut target_dir = output_dir.clone();
                if src.path().parent().is_some() {
                    for comp in src.path().parent().unwrap().components().skip(path_comps.len()) {
                        target_dir.push(comp.as_os_str());
                    }
                }
                build_page(nav_items.clone(), &src, target_dir.as_path(), conf);
            }
        });
    copy_dirs(path, output_dir.as_path(), conf);
    Ok(())
}

fn build_page(nav_items: Vec<Value>, entry: &DirEntry, target_dir: &Path, conf: &Config) {
    let page_content = convert_markdown_to_html(&entry.path());
    let html = if entry.file_name() == "gallery.md" {
        let images = prepare_gallery(&entry, target_dir, conf);
        apply_gallery_template(page_content, nav_items.clone(), images, entry.depth(), conf)
    } else {
        apply_page_template(page_content, nav_items.clone(), entry.depth(), conf)
    };
    write_html_file(html, target_dir, &entry);
    copy_images(entry.path().parent().unwrap(), target_dir);
}

fn copy_dirs(path: &Path, target_path: &Path, conf: &Config) {
    if conf.copy_dirs.is_some() {
        let path_comps = path.components().collect::<Vec<_>>();
        for copy_dir in conf.copy_dirs.as_ref().unwrap() {
            let walker = WalkDir::new(path.join(copy_dir).as_path()).min_depth(1).into_iter();
            for entry in walker.filter(|e| e.is_ok() && !is_directory(e.as_ref().unwrap())) {
                let entry = entry.unwrap();
                let mut target_file = PathBuf::from(target_path);
                for comp in entry.path().components().skip(path_comps.len()) {
                    target_file.push(comp.as_os_str());
                }
                match DirBuilder::new()
                    .recursive(true)
                    .create(target_file.parent().expect("Missing parent folder!")) {
                    Ok(_) => {}
                    Err(e) => println!("{}", e),
                }
                if !target_file.exists() {
                    fs::copy(entry.path(), target_file)
                        .expect(format!("error during copy of {:?}", copy_dir).as_ref());
                }
            }
        }
    }
}

fn copy_images(source: &Path, target: &Path) {
    let walker = WalkDir::new(source)
        .min_depth(1)
        .max_depth(1)
        .follow_links(true)
        .into_iter();
    for entry in walker.filter(|e| e.is_ok() && is_image(e.as_ref().unwrap())) {
        let mut target_file = PathBuf::from(target);
        let entry = entry.unwrap();
        target_file.push(entry.path().file_name().unwrap());
        if !target_file.exists() {
            fs::copy(entry.path(), target_file.as_path())
                .expect(format!("Error during copy of {:?}", entry.path().display()).as_ref());
        }
    }
}

fn collect_navigation_items(path: &Path) -> Vec<Value> {
    let mut nav_entries = Vec::<Value>::new();
    let walker = WalkDir::new(path).min_depth(1).sort_by(|a, b| a.cmp(b)).into_iter();
    let mut prev_depth = 1;
    let path_comps = path.components().collect::<Vec<_>>();
    for entry in
        walker.filter_entry(|e| is_hidden(e) && is_directory(e) && contains_markdown_file(e)) {
        let entry = entry.expect("Reading directory entry failed");
        let name = String::from(entry.file_name()
            .to_str()
            .expect("Failed to read navigation entries"));
        let mut url = PathBuf::new();
        for comp in entry.path().components().skip(path_comps.len()) {
            url.push(comp.as_os_str());
        }
        url.push("index.html");
        let mut nav_entry = HashMap::new();
        nav_entry.insert("name".to_owned(), Value::Str(name));
        nav_entry.insert("url".to_owned(),
                         Value::Str(url.as_os_str().to_str().unwrap().to_owned()));
        let (menu_cmd, level_depth) = match (contains_markdown_subdir(&entry),
                                             prev_depth > entry.depth()) {
            (true, true) => (MenuCmd::CloseOpenLevel, prev_depth - entry.depth()),
            (true, false) => (MenuCmd::OpenLevel, 0),
            (false, true) => (MenuCmd::CloseLevel, prev_depth - entry.depth()),
            _ => (MenuCmd::None, 0),
        };
        nav_entry.insert("menu_cmd".to_owned(),
                         Value::Str(menu_cmd.to_string().to_owned()));
        nav_entry.insert("level_depth".to_owned(), Value::Num(level_depth as f32));
        nav_entries.push(Value::Object(nav_entry));
        prev_depth = entry.depth();
    }
    nav_entries
}

fn prepare_gallery(source_entry: &DirEntry, target_path: &Path, conf: &Config) -> Vec<Value> {
    let gallery_settings = conf.gallery.as_ref().unwrap();
    let mut images = Vec::<Value>::new();
    let img_dir = gallery_settings.img_dir.as_ref().unwrap();
    let target_dir = target_path.join(img_dir.as_str());
    match DirBuilder::new()
        .recursive(true)
        .create(&target_dir) {
        Ok(_) => {}
        Err(e) => println!("{}", e),
    }
    let walker = WalkDir::new(source_entry.path()
            .parent()
            .unwrap()
            .join(img_dir.as_str())
            .as_path())
        .min_depth(1)
        .follow_links(true)
        .into_iter();
    for entry in walker.filter(|e| e.is_ok() && !is_directory(e.as_ref().unwrap())) {
        let entry = entry.unwrap();
        let mut img = image::open(entry.path())
            .expect(format!("Resize of '{}' failed: The gallery folder should only contain \
                             images!",
                            entry.path().display())
                .as_ref());

        let mut image_path = PathBuf::from(&target_dir);
        let mut rel_image_path = PathBuf::from(img_dir.as_str());
        image_path.push(entry.file_name());
        image_path.set_extension("png");
        rel_image_path.push(entry.file_name());
        rel_image_path.set_extension("png");
        if !image_path.exists() {
            let ref mut fout = File::create(&image_path).unwrap();
            img = img.resize(gallery_settings.img_width,
                             gallery_settings.img_height,
                             image::FilterType::Nearest);
            img.save(fout, image::PNG)
                .expect(format!("Saving thumb image '{}' failed", image_path.display()).as_ref());
        }

        let mut thumb_path = PathBuf::from(&target_dir);
        let mut rel_thumb_path = PathBuf::from(img_dir.as_str());
        let mut thumb_file_name =
            String::from(entry.path().file_stem().map(|s| s.to_str().unwrap()).unwrap());
        thumb_file_name.push_str("_thumb.png");
        thumb_path.push(thumb_file_name.clone());
        rel_thumb_path.push(thumb_file_name);
        if !thumb_path.exists() {
            let ref mut fout = File::create(&thumb_path).unwrap();
            img = img.resize(gallery_settings.thumb_width,
                             gallery_settings.thumb_height,
                             image::FilterType::Nearest);
            img.save(fout, image::PNG)
                .expect(format!("Saving thumb image '{}' failed", thumb_path.display()).as_ref());
        }

        let mut image_entry = HashMap::new();
        image_entry.insert("name".to_owned(),
                           Value::Str(rel_image_path.to_str().unwrap().to_owned()));
        image_entry.insert("thumb".to_owned(),
                           Value::Str(rel_thumb_path.to_str().unwrap().to_owned()));
        images.push(Value::Object(image_entry));
    }
    images
}

fn apply_gallery_template(content: String,
                          nav_items: Vec<Value>,
                          images: Vec<Value>,
                          depth: usize,
                          conf: &Config)
                          -> String {
    let template = liquid::parse(template::load_gallery_template(conf).as_str(),
                                 Default::default())
        .expect("Gallery template could not be parsed!");
    let mut root_dir = String::new();
    for _ in 1..depth {
        root_dir.push_str("../");
    }
    let mut context = Context::new();
    context.set_val("root_dir", Value::Str(root_dir));
    context.set_val("title",
                    Value::Str(if conf.title.is_some() {
                        conf.title.as_ref().unwrap().clone()
                    } else {
                        "None".to_string()
                    }));
    context.set_val("nav_items", Value::Array(nav_items));
    context.set_val("content", Value::Str(content.to_owned()));
    context.set_val("images", Value::Array(images));
    match template.render(&mut context) {
        Ok(output) => {
            if output.is_some() {
                output.unwrap()
            } else {
                content
            }
        }
        Err(error) => panic!("Could not render Page template: {}", error),
    }
}

fn apply_page_template(content: String,
                       nav_items: Vec<Value>,
                       depth: usize,
                       conf: &Config)
                       -> String {
    let template = liquid::parse(template::load_page_template(conf).as_str(),
                                 Default::default())
        .expect("Page template could not be parsed!");
    let mut root_dir = String::new();
    for _ in 1..depth {
        root_dir.push_str("../");
    }
    let mut context = Context::new();
    context.set_val("root_dir", Value::Str(root_dir));
    context.set_val("title",
                    Value::Str(if conf.title.is_some() {
                        conf.title.as_ref().unwrap().clone()
                    } else {
                        "None".to_string()
                    }));
    context.set_val("nav_items", Value::Array(nav_items));
    context.set_val("content", Value::Str(content.to_owned()));
    match template.render(&mut context) {
        Ok(output) => {
            if output.is_some() {
                output.unwrap()
            } else {
                content
            }
        }
        Err(error) => panic!("Could not render Page template: {}", error),
    }
}

fn convert_markdown_to_html(entry: &Path) -> String {
    let mut markdown = String::new();
    let mut html_output = String::new();
    match File::open(entry).and_then(|mut f| f.read_to_string(&mut markdown)) {
        Err(error) => panic!("failed to open {}: {}", entry.display(), error),
        Ok(_) => {
            html::push_html(&mut html_output,
                            Parser::new_ext(markdown.as_str(), Options::empty()))
        }
    }
    html_output
}

fn write_html_file(html: String, target_dir: &Path, entry: &DirEntry) {
    match DirBuilder::new()
        .recursive(true)
        .create(target_dir) {
        Ok(_) => {}
        Err(e) => println!("{}", e),
    }
    let html_file = match entry.file_name().to_str() {
        Some(fname) => {
            let mut tmp_path = PathBuf::from(fname);
            tmp_path.set_file_name("index.html");
            tmp_path
        }
        None => panic!("Could not write html file {}", entry.path().display()),
    };
    let file_path = target_dir.join(html_file.as_path());
    let result = File::create(file_path.as_path()).and_then(|mut f| f.write_all(html.as_bytes()));
    if result.is_err() {
        panic!("Could not write html file {}: {:?}",
               file_path.as_path().display(),
               result.err());
    }
    println!("written file {}", file_path.display());
}<commit_msg>create site structure up front<commit_after>use std::vec::Vec;
use std::fs;
use std::fs::File;
use std::fs::DirBuilder;
use std::io::prelude::*;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fmt::{self, Debug};
use walkdir::{DirEntry, WalkDir, WalkDirIterator};
use liquid;
use liquid::{Renderable, Context, Value};
use config::Config;
use pulldown_cmark::{Parser, html, Options};
use template;
use image;
use errors::*;
use rayon::prelude::*;
use filter::{is_markdown, is_hidden, is_directory, is_image, contains_markdown_file,
             contains_markdown_subdir};

#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum MenuCmd {
    OpenLevel,
    CloseLevel,
    CloseOpenLevel,
    None,
}

impl fmt::Display for MenuCmd {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Debug::fmt(self, f)
    }
}

pub fn build(path: &Path, conf: &Config) -> Result<()> {
    let path_comps = path.components().collect::<Vec<_>>();
    let output_dir = PathBuf::from(conf.output_dir.as_ref().unwrap());
    let nav_items = prepare_site_structure(path, output_dir.as_path());
    let entries = WalkDir::new(path)
        .min_depth(1)
        .into_iter()
        .filter_entry(|e| is_hidden(e))
        .filter(|e| e.is_ok() && is_markdown(e.as_ref().unwrap()))
        .collect::<Vec<_>>();
    entries.par_iter()
        .for_each(|e| {
            let src = e.as_ref().unwrap();
            let mut target_dir = output_dir.clone();
            if src.path().parent().is_some() {
                for comp in src.path().parent().unwrap().components().skip(path_comps.len()) {
                    target_dir.push(comp.as_os_str());
                }
            }
            build_page(nav_items.clone(), &src, target_dir.as_path(), conf);
        });
    copy_dirs(path, output_dir.as_path(), conf);
    Ok(())
}

fn build_page(nav_items: Vec<Value>, entry: &DirEntry, target_dir: &Path, conf: &Config) {
    let page_content = convert_markdown_to_html(&entry.path());
    let html = if entry.file_name() == "gallery.md" {
        let images = prepare_gallery(&entry, target_dir, conf);
        apply_gallery_template(page_content, nav_items.clone(), images, entry.depth(), conf)
    } else {
        apply_page_template(page_content, nav_items.clone(), entry.depth(), conf)
    };
    write_html_file(html, target_dir, &entry);
    copy_images(entry.path().parent().unwrap(), target_dir);
}

fn copy_dirs(path: &Path, target_path: &Path, conf: &Config) {
    if conf.copy_dirs.is_some() {
        let path_comps = path.components().collect::<Vec<_>>();
        for copy_dir in conf.copy_dirs.as_ref().unwrap() {
            let walker = WalkDir::new(path.join(copy_dir).as_path()).min_depth(1).into_iter();
            for entry in walker.filter(|e| e.is_ok() && !is_directory(e.as_ref().unwrap())) {
                let entry = entry.unwrap();
                let mut target_file = PathBuf::from(target_path);
                for comp in entry.path().components().skip(path_comps.len()) {
                    target_file.push(comp.as_os_str());
                }
                match DirBuilder::new()
                    .recursive(true)
                    .create(target_file.parent().expect("Missing parent folder!")) {
                    Ok(_) => {}
                    Err(e) => println!("{}", e),
                }
                if !target_file.exists() {
                    fs::copy(entry.path(), target_file)
                        .expect(format!("error during copy of {:?}", copy_dir).as_ref());
                }
            }
        }
    }
}

fn copy_images(source: &Path, target: &Path) {
    let walker = WalkDir::new(source)
        .min_depth(1)
        .max_depth(1)
        .follow_links(true)
        .into_iter();
    for entry in walker.filter(|e| e.is_ok() && is_image(e.as_ref().unwrap())) {
        let mut target_file = PathBuf::from(target);
        let entry = entry.unwrap();
        target_file.push(entry.path().file_name().unwrap());
        if !target_file.exists() {
            fs::copy(entry.path(), target_file.as_path())
                .expect(format!("Error during copy of {:?}", entry.path().display()).as_ref());
        }
    }
}

fn prepare_site_structure(path: &Path, target_path: &Path) -> Vec<Value> {
    let mut nav_entries = Vec::<Value>::new();
    let walker = WalkDir::new(path).min_depth(1).sort_by(|a, b| a.cmp(b)).into_iter();
    let mut prev_depth = 1;
    let path_comps = path.components().collect::<Vec<_>>();
    for entry in
        walker.filter_entry(|e| is_hidden(e) && is_directory(e) && contains_markdown_file(e)) {
        let entry = entry.expect("Reading directory entry failed");
        let name = String::from(entry.file_name()
            .to_str()
            .expect("Failed to read navigation entries"));
        let mut url = PathBuf::new();
        for comp in entry.path().components().skip(path_comps.len()) {
            url.push(comp.as_os_str());
        }
        let target_dir = target_path.join(url.as_path());
        match DirBuilder::new()
            .recursive(true)
            .create(target_dir) {
            Ok(_) => {}
            Err(e) => println!("{}", e),
        }
        url.push("index.html");
        let mut nav_entry = HashMap::new();
        nav_entry.insert("name".to_owned(), Value::Str(name));
        nav_entry.insert("url".to_owned(),
                         Value::Str(url.as_os_str().to_str().unwrap().to_owned()));
        let (menu_cmd, level_depth) = match (contains_markdown_subdir(&entry),
                                             prev_depth > entry.depth()) {
            (true, true) => (MenuCmd::CloseOpenLevel, prev_depth - entry.depth()),
            (true, false) => (MenuCmd::OpenLevel, 0),
            (false, true) => (MenuCmd::CloseLevel, prev_depth - entry.depth()),
            _ => (MenuCmd::None, 0),
        };
        nav_entry.insert("menu_cmd".to_owned(),
                         Value::Str(menu_cmd.to_string().to_owned()));
        nav_entry.insert("level_depth".to_owned(), Value::Num(level_depth as f32));
        nav_entries.push(Value::Object(nav_entry));
        prev_depth = entry.depth();
    }
    nav_entries
}

fn prepare_gallery(source_entry: &DirEntry, target_path: &Path, conf: &Config) -> Vec<Value> {
    let gallery_settings = conf.gallery.as_ref().unwrap();
    let mut images = Vec::<Value>::new();
    let img_dir = gallery_settings.img_dir.as_ref().unwrap();
    let target_dir = target_path.join(img_dir.as_str());
    match DirBuilder::new()
        .recursive(true)
        .create(&target_dir) {
        Ok(_) => {}
        Err(e) => println!("{}", e),
    }
    let entries = WalkDir::new(source_entry.path()
            .parent()
            .unwrap()
            .join(img_dir.as_str())
            .as_path())
        .min_depth(1)
        .follow_links(true)
        .into_iter()
        .filter(|e| e.is_ok() && !is_directory(e.as_ref().unwrap()))
        .collect::<Vec<_>>();
    for entry in entries {
        let entry = entry.unwrap();
        let mut img = image::open(entry.path())
            .expect(format!("Resize of '{}' failed: The gallery folder should only contain \
                             images!",
                            entry.path().display())
                .as_ref());

        let mut image_path = PathBuf::from(&target_dir);
        let mut rel_image_path = PathBuf::from(img_dir.as_str());
        image_path.push(entry.file_name());
        image_path.set_extension("png");
        rel_image_path.push(entry.file_name());
        rel_image_path.set_extension("png");
        if !image_path.exists() {
            let ref mut fout = File::create(&image_path).unwrap();
            img = img.resize(gallery_settings.img_width,
                             gallery_settings.img_height,
                             image::FilterType::Nearest);
            img.save(fout, image::PNG)
                .expect(format!("Saving image '{}' failed", image_path.display()).as_ref());
        }

        let mut thumb_path = PathBuf::from(&target_dir);
        let mut rel_thumb_path = PathBuf::from(img_dir.as_str());
        let mut thumb_file_name =
            String::from(entry.path().file_stem().map(|s| s.to_str().unwrap()).unwrap());
        thumb_file_name.push_str("_thumb.png");
        thumb_path.push(thumb_file_name.clone());
        rel_thumb_path.push(thumb_file_name);
        if !thumb_path.exists() {
            let ref mut fout = File::create(&thumb_path).unwrap();
            img = img.resize(gallery_settings.thumb_width,
                             gallery_settings.thumb_height,
                             image::FilterType::Nearest);
            img.save(fout, image::PNG)
                .expect(format!("Saving thumb image '{}' failed", thumb_path.display()).as_ref());
        }

        let mut image_entry = HashMap::new();
        image_entry.insert("name".to_owned(),
                           Value::Str(rel_image_path.to_str().unwrap().to_owned()));
        image_entry.insert("thumb".to_owned(),
                           Value::Str(rel_thumb_path.to_str().unwrap().to_owned()));
        images.push(Value::Object(image_entry));
    }
    images
}

fn apply_gallery_template(content: String,
                          nav_items: Vec<Value>,
                          images: Vec<Value>,
                          depth: usize,
                          conf: &Config)
                          -> String {
    let template = liquid::parse(template::load_gallery_template(conf).as_str(),
                                 Default::default())
        .expect("Gallery template could not be parsed!");
    let mut root_dir = String::new();
    for _ in 1..depth {
        root_dir.push_str("../");
    }
    let mut context = Context::new();
    context.set_val("root_dir", Value::Str(root_dir));
    context.set_val("title",
                    Value::Str(if conf.title.is_some() {
                        conf.title.as_ref().unwrap().clone()
                    } else {
                        "None".to_string()
                    }));
    context.set_val("nav_items", Value::Array(nav_items));
    context.set_val("content", Value::Str(content.to_owned()));
    context.set_val("images", Value::Array(images));
    match template.render(&mut context) {
        Ok(output) => {
            if output.is_some() {
                output.unwrap()
            } else {
                content
            }
        }
        Err(error) => panic!("Could not render Page template: {}", error),
    }
}

fn apply_page_template(content: String,
                       nav_items: Vec<Value>,
                       depth: usize,
                       conf: &Config)
                       -> String {
    let template = liquid::parse(template::load_page_template(conf).as_str(),
                                 Default::default())
        .expect("Page template could not be parsed!");
    let mut root_dir = String::new();
    for _ in 1..depth {
        root_dir.push_str("../");
    }
    let mut context = Context::new();
    context.set_val("root_dir", Value::Str(root_dir));
    context.set_val("title",
                    Value::Str(if conf.title.is_some() {
                        conf.title.as_ref().unwrap().clone()
                    } else {
                        "None".to_string()
                    }));
    context.set_val("nav_items", Value::Array(nav_items));
    context.set_val("content", Value::Str(content.to_owned()));
    match template.render(&mut context) {
        Ok(output) => {
            if output.is_some() {
                output.unwrap()
            } else {
                content
            }
        }
        Err(error) => panic!("Could not render Page template: {}", error),
    }
}

fn convert_markdown_to_html(entry: &Path) -> String {
    let mut markdown = String::new();
    let mut html_output = String::new();
    match File::open(entry).and_then(|mut f| f.read_to_string(&mut markdown)) {
        Err(error) => panic!("failed to open {}: {}", entry.display(), error),
        Ok(_) => {
            html::push_html(&mut html_output,
                            Parser::new_ext(markdown.as_str(), Options::empty()))
        }
    }
    html_output
}

fn write_html_file(html: String, target_dir: &Path, entry: &DirEntry) {
    let html_file = match entry.file_name().to_str() {
        Some(fname) => {
            let mut tmp_path = PathBuf::from(fname);
            tmp_path.set_file_name("index.html");
            tmp_path
        }
        None => panic!("Could not write html file {}", entry.path().display()),
    };
    let file_path = target_dir.join(html_file.as_path());
    let result = File::create(file_path.as_path()).and_then(|mut f| f.write_all(html.as_bytes()));
    if result.is_err() {
        panic!("Could not write html file {}: {:?}",
               file_path.as_path().display(),
               result.err());
    }
    println!("written file {}", file_path.display());
}<|endoftext|>


<commit_before>#![feature(phase)]
#[phase(syntax, link)] extern crate log;
extern crate collections;
extern crate sync;
extern crate rand;
extern crate serialize;
use std::cmp::{min, max};
use std::io::{IoResult, IoError, InvalidInput, SeekStyle, MemWriter, BufWriter, BufReader};
use std::io::{SeekSet, SeekEnd, SeekCur};
use serialize::ebml::{reader,writer};
use serialize::{Encodable, Decodable};
mod buffer;

#[deriving(Encodable, Decodable)]
enum SqlType {
	Char(uint),
	Varchar(uint),
	Integer,
}

#[deriving(Encodable, Decodable)]
enum SqlAttribute {
	Null,
	NotNull,
}

#[deriving(Encodable, Decodable)]
struct Column {
	name: ~str,
	datatype: SqlType,
	attributes: Vec<SqlAttribute>,
}

#[deriving(Encodable, Decodable)]
struct Relation {
	name: ~str,
	columns: Vec<Column>,
}

impl Relation {
	pub fn new(name: ~str) -> Relation {
		Relation {name: name, columns: Vec::new()}
	}

	pub fn add_column(&mut self, column: Column) {
		self.columns.push(column);
	}
}

#[deriving(Encodable, Decodable)]
struct Schema {
	relations: Vec<Relation>,
}

struct SchemaWriter<'a> {
	buffer_manager: &'a mut buffer::BufferManager,
	location: u64,
	maximum: u64,
}

impl<'a> Writer for SchemaWriter<'a> {
	fn write(&mut self, buf: &[u8]) -> IoResult<()> {
		let pageno = self.location / buffer::PAGE_SIZE as u64 + 1;
		let start_from = (self.location % buffer::PAGE_SIZE as u64) as uint;

		let pagelock = self.buffer_manager.fix_page(pageno).unwrap_or_else(|| fail!("Fix page failed"));
		let mut copied = 0;
		{
			let mut page = pagelock.write();
			let content = page.get_mut_data();

			for i in range(0, min(buf.len(), buffer::PAGE_SIZE)) {
				content[start_from+i] = buf[i];
				copied += 1;
				self.location += 1;
			}
		}
		self.buffer_manager.unfix_page(pagelock, true);
		if self.location > self.maximum {
			self.maximum = self.location;
			let pagelock = self.buffer_manager.fix_page(0).unwrap_or_else(
				|| fail!("Fixing zero page failed"));
			{
				let mut page = pagelock.write();
				let content = page.get_mut_data();
				let mut writer = BufWriter::new(content);
				writer.write_le_u64(self.maximum);
			}
			self.buffer_manager.unfix_page(pagelock, true);
		}
		//TODO remaining bytes from buf
		println!("copied {}/{}, location: {}", copied, buf.len(), self.location);
		//assert!(copied, buf.len());
		if copied == buf.len() {
			Ok(())
		} else {
			Err(IoError {
				kind: InvalidInput,
				desc: "Did not copy all data",
				detail: None,
			})
		}
	}
}

impl<'a> Seek for SchemaWriter<'a> {
	fn tell(&self) -> IoResult<u64> {
		Ok(self.location)
	}

	fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<()> {
		println!("Seeking {}", pos);
		match style {
			SeekSet => {
				self.location = pos as u64;
				Ok(())
			}
			SeekEnd => {
				Err(IoError {
					kind: InvalidInput,
					desc: "seek to end not supported",
					detail: None,
				})
			}
			SeekCur => {
				self.location = (self.location as i64 + pos) as u64;
				Ok(())
			}
		}
	}
}

impl<'a> SchemaWriter<'a> {
	pub fn new<'b>(bufman: &'b mut buffer::BufferManager) -> SchemaWriter<'b> {
		SchemaWriter {buffer_manager: bufman, location: 0,
			maximum: 0}
	}

	pub fn get_data(&mut self) -> Vec<u8> {
		let pagelock = self.buffer_manager.fix_page(0).unwrap_or_else(
			|| fail!("Failed fixing 0 page for schema length"));
		let mut size = 0_u64;
		{
			let page = pagelock.read();
			let mut reader = BufReader::new(page.get_data());
			size = reader.read_le_u64().unwrap();
		}
		self.buffer_manager.unfix_page(pagelock, false);
		println!("Size: {}", size);

		let mut data: Vec<u8> = Vec::with_capacity(size as uint);
		let mut read = 0;

		println!("location: {}", self.location);
		for i in range(1, self.location / buffer::PAGE_SIZE as u64 + 2) {
			println!("Reading page {}", i);
			let pagelock = self.buffer_manager.fix_page(i).unwrap_or_else(
				|| fail!("Failed fixing page {}", i));
			{
				let page = pagelock.read();
				let content = page.get_data();
				let mut j = 0;
				while read < size {
					data.push(content[j]);
					j += 1;
					read += 1;
				}
			}
			self.buffer_manager.unfix_page(pagelock, false);
		}
		data
	}
}

impl Schema {
	pub fn new() -> Schema {
		Schema {relations: Vec::new()}
	}

	pub fn new_from_disk(bufmanager: &mut buffer::BufferManager) -> Schema {
		let mut wr = SchemaWriter::new(bufmanager);
		let data = wr.get_data();
		let ebml_doc = reader::Doc(data.as_slice());
		let mut deser = reader::Decoder(ebml_doc);
		let value: Schema = match Decodable::decode(&mut deser) {
			Ok(v) => v,
			Err(e) => fail!("Error decoding: {}", e),
		};
		value
	}

	pub fn add_relation(&mut self, relation: Relation) {
		self.relations.push(relation);
	}

	pub fn save_to_disk(&self, bufmanager: &mut buffer::BufferManager) {
		let mut wr1 = MemWriter::new();
		let mut wr2 = SchemaWriter::new(bufmanager);
		{
			let mut ebml_w1 = writer::Encoder(&mut wr1);
			let _ = self.encode(&mut ebml_w1);
			let mut ebml_w2 = writer::Encoder(&mut wr2);
			let _ = self.encode(&mut ebml_w2);
		}

		let dta = wr2.get_data();
		println!("wr1 len: {}", wr1.get_ref().len());
		println!("wr2 len: {}", dta.len());
		let ebml_doc1 = reader::Doc(wr1.get_ref());
		let ebml_doc2 = reader::Doc(dta.as_slice());
		let mut deser1 = reader::Decoder(ebml_doc1);
		let mut deser2 = reader::Decoder(ebml_doc2);
		let v1: Schema = match Decodable::decode(&mut deser1) {
			Ok(v) => v,
			Err(e) => fail!("Error decoding: {}", e),
		};
		println!("v1 == {:?}", v1);
		let v2: Schema = match Decodable::decode(&mut deser2) {
			Ok(v) => v,
			Err(e) => fail!("Error decoding: {}", e),
		};
		println!("v2 == {:?}", v2);
	}
}

struct Record {
	// TODO: do we even need this?
	len: uint,
	data: Vec<u8>,
}

impl Record {
	/* move semantics, no copy */
	pub fn new(len: uint, data: Vec<u8>) -> Record {
		Record {len: len, data: data}
	}

	pub fn get_data<'a>(&'a self) -> &'a [u8] {
		self.data.as_slice()
	}
}

struct SPSegment;

/* our newtype struct: create a TID type as an alias to another type */
struct TID(u8);

impl SPSegment {
	pub fn insert(r: Record) -> TID {
		TID(0)
	}

	pub fn remove(tid: TID) -> bool {
		false
	}

	pub fn lookup(tid: TID) -> Record {
		// TODO
		Record {len: 1, data: vec!(1)}
	}

	pub fn update(tid: TID, r: Record) -> bool {
		false
	}
}

#[test]
fn main() {
}

#[test]
fn create_schema() {
	let name = Column {name: ~"name", datatype: Varchar(128), attributes: vec!(NotNull)};
	let age = Column {name: ~"age", datatype: Integer, attributes: vec!(NotNull)};
	let mut relation = Relation::new(~"Person");
	relation.add_column(name);
	relation.add_column(age);
	let mut schema = Schema::new();
	schema.add_relation(relation);

	let mut manager = buffer::BufferManager::new(1024, Path::new("."));
	schema.save_to_disk(&mut manager);
	let new_schema = Schema::new_from_disk(&mut manager);
	println!("new_schema == {:?}", new_schema);
	assert!(false);
}
<commit_msg>Cleaned up code for writing to not do stupid stuff<commit_after>#![feature(phase)]
#[phase(syntax, link)] extern crate log;
extern crate collections;
extern crate sync;
extern crate rand;
extern crate serialize;
use std::cmp::min;
use std::io::{IoResult, IoError, InvalidInput, SeekStyle, BufWriter, BufReader};
use std::io::{SeekSet, SeekEnd, SeekCur};
use serialize::ebml::{reader,writer};
use serialize::{Encodable, Decodable};
mod buffer;

#[deriving(Encodable, Decodable)]
enum SqlType {
	Char(uint),
	Varchar(uint),
	Integer,
}

#[deriving(Encodable, Decodable)]
enum SqlAttribute {
	Null,
	NotNull,
}

#[deriving(Encodable, Decodable)]
struct Column {
	name: ~str,
	datatype: SqlType,
	attributes: Vec<SqlAttribute>,
}

#[deriving(Encodable, Decodable)]
struct Relation {
	name: ~str,
	columns: Vec<Column>,
}

impl Relation {
	pub fn new(name: ~str) -> Relation {
		Relation {name: name, columns: Vec::new()}
	}

	pub fn add_column(&mut self, column: Column) {
		self.columns.push(column);
	}
}

#[deriving(Encodable, Decodable)]
struct Schema {
	relations: Vec<Relation>,
}

struct SchemaWriter<'a> {
	buffer_manager: &'a mut buffer::BufferManager,
	location: u64,
	maximum: u64,
}

impl<'a> Writer for SchemaWriter<'a> {
	fn write(&mut self, buf: &[u8]) -> IoResult<()> {
		let pageno = self.location / buffer::PAGE_SIZE as u64 + 1;
		let start_from = (self.location % buffer::PAGE_SIZE as u64) as uint;

		let pagelock = self.buffer_manager.fix_page(pageno).unwrap_or_else(|| fail!("Fix page failed"));
		let mut copied = 0;
		{
			let mut page = pagelock.write();
			let content = page.get_mut_data();

			for i in range(0, min(buf.len(), buffer::PAGE_SIZE)) {
				content[start_from+i] = buf[i];
				copied += 1;
				self.location += 1;
			}
		}
		self.buffer_manager.unfix_page(pagelock, true);
		if self.location > self.maximum {
			self.maximum = self.location;
			let pagelock = self.buffer_manager.fix_page(0).unwrap_or_else(
				|| fail!("Fixing zero page failed"));
			{
				let mut page = pagelock.write();
				let content = page.get_mut_data();
				let mut writer = BufWriter::new(content);
				match writer.write_le_u64(self.maximum) {
					Ok(_) => (),
					Err(e) => fail!("Failed writing lenght to page: {}", e)
				};
			}
			self.buffer_manager.unfix_page(pagelock, true);
		}
		//TODO remaining bytes from buf
		println!("copied {}/{}, location: {}", copied, buf.len(), self.location);
		//assert!(copied, buf.len());
		if copied == buf.len() {
			Ok(())
		} else {
			Err(IoError {
				kind: InvalidInput,
				desc: "Did not copy all data",
				detail: None,
			})
		}
	}
}

impl<'a> Seek for SchemaWriter<'a> {
	fn tell(&self) -> IoResult<u64> {
		Ok(self.location)
	}

	fn seek(&mut self, pos: i64, style: SeekStyle) -> IoResult<()> {
		println!("Seeking {}", pos);
		match style {
			SeekSet => {
				self.location = pos as u64;
				Ok(())
			}
			SeekEnd => {
				Err(IoError {
					kind: InvalidInput,
					desc: "seek to end not supported",
					detail: None,
				})
			}
			SeekCur => {
				self.location = (self.location as i64 + pos) as u64;
				Ok(())
			}
		}
	}
}

impl<'a> SchemaWriter<'a> {
	pub fn new<'b>(bufman: &'b mut buffer::BufferManager) -> SchemaWriter<'b> {
		SchemaWriter {buffer_manager: bufman, location: 0,
			maximum: 0}
	}

	pub fn get_data(&mut self) -> Vec<u8> {
		let pagelock = self.buffer_manager.fix_page(0).unwrap_or_else(
			|| fail!("Failed fixing 0 page for schema length"));
		let mut size;
		{
			let page = pagelock.read();
			let mut reader = BufReader::new(page.get_data());
			size = reader.read_le_u64().unwrap();
		}
		self.buffer_manager.unfix_page(pagelock, false);
		println!("Size: {}", size);

		let mut data: Vec<u8> = Vec::with_capacity(size as uint);
		let mut read = 0;

		println!("location: {}", self.location);
		for i in range(1, self.location / buffer::PAGE_SIZE as u64 + 2) {
			println!("Reading page {}", i);
			let pagelock = self.buffer_manager.fix_page(i).unwrap_or_else(
				|| fail!("Failed fixing page {}", i));
			{
				let page = pagelock.read();
				let content = page.get_data();
				let mut j = 0;
				while read < size {
					data.push(content[j]);
					j += 1;
					read += 1;
				}
			}
			self.buffer_manager.unfix_page(pagelock, false);
		}
		data
	}
}

impl Schema {
	pub fn new() -> Schema {
		Schema {relations: Vec::new()}
	}

	pub fn new_from_disk(bufmanager: &mut buffer::BufferManager) -> Schema {
		let mut wr = SchemaWriter::new(bufmanager);
		let data = wr.get_data();
		let ebml_doc = reader::Doc(data.as_slice());
		let mut deser = reader::Decoder(ebml_doc);
		let value: Schema = match Decodable::decode(&mut deser) {
			Ok(v) => v,
			Err(e) => fail!("Error decoding: {}", e),
		};
		value
	}

	pub fn add_relation(&mut self, relation: Relation) {
		self.relations.push(relation);
	}

	pub fn save_to_disk(&self, bufmanager: &mut buffer::BufferManager) {
		let mut wr = SchemaWriter::new(bufmanager);
		{
			let mut ebml_w = writer::Encoder(&mut wr);
			let _ = self.encode(&mut ebml_w);
		}
	}
}

struct Record {
	// TODO: do we even need this?
	len: uint,
	data: Vec<u8>,
}

impl Record {
	/* move semantics, no copy */
	pub fn new(len: uint, data: Vec<u8>) -> Record {
		Record {len: len, data: data}
	}

	pub fn get_data<'a>(&'a self) -> &'a [u8] {
		self.data.as_slice()
	}
}

struct SPSegment;

/* our newtype struct: create a TID type as an alias to another type */
struct TID(u8);

impl SPSegment {
	pub fn insert(r: Record) -> TID {
		TID(0)
	}

	pub fn remove(tid: TID) -> bool {
		false
	}

	pub fn lookup(tid: TID) -> Record {
		// TODO
		Record {len: 1, data: vec!(1)}
	}

	pub fn update(tid: TID, r: Record) -> bool {
		false
	}
}

#[test]
fn main() {
}

#[test]
fn create_schema() {
	let name = Column {name: ~"name", datatype: Varchar(128), attributes: vec!(NotNull)};
	let age = Column {name: ~"age", datatype: Integer, attributes: vec!(NotNull)};
	let mut relation = Relation::new(~"Person");
	relation.add_column(name);
	relation.add_column(age);
	let mut schema = Schema::new();
	schema.add_relation(relation);

	let mut manager = buffer::BufferManager::new(1024, Path::new("."));
	schema.save_to_disk(&mut manager);
	let new_schema = Schema::new_from_disk(&mut manager);
	println!("new_schema == {:?}", new_schema);
	assert!(false);
}
<|endoftext|>


<commit_before>use linear_map::LinearMap;
use regex::Regex;
use error::Error;
use query::Query;
use value::{Key, Value};

/// A schema detailing what the data received from the driver (or inserted
/// into the driver) should be. Inspired after [JSON Schema][1]. A reference
/// on JSON Schema type-specific validations used in this enum may be found
/// [here][2].
///
/// [1]: http://json-schema.org
/// [2]: http://spacetelescope.github.io/understanding-json-schema/reference/type.html
pub enum Schema {
  /// There is no schema. No validations should occur. Does not represent the
  /// abscense of any value, only represents that a schema does not define the
  /// data structure at this point.
  None,
  /// Represents the absence of any value.
  Null,
  /// Represents a binary true/false value.
  Boolean,
  /// Represents a numeric type.
  Number {
    /// Forces the number to be a multiple of another. This helps in specifying
    /// integers if this value is `Some(1)` for example.
    multiple_of: Option<f32>,
    /// The minimum value the number can be.
    minimum: Option<f64>,
    /// The maximum value the number can be.
    maximum: Option<f64>
  },
  String {
    /// The mimimum length of characters in the string.
    min_length: Option<u64>,
    /// The maximum length of characters in the string.
    max_length: Option<u64>,
    /// A regular expression pattern to validate the string against.
    // TODO: Use a regex crate.
    pattern: Option<String>
  },
  /// Represents a set of any type.
  Array {
    /// A schema which all items in the array must match.
    items: Box<Schema>
  },
  /// Represents any tuple of values.
  Tuple {
    /// Schemas which each tuple value (in the same place) must comply with.
    items: Vec<Schema>,
    /// Whether or not there can be more items in the tuple.
    additional_items: bool
  },
  /// Represents a set of key/value pairs.
  Object {
    /// Schemas associated to the object properties.
    properties: LinearMap<Key, Schema>,
    /// Properties that are required to be in the object.
    required: Vec<Key>,
    /// Whether or not there may be extra properties outside of the ones
    /// defined by the properties map.
    additional_properties: bool
  },
  /// Represents a value which *must* be one of the defined values. An enum is
  /// considered a primitive type as if it is a single value is a higher order
  /// type, no variation is allowed.
  Enum(Vec<Value>)
}

impl Schema {
  /// Validates a query that a user would like to make on the database by
  /// comparing it to the schema. Mostly checks that all properties described
  /// in the query are accessible according to the schema.
  pub fn validate_query(&self, query: &Query) -> Result<(), Error> {
    static NO_PRIMITIVE_HINT: &'static str = "Try not querying specific properties of a primitive like `null` or `boolean`.";
    lazy_static! { static ref INTEGER_RE: Regex = Regex::new(r"^\d+$").unwrap(); }

    match (self, query) {
      // No schema describes these values, its the wild west. Go crazy query.
      // `Schema::None` does not represent the absence of value, just the
      // absence of validation.
      (&Schema::None, _) => Ok(()),
      (&Schema::Null, &Query::Value) => Ok(()),
      (&Schema::Null, &Query::Object(_)) => Err(Error::validation("Cannot deeply query null.", NO_PRIMITIVE_HINT)),
      (&Schema::Boolean, &Query::Value) => Ok(()),
      (&Schema::Boolean, &Query::Object(_)) => Err(Error::validation("Cannot deeply query a boolean.", NO_PRIMITIVE_HINT)),
      (&Schema::Number{..}, &Query::Value) => Ok(()),
      (&Schema::Number{..}, &Query::Object(_)) => Err(Error::validation("Cannot deeply query a number.", NO_PRIMITIVE_HINT)),
      (&Schema::String{..}, &Query::Value) => Ok(()),
      (&Schema::String{..}, &Query::Object(_)) => Err(Error::validation("Cannot deeply query a string.", NO_PRIMITIVE_HINT)),
      (&Schema::Array{..}, &Query::Value) => Ok(()),
      (&Schema::Array{ref items}, &Query::Object(ref query_properties)) => {
        match query_properties.keys().map(|key| {
          if !INTEGER_RE.is_match(key) {
            Err(Error::validation(format!("Cannot query non-integer \"{}\" array property.", key), "Only query integer array keys like 1, 2, and 3."))
          } else {
            items.validate_query(&query_properties[key])
          }
        }).find(|r| r.is_err()) {
          None => Ok(()),
          Some(error) => error
        }
      },
      (&Schema::Tuple{..}, &Query::Value) => Ok(()),
      (&Schema::Tuple{ref items, ref additional_items}, &Query::Object(ref query_properties)) => {
        match query_properties.keys().map(|key| {
          if !INTEGER_RE.is_match(key) {
            Err(Error::validation(format!("Cannot query non-integer \"{}\" array property.", key), "Only query integer array keys like 1, 2, and 3."))
          } else if let Some(item_schema) = items.get(key.parse::<usize>().unwrap()) {
            item_schema.validate_query(&query_properties[key])
          } else if !additional_items {
            Err(Error::validation(format!("Tuple has only {} values. Can’t query the index {}.", items.len(), key), format!("Query a key less than or equal to {}.", items.len() - 1)))
          } else {
            Ok(())
          }
        }).find(|r| r.is_err()) {
          None => Ok(()),
          Some(error) => error
        }
      },
      (&Schema::Object{..}, &Query::Value) => Ok(()),
      (&Schema::Object{ref properties, ref additional_properties,..}, &Query::Object(ref query_properties)) => {
        match query_properties.keys().map(|key| {
          if let Some(property_schema) = properties.get(key) {
            property_schema.validate_query(&query_properties[key])
          } else if !additional_properties {
            Err(Error::validation(format!("Cannot query object property \"{}\".", key), "Query an object property that is defined in the schema."))
          } else {
            Ok(())
          }
        }).find(|r| r.is_err()) {
          None => Ok(()),
          Some(error) => error
        }
      },
      (&Schema::Enum(_), &Query::Value) => Ok(()),
      (&Schema::Enum(_), &Query::Object(_)) => Err(Error::validation("Cannot deeply query an enum.", NO_PRIMITIVE_HINT))
    }
  }
}

#[cfg(test)]
mod tests {
  use linear_map::LinearMap;
  use schema::Schema;

  #[test]
  fn test_query_none() {
    assert_eq!(Schema::None.validate_query(&qvalue!()).is_ok(), true);
    assert_eq!(Schema::None.validate_query(&qobject!{
      "s@#f&/Ij)82h(;pa0]" => qvalue!(),
      "123" => qvalue!(),
      "hello" => qvalue!(),
      "nested" => qobject!{
        "yo" => qvalue!()
      }
    }).is_ok(), true);
  }

  #[test]
  fn test_query_primitive() {
    assert!(Schema::Null.validate_query(&qvalue!()).is_ok());
    let obj_query = qobject!{};
    Schema::Null.validate_query(&obj_query).unwrap_err().assert_message(r"deeply query");
    Schema::Boolean.validate_query(&obj_query).unwrap_err().assert_message(r"deeply query");
    Schema::Number{
      multiple_of: None,
      minimum: None,
      maximum: None
    }.validate_query(&obj_query).unwrap_err().assert_message(r"deeply query");
    Schema::String{
      min_length: None,
      max_length: None,
      pattern: None
    }.validate_query(&obj_query).unwrap_err().assert_message(r"deeply query");
    Schema::Enum(vec![
      vbool!(true),
      vbool!(false)
    ]).validate_query(&obj_query).unwrap_err().assert_message(r"deeply query");
  }

  #[test]
  fn test_query_array() {
    let array_none = Schema::Array{
      items: Box::new(Schema::None)
    };
    let array_bool = Schema::Array{
      items: Box::new(Schema::Boolean)
    };
    assert!(array_none.validate_query(&qvalue!()).is_ok());
    assert!(array_none.validate_query(&qobject!{
      "1" => qvalue!()
    }).is_ok());
    assert!(array_none.validate_query(&qobject!{
      "1" => qobject!{}
    }).is_ok());
    assert!(array_bool.validate_query(&qobject!{
      "1" => qvalue!(),
      "2" => qvalue!(),
      "3" => qvalue!(),
      "50" => qvalue!(),
      "999999999999999" => qvalue!()
    }).is_ok());
    array_none.validate_query(&qobject!{
      "hello" => qvalue!()
    }).unwrap_err().assert_message("non-integer \"hello\"");
    array_bool.validate_query(&qobject!{
      "1" => qobject!{}
    }).unwrap_err().assert_message(r"Cannot deeply query a boolean\.");
  }

  #[test]
  fn test_query_tuple() {
    let nums = Schema::Tuple{
      items: vec![Schema::Boolean, Schema::Boolean, Schema::Boolean],
      additional_items: false
    };
    let nums_additional = Schema::Tuple{
      items: vec![Schema::Boolean, Schema::Boolean, Schema::Boolean],
      additional_items: true
    };
    let nums_and_object = Schema::Tuple{
      items: vec![
        Schema::Boolean,
        Schema::Object{
          properties: {
            let mut map = LinearMap::new();
            map.insert(String::from("hello"), Schema::Boolean);
            map
          },
          required: vec![],
          additional_properties: false
        },
        Schema::Boolean
      ],
      additional_items: false
    };
    assert!(nums.validate_query(&qobject!{
      "0" => qvalue!(),
      "2" => qvalue!()
    }).is_ok());
    nums.validate_query(&qobject!{
      "0" => qvalue!(),
      "1" => qvalue!(),
      "2" => qvalue!(),
      "3" => qvalue!()
    }).unwrap_err().assert_message(r"Tuple has only 3 values\. Can’t query the index 3\.");
    nums.validate_query(&qobject!{
      "asd" => qvalue!(),
      "1" => qvalue!(),
      "2" => qvalue!(),
      "3" => qvalue!()
    }).unwrap_err().assert_message("non-integer \"asd\"");
    nums.validate_query(&qobject!{
      "1" => qvalue!(),
      "2" => qobject!{},
      "3" => qvalue!()
    }).unwrap_err().assert_message(r"Cannot deeply query a boolean\.");
    assert!(nums_additional.validate_query(&qobject!{
      "0" => qvalue!(),
      "1" => qvalue!(),
      "3" => qvalue!(),
      "99999999" => qvalue!()
    }).is_ok());
    assert!(nums_and_object.validate_query(&qobject!{
      "0" => qvalue!(),
      "1" => qobject!{"hello" => qvalue!()},
      "2" => qvalue!()
    }).is_ok());
  }

  #[test]
  fn test_query_object() {
    let object = Schema::Object{
      properties: {
        let mut map1 = LinearMap::new();
        map1.insert(String::from("hello"), Schema::Boolean);
        map1.insert(String::from("world"), Schema::Boolean);
        map1.insert(String::from("5"), Schema::Boolean);
        map1.insert(String::from("goodbye"), Schema::Object{
          properties: {
            let mut map2 = LinearMap::new();
            map2.insert(String::from("hello"), Schema::Boolean);
            map2.insert(String::from("world"), Schema::Boolean);
            map2
          },
          required: vec![],
          additional_properties: false
        });
        map1
      },
      required: vec![],
      additional_properties: false
    };
    let object_additional = Schema::Object{
      properties: {
        let mut map = LinearMap::new();
        map.insert(String::from("hello"), Schema::Boolean);
        map.insert(String::from("world"), Schema::Boolean);
        map
      },
      required: vec![],
      additional_properties: true
    };
    assert!(object.validate_query(&qobject!{
      "world" => qvalue!(),
      "5" => qvalue!(),
      "goodbye" => qvalue!()
    }).is_ok());
    object.validate_query(&qobject!{
      "hello" => qvalue!(),
      "moon" => qvalue!()
    }).unwrap_err().assert_message("Cannot query object property \"moon\".");
    object.validate_query(&qobject!{
      "hello" => qobject!{}
    }).unwrap_err().assert_message(r"Cannot deeply query a boolean\.");
    assert!(object.validate_query(&qobject!{
      "goodbye" => qobject!{
        "hello" => qvalue!()
      }
    }).is_ok());
    object.validate_query(&qobject!{
      "goodbye" => qobject!{
        "hello" => qobject!{}
      }
    }).unwrap_err().assert_message(r"Cannot deeply query a boolean\.");
    assert!(object_additional.validate_query(&qobject!{
      "world" => qvalue!(),
      "5" => qvalue!(),
      "goodbye" => qvalue!(),
      "moon" => qvalue!()
    }).is_ok());
  }
}
<commit_msg>feat: add exclusive number validations<commit_after>use linear_map::LinearMap;
use regex::Regex;
use error::Error;
use query::Query;
use value::{Key, Value};

/// A schema detailing what the data received from the driver (or inserted
/// into the driver) should be. Inspired after [JSON Schema][1]. A reference
/// on JSON Schema type-specific validations used in this enum may be found
/// [here][2].
///
/// [1]: http://json-schema.org
/// [2]: http://spacetelescope.github.io/understanding-json-schema/reference/type.html
pub enum Schema {
  /// There is no schema. No validations should occur. Does not represent the
  /// abscense of any value, only represents that a schema does not define the
  /// data structure at this point.
  None,
  /// Represents the absence of any value.
  Null,
  /// Represents a binary true/false value.
  Boolean,
  /// Represents a numeric type.
  Number {
    /// Forces the number to be a multiple of another. This helps in specifying
    /// integers if this value is `Some(1)` for example.
    multiple_of: Option<f32>,
    /// The minimum value the number can be.
    minimum: Option<f64>,
    /// Whether or not the minimum value should be included when validating.
    /// Default is `false`.
    exclusive_minimum: bool,
    /// The maximum value the number can be.
    maximum: Option<f64>,
    /// Whether or not the maximum value should be included when validating.
    /// Default is `false`.
    exclusive_maximum: bool
  },
  String {
    /// The mimimum length of characters in the string.
    min_length: Option<u64>,
    /// The maximum length of characters in the string.
    max_length: Option<u64>,
    /// A regular expression pattern to validate the string against.
    // TODO: Use a regex crate.
    pattern: Option<String>
  },
  /// Represents a set of any type.
  Array {
    /// A schema which all items in the array must match.
    items: Box<Schema>
  },
  /// Represents any tuple of values.
  Tuple {
    /// Schemas which each tuple value (in the same place) must comply with.
    items: Vec<Schema>,
    /// Whether or not there can be more items in the tuple.
    additional_items: bool
  },
  /// Represents a set of key/value pairs.
  Object {
    /// Schemas associated to the object properties.
    properties: LinearMap<Key, Schema>,
    /// Properties that are required to be in the object.
    required: Vec<Key>,
    /// Whether or not there may be extra properties outside of the ones
    /// defined by the properties map.
    additional_properties: bool
  },
  /// Represents a value which *must* be one of the defined values. An enum is
  /// considered a primitive type as if it is a single value is a higher order
  /// type, no variation is allowed.
  Enum(Vec<Value>)
}

impl Schema {
  /// Validates a query that a user would like to make on the database by
  /// comparing it to the schema. Mostly checks that all properties described
  /// in the query are accessible according to the schema.
  pub fn validate_query(&self, query: &Query) -> Result<(), Error> {
    static NO_PRIMITIVE_HINT: &'static str = "Try not querying specific properties of a primitive like `null` or `boolean`.";
    lazy_static! { static ref INTEGER_RE: Regex = Regex::new(r"^\d+$").unwrap(); }

    match (self, query) {
      // No schema describes these values, its the wild west. Go crazy query.
      // `Schema::None` does not represent the absence of value, just the
      // absence of validation.
      (&Schema::None, _) => Ok(()),
      (&Schema::Null, &Query::Value) => Ok(()),
      (&Schema::Null, &Query::Object(_)) => Err(Error::validation("Cannot deeply query null.", NO_PRIMITIVE_HINT)),
      (&Schema::Boolean, &Query::Value) => Ok(()),
      (&Schema::Boolean, &Query::Object(_)) => Err(Error::validation("Cannot deeply query a boolean.", NO_PRIMITIVE_HINT)),
      (&Schema::Number{..}, &Query::Value) => Ok(()),
      (&Schema::Number{..}, &Query::Object(_)) => Err(Error::validation("Cannot deeply query a number.", NO_PRIMITIVE_HINT)),
      (&Schema::String{..}, &Query::Value) => Ok(()),
      (&Schema::String{..}, &Query::Object(_)) => Err(Error::validation("Cannot deeply query a string.", NO_PRIMITIVE_HINT)),
      (&Schema::Array{..}, &Query::Value) => Ok(()),
      (&Schema::Array{ref items}, &Query::Object(ref query_properties)) => {
        match query_properties.keys().map(|key| {
          if !INTEGER_RE.is_match(key) {
            Err(Error::validation(format!("Cannot query non-integer \"{}\" array property.", key), "Only query integer array keys like 1, 2, and 3."))
          } else {
            items.validate_query(&query_properties[key])
          }
        }).find(|r| r.is_err()) {
          None => Ok(()),
          Some(error) => error
        }
      },
      (&Schema::Tuple{..}, &Query::Value) => Ok(()),
      (&Schema::Tuple{ref items, ref additional_items}, &Query::Object(ref query_properties)) => {
        match query_properties.keys().map(|key| {
          if !INTEGER_RE.is_match(key) {
            Err(Error::validation(format!("Cannot query non-integer \"{}\" array property.", key), "Only query integer array keys like 1, 2, and 3."))
          } else if let Some(item_schema) = items.get(key.parse::<usize>().unwrap()) {
            item_schema.validate_query(&query_properties[key])
          } else if !additional_items {
            Err(Error::validation(format!("Tuple has only {} values. Can’t query the index {}.", items.len(), key), format!("Query a key less than or equal to {}.", items.len() - 1)))
          } else {
            Ok(())
          }
        }).find(|r| r.is_err()) {
          None => Ok(()),
          Some(error) => error
        }
      },
      (&Schema::Object{..}, &Query::Value) => Ok(()),
      (&Schema::Object{ref properties, ref additional_properties,..}, &Query::Object(ref query_properties)) => {
        match query_properties.keys().map(|key| {
          if let Some(property_schema) = properties.get(key) {
            property_schema.validate_query(&query_properties[key])
          } else if !additional_properties {
            Err(Error::validation(format!("Cannot query object property \"{}\".", key), "Query an object property that is defined in the schema."))
          } else {
            Ok(())
          }
        }).find(|r| r.is_err()) {
          None => Ok(()),
          Some(error) => error
        }
      },
      (&Schema::Enum(_), &Query::Value) => Ok(()),
      (&Schema::Enum(_), &Query::Object(_)) => Err(Error::validation("Cannot deeply query an enum.", NO_PRIMITIVE_HINT))
    }
  }
}

#[cfg(test)]
mod tests {
  use linear_map::LinearMap;
  use schema::Schema;

  #[test]
  fn test_query_none() {
    assert_eq!(Schema::None.validate_query(&qvalue!()).is_ok(), true);
    assert_eq!(Schema::None.validate_query(&qobject!{
      "s@#f&/Ij)82h(;pa0]" => qvalue!(),
      "123" => qvalue!(),
      "hello" => qvalue!(),
      "nested" => qobject!{
        "yo" => qvalue!()
      }
    }).is_ok(), true);
  }

  #[test]
  fn test_query_primitive() {
    assert!(Schema::Null.validate_query(&qvalue!()).is_ok());
    let obj_query = qobject!{};
    Schema::Null.validate_query(&obj_query).unwrap_err().assert_message(r"deeply query");
    Schema::Boolean.validate_query(&obj_query).unwrap_err().assert_message(r"deeply query");
    Schema::Number{
      multiple_of: None,
      minimum: None,
      exclusive_minimum: false,
      maximum: None,
      exclusive_maximum: false
    }.validate_query(&obj_query).unwrap_err().assert_message(r"deeply query");
    Schema::String{
      min_length: None,
      max_length: None,
      pattern: None
    }.validate_query(&obj_query).unwrap_err().assert_message(r"deeply query");
    Schema::Enum(vec![
      vbool!(true),
      vbool!(false)
    ]).validate_query(&obj_query).unwrap_err().assert_message(r"deeply query");
  }

  #[test]
  fn test_query_array() {
    let array_none = Schema::Array{
      items: Box::new(Schema::None)
    };
    let array_bool = Schema::Array{
      items: Box::new(Schema::Boolean)
    };
    assert!(array_none.validate_query(&qvalue!()).is_ok());
    assert!(array_none.validate_query(&qobject!{
      "1" => qvalue!()
    }).is_ok());
    assert!(array_none.validate_query(&qobject!{
      "1" => qobject!{}
    }).is_ok());
    assert!(array_bool.validate_query(&qobject!{
      "1" => qvalue!(),
      "2" => qvalue!(),
      "3" => qvalue!(),
      "50" => qvalue!(),
      "999999999999999" => qvalue!()
    }).is_ok());
    array_none.validate_query(&qobject!{
      "hello" => qvalue!()
    }).unwrap_err().assert_message("non-integer \"hello\"");
    array_bool.validate_query(&qobject!{
      "1" => qobject!{}
    }).unwrap_err().assert_message(r"Cannot deeply query a boolean\.");
  }

  #[test]
  fn test_query_tuple() {
    let nums = Schema::Tuple{
      items: vec![Schema::Boolean, Schema::Boolean, Schema::Boolean],
      additional_items: false
    };
    let nums_additional = Schema::Tuple{
      items: vec![Schema::Boolean, Schema::Boolean, Schema::Boolean],
      additional_items: true
    };
    let nums_and_object = Schema::Tuple{
      items: vec![
        Schema::Boolean,
        Schema::Object{
          properties: {
            let mut map = LinearMap::new();
            map.insert(String::from("hello"), Schema::Boolean);
            map
          },
          required: vec![],
          additional_properties: false
        },
        Schema::Boolean
      ],
      additional_items: false
    };
    assert!(nums.validate_query(&qobject!{
      "0" => qvalue!(),
      "2" => qvalue!()
    }).is_ok());
    nums.validate_query(&qobject!{
      "0" => qvalue!(),
      "1" => qvalue!(),
      "2" => qvalue!(),
      "3" => qvalue!()
    }).unwrap_err().assert_message(r"Tuple has only 3 values\. Can’t query the index 3\.");
    nums.validate_query(&qobject!{
      "asd" => qvalue!(),
      "1" => qvalue!(),
      "2" => qvalue!(),
      "3" => qvalue!()
    }).unwrap_err().assert_message("non-integer \"asd\"");
    nums.validate_query(&qobject!{
      "1" => qvalue!(),
      "2" => qobject!{},
      "3" => qvalue!()
    }).unwrap_err().assert_message(r"Cannot deeply query a boolean\.");
    assert!(nums_additional.validate_query(&qobject!{
      "0" => qvalue!(),
      "1" => qvalue!(),
      "3" => qvalue!(),
      "99999999" => qvalue!()
    }).is_ok());
    assert!(nums_and_object.validate_query(&qobject!{
      "0" => qvalue!(),
      "1" => qobject!{"hello" => qvalue!()},
      "2" => qvalue!()
    }).is_ok());
  }

  #[test]
  fn test_query_object() {
    let object = Schema::Object{
      properties: {
        let mut map1 = LinearMap::new();
        map1.insert(String::from("hello"), Schema::Boolean);
        map1.insert(String::from("world"), Schema::Boolean);
        map1.insert(String::from("5"), Schema::Boolean);
        map1.insert(String::from("goodbye"), Schema::Object{
          properties: {
            let mut map2 = LinearMap::new();
            map2.insert(String::from("hello"), Schema::Boolean);
            map2.insert(String::from("world"), Schema::Boolean);
            map2
          },
          required: vec![],
          additional_properties: false
        });
        map1
      },
      required: vec![],
      additional_properties: false
    };
    let object_additional = Schema::Object{
      properties: {
        let mut map = LinearMap::new();
        map.insert(String::from("hello"), Schema::Boolean);
        map.insert(String::from("world"), Schema::Boolean);
        map
      },
      required: vec![],
      additional_properties: true
    };
    assert!(object.validate_query(&qobject!{
      "world" => qvalue!(),
      "5" => qvalue!(),
      "goodbye" => qvalue!()
    }).is_ok());
    object.validate_query(&qobject!{
      "hello" => qvalue!(),
      "moon" => qvalue!()
    }).unwrap_err().assert_message("Cannot query object property \"moon\".");
    object.validate_query(&qobject!{
      "hello" => qobject!{}
    }).unwrap_err().assert_message(r"Cannot deeply query a boolean\.");
    assert!(object.validate_query(&qobject!{
      "goodbye" => qobject!{
        "hello" => qvalue!()
      }
    }).is_ok());
    object.validate_query(&qobject!{
      "goodbye" => qobject!{
        "hello" => qobject!{}
      }
    }).unwrap_err().assert_message(r"Cannot deeply query a boolean\.");
    assert!(object_additional.validate_query(&qobject!{
      "world" => qvalue!(),
      "5" => qvalue!(),
      "goodbye" => qvalue!(),
      "moon" => qvalue!()
    }).is_ok());
  }
}
<|endoftext|>