<commit_before>#![deny(warnings)]
extern crate extra;
use extra::option::OptionalExt;
use extra::io::{fail, WriteExt};

use std::env::args;
use std::io::{self, Write};

#[derive(Debug, Clone)]
pub enum Token {
    Plus,
    Minus,
    Divide,
    Multiply,
    Exponent,
    OpenParen,
    CloseParen,
    Number(String),
}

impl Token {
    pub fn to_str(&self) -> &'static str {
        match self {
            &Token::Plus       => "Plus",
            &Token::Minus      => "Minus",
            &Token::Divide     => "Divide",
            &Token::Multiply   => "Multiply",
            &Token::Exponent   => "Exponent",
            &Token::OpenParen  => "OpenParen",
            &Token::CloseParen => "CloseParen",
            &Token::Number(_)  => "Number",
        }
    }

    pub fn to_string(&self) -> String {
        self.to_str().to_owned()
    }
}

#[derive(Debug,  Clone)]
pub enum ParseError {
    InvalidNumber(String),
    UnrecognizedToken(String),
    UnexpectedToken(String, &'static str),
    UnexpectedEndOfInput,
    OtherError(String),
}

#[derive(Clone,Debug)]
pub struct IntermediateResult {
    value: f64,
    tokens_read: usize,
}

impl IntermediateResult {
    fn new(value: f64, tokens_read: usize) -> Self {
        IntermediateResult {
            value: value,
            tokens_read: tokens_read,
        }
    }
}

pub trait OperatorFunctions {
    fn is_operator(self) -> bool;
    fn operator_type(self) -> Token;
}

impl OperatorFunctions for char {
    fn is_operator(self) -> bool {
        self == '+' ||
        self == '-' ||
        self == '*' ||
        self == '/' ||
        self == '^' ||
        self == '(' ||
        self == ')'
    }

    fn operator_type(self) -> Token {
        match self {
            '+' => Token::Plus,
            '-' => Token::Minus,
            '/' => Token::Divide,
            '*' => Token::Multiply,
            '^' => Token::Exponent,
            '(' => Token::OpenParen,
            ')' => Token::CloseParen,
            _   => fail("Invalid operator", &mut io::stderr())
        }
    }
}

pub fn tokenize(input: &str) -> Result<Vec<Token>, ParseError> {
    // CONSIDER: input.len() too generous?
    let mut tokens = Vec::with_capacity(input.len());

    // TODO: Not this. Modify to use iterator
    let chars: Vec<char> = input.chars().collect();

    let input_length = chars.len();
    let mut current_pos = 0;
    while current_pos < input_length {
        let c = chars[current_pos];
        if c.is_digit(10) {
            let token_string = consume_number(&chars[current_pos..]);
            current_pos += token_string.len();
            tokens.push(Token::Number(token_string));
        } else if c.is_operator() {
            tokens.push(c.operator_type());
            current_pos += 1;
        } else if c.is_whitespace() {
            current_pos += 1;
        } else {
            let token_string = consume_until_new_token(&chars[current_pos..]);
            return Err(ParseError::UnrecognizedToken(token_string));
        }
    }
    Ok(tokens)
}

fn consume_number(input: &[char]) -> String {
    // CONSIDER: input.len seems a bit generous
    let mut number = String::with_capacity(input.len());
    let mut has_decimal_point = false;
    for &c in input {
        if c == '.' {
            if has_decimal_point {
                break;
            } else {
                number.push(c);
                has_decimal_point = true;
            }
        } else if c.is_digit(10) {
            number.push(c);
        } else {
            break;
        }
    }
    number
}

fn consume_until_new_token(input: &[char]) -> String {
    input.iter()
         .take_while(|c| !(c.is_whitespace() || c.is_operator() || c.is_digit(10)))
         .map(|&c| c)
         .collect()
}

// Addition and subtraction
pub fn e_expr(token_list: &[Token]) -> Result<IntermediateResult, ParseError> {
    let mut t1 = try!(t_expr(token_list));
    let mut index = t1.tokens_read;

    while index < token_list.len() {
        match token_list[index] {
            Token::Plus => {
                let t2 = try!(t_expr(&token_list[index+1..]));
                t1.value += t2.value;
                t1.tokens_read += t2.tokens_read + 1;
            }
            Token::Minus => {
                let t2 = try!(t_expr(&token_list[index+1..]));
                t1.value -= t2.value;
                t1.tokens_read += t2.tokens_read + 1;
            }
            Token::Number(ref n) => return Err(ParseError::UnexpectedToken(n.clone(),"operator")),
            _ => break,
        };
        index = t1.tokens_read;
    }
    Ok(t1)
}

// Multiplication and division
pub fn t_expr(token_list: &[Token]) -> Result<IntermediateResult, ParseError> {
    let mut f1 = try!(f_expr(token_list));
    let mut index = f1.tokens_read;

    while index < token_list.len() {
        match token_list[index] {
            Token::Multiply => {
                let f2 = try!(f_expr(&token_list[index+1..]));
                f1.value *= f2.value;
                f1.tokens_read += f2.tokens_read + 1;
            }
            Token::Divide => {
                let f2 = try!(f_expr(&token_list[index+1..]));
                if f2.value == 0.0 {
                    return Err(ParseError::OtherError("Divide by zero error".to_owned()));
                } else {
                    f1.value /= f2.value;
                    f1.tokens_read += f2.tokens_read + 1;
                }
            }
            Token::Number(ref n) => return Err(ParseError::UnexpectedToken(n.clone(),"operator")),
            _ => break,
        }
        index = f1.tokens_read;
    }
    Ok(f1)
}

// Exponentiation
pub fn f_expr(token_list: &[Token]) -> Result<IntermediateResult, ParseError> {
    let mut g1 = try!(g_expr(token_list));
    let mut index = g1.tokens_read;
    let token_len = token_list.len();
    while index < token_len {
        match token_list[index] {
            Token::Exponent => {
                let f = try!(f_expr(&token_list[index+1..]));
                g1.value = g1.value.powf(f.value);
                g1.tokens_read += f.tokens_read + 1;
            }
            Token::Number(ref n) => return Err(ParseError::UnexpectedToken(n.clone(),"operator")),
            _ => break,
        }
        index = g1.tokens_read;
    }
    Ok(g1)
}

// Numbers and parenthesized expressions
pub fn g_expr(token_list: &[Token]) -> Result<IntermediateResult, ParseError> {
    if !token_list.is_empty() {
        match token_list[0] {
            Token::Number(ref n) => {
                n.parse::<f64>()
                 .map_err(|_| ParseError::InvalidNumber(n.clone()))
                 .and_then(|num| Ok(IntermediateResult::new(num, 1)))
            }
            Token::Minus => {
                if token_list.len() > 1 {
                    if let Token::Number(ref n) = token_list[1] {
                        n.parse::<f64>()
                         .map_err(|_| ParseError::InvalidNumber(n.clone()))
                         .and_then(|num| Ok(IntermediateResult::new(-1.0 * num, 2)))
                    } else {
                        Err(ParseError::UnexpectedToken(token_list[1].to_string(), "number"))
                    }
                } else {
                    Err(ParseError::UnexpectedEndOfInput)
                }
            }
            Token::OpenParen => {
                let expr = e_expr(&token_list[1..]);
                match expr {
                    Ok(ir) => {
                        let close_paren = ir.tokens_read + 1;
                        if close_paren < token_list.len() {
                            match token_list[close_paren] {
                                Token::CloseParen => Ok(IntermediateResult::new(ir.value, close_paren+1)),
                                _ => Err(ParseError::UnexpectedToken(token_list[close_paren].to_string(), ")")),
                            }
                        } else {
                            Err(ParseError::OtherError("no matching close parenthesis found.".to_owned()))
                        }
                    }
                    Err(e) => Err(e),
                }
            }
            _ => Err(ParseError::UnexpectedToken(token_list[0].to_string(), "number"))
        }
    } else {
        Err(ParseError::UnexpectedEndOfInput)
    }
}

pub fn parse(tokens: Vec<Token>) -> Result<String, ParseError> {
    e_expr(&tokens).map(|answer| answer.value.to_string())
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn simple_addition() {
        assert_eq!(tokenize("12+3").and_then(parse).unwrap(), "15");
    }

    #[test]
    fn addition() {
        assert_eq!(tokenize("12+3+5").and_then(parse).unwrap(), "20");
    }

    #[test]
    fn simple_subtraction() {
        assert_eq!(tokenize("12-3").and_then(parse).unwrap(), "9");
    }

    #[test]
    fn subtraction() {
        assert_eq!(tokenize("12-3-4").and_then(parse).unwrap(), "5");
    }

    #[test]
    fn mixed_addition_and_subtraction() {
        assert_eq!(tokenize("12+3-4+8-2-3").and_then(parse).unwrap(), "14");
    }

    #[test]
    fn simple_parentheses() {
        assert_eq!(tokenize("((3))").and_then(parse).unwrap(), "3");
        assert_eq!(tokenize("(12+(2+3))").and_then(parse).unwrap(), "17");
        assert_eq!(tokenize("12+(2+3)").and_then(parse).unwrap(), "17");
    }

    #[test]
    fn parentheses() {
        assert_eq!(tokenize("12+(2+(3+5))+4+(((6)))").and_then(parse).unwrap(), "32");
    }

    #[test]
    fn multiplication() {
        assert_eq!(tokenize("3*3").and_then(parse).unwrap(), "9");
        assert_eq!(tokenize("3*5").and_then(parse).unwrap(), "15");
        assert_eq!(tokenize("0*5").and_then(parse).unwrap(), "0");
        assert_eq!(tokenize("5*4*3*2*1").and_then(parse).unwrap(), "120");
        assert_eq!(tokenize("(5*4)*3*(2*1)").and_then(parse).unwrap(), "120");
    }

    #[test]
    fn division() {
        assert_eq!(tokenize("12/4").and_then(parse).unwrap(), "3");
        assert_eq!(tokenize("12/3").and_then(parse).unwrap(), "4");
        assert_eq!(tokenize("5/2").and_then(parse).unwrap(), "2.5");
        assert_eq!(tokenize("120/5/4/3/2").and_then(parse).unwrap(), "1");
        assert_eq!(tokenize("(120/5)/4/(3/2)").and_then(parse).unwrap(), "4");
    }

    #[test]
    fn exponentiation() {
        assert_eq!(tokenize("3^2").and_then(parse).unwrap(), "9");
        assert_eq!(tokenize("2^3^2").and_then(parse).unwrap(), "512");
        assert_eq!(tokenize("2^(2+1)^2").and_then(parse).unwrap(), "512");
    }
}

fn eval(input: &str) -> String {
    match tokenize(input).and_then(parse) {
        Ok(s) => s,
        Err(e) => match e {
            ParseError::InvalidNumber(s) => ["Error: Invalid number: ", s.as_str() ].concat(),
            ParseError::UnrecognizedToken(s) => ["Error: Unrecognized token: ", s.as_str()].concat(),
            ParseError::UnexpectedToken(found, expected) => ["Error: Unexpected token: expected [", expected, "] but found '", found.as_str(), "'"].concat(),
            ParseError::UnexpectedEndOfInput => "Error: Unexpected end of input.".to_owned(),
            ParseError::OtherError(s) => s,
        }
    }
}

fn main() {
    let args = args();
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let mut stderr = io::stderr();
    if args.len() > 1 {
        let input: Vec<String> = args.skip(1).collect();
        stdout.writeln(eval(&input.join("")).as_bytes()).try(&mut stderr);
    } else {
        let prompt = "[]> ".as_bytes();
        loop {
            stdout.write(prompt).try(&mut stderr);
            stdout.flush().try(&mut stderr);
            let mut input = String::new();
            io::stdin().read_line(&mut input)
                       .try(&mut stderr);
            if input.is_empty() {
                break;
            } else {
                match input.trim() {
                    "" => (),
                    "exit" => break,
                    s => {
                        stdout.writeln(eval(s).as_bytes()).try(&mut stderr);
                        stdout.flush().try(&mut stderr);
                    },
                }
            }
        }
    }
}
<commit_msg>Allow numbers to start with decimal point<commit_after>#![deny(warnings)]
extern crate extra;
use extra::option::OptionalExt;
use extra::io::{fail, WriteExt};

use std::env::args;
use std::io::{self, Write};

#[derive(Debug, Clone)]
pub enum Token {
    Plus,
    Minus,
    Divide,
    Multiply,
    Exponent,
    OpenParen,
    CloseParen,
    Number(String),
}

impl Token {
    pub fn to_str(&self) -> &'static str {
        match self {
            &Token::Plus       => "Plus",
            &Token::Minus      => "Minus",
            &Token::Divide     => "Divide",
            &Token::Multiply   => "Multiply",
            &Token::Exponent   => "Exponent",
            &Token::OpenParen  => "OpenParen",
            &Token::CloseParen => "CloseParen",
            &Token::Number(_)  => "Number",
        }
    }

    pub fn to_string(&self) -> String {
        self.to_str().to_owned()
    }
}

#[derive(Debug,  Clone)]
pub enum ParseError {
    InvalidNumber(String),
    UnrecognizedToken(String),
    UnexpectedToken(String, &'static str),
    UnexpectedEndOfInput,
    OtherError(String),
}

#[derive(Clone,Debug)]
pub struct IntermediateResult {
    value: f64,
    tokens_read: usize,
}

impl IntermediateResult {
    fn new(value: f64, tokens_read: usize) -> Self {
        IntermediateResult {
            value: value,
            tokens_read: tokens_read,
        }
    }
}

pub trait OperatorFunctions {
    fn is_operator(self) -> bool;
    fn operator_type(self) -> Token;
}

impl OperatorFunctions for char {
    fn is_operator(self) -> bool {
        self == '+' ||
        self == '-' ||
        self == '*' ||
        self == '/' ||
        self == '^' ||
        self == '(' ||
        self == ')'
    }

    fn operator_type(self) -> Token {
        match self {
            '+' => Token::Plus,
            '-' => Token::Minus,
            '/' => Token::Divide,
            '*' => Token::Multiply,
            '^' => Token::Exponent,
            '(' => Token::OpenParen,
            ')' => Token::CloseParen,
            _   => fail("Invalid operator", &mut io::stderr())
        }
    }
}

pub fn tokenize(input: &str) -> Result<Vec<Token>, ParseError> {
    let mut tokens = Vec::with_capacity(input.len());

    // TODO: Not this. Modify to use iterator
    let chars: Vec<char> = input.chars().collect();

    let input_length = chars.len();
    let mut current_pos = 0;
    while current_pos < input_length {
        let c = chars[current_pos];
        if c.is_digit(10) || c == '.' {
            let token_string = consume_number(&chars[current_pos..]);
            current_pos += token_string.len();
            tokens.push(Token::Number(token_string));
        } else if c.is_operator() {
            tokens.push(c.operator_type());
            current_pos += 1;
        } else if c.is_whitespace() {
            current_pos += 1;
        } else {
            let token_string = consume_until_new_token(&chars[current_pos..]);
            return Err(ParseError::UnrecognizedToken(token_string));
        }
    }
    Ok(tokens)
}

fn consume_number(input: &[char]) -> String {
    let mut number = String::with_capacity(input.len());
    let mut has_decimal_point = false;
    for &c in input {
        if c == '.' {
            if has_decimal_point {
                break;
            } else {
                number.push(c);
                has_decimal_point = true;
            }
        } else if c.is_digit(10) {
            number.push(c);
        } else {
            break;
        }
    }
    number
}

fn consume_until_new_token(input: &[char]) -> String {
    input.iter()
         .take_while(|c| !(c.is_whitespace() || c.is_operator() || c.is_digit(10)))
         .map(|&c| c)
         .collect()
}

// Addition and subtraction
pub fn e_expr(token_list: &[Token]) -> Result<IntermediateResult, ParseError> {
    let mut t1 = try!(t_expr(token_list));
    let mut index = t1.tokens_read;

    while index < token_list.len() {
        match token_list[index] {
            Token::Plus => {
                let t2 = try!(t_expr(&token_list[index+1..]));
                t1.value += t2.value;
                t1.tokens_read += t2.tokens_read + 1;
            }
            Token::Minus => {
                let t2 = try!(t_expr(&token_list[index+1..]));
                t1.value -= t2.value;
                t1.tokens_read += t2.tokens_read + 1;
            }
            Token::Number(ref n) => return Err(ParseError::UnexpectedToken(n.clone(),"operator")),
            _ => break,
        };
        index = t1.tokens_read;
    }
    Ok(t1)
}

// Multiplication and division
pub fn t_expr(token_list: &[Token]) -> Result<IntermediateResult, ParseError> {
    let mut f1 = try!(f_expr(token_list));
    let mut index = f1.tokens_read;

    while index < token_list.len() {
        match token_list[index] {
            Token::Multiply => {
                let f2 = try!(f_expr(&token_list[index+1..]));
                f1.value *= f2.value;
                f1.tokens_read += f2.tokens_read + 1;
            }
            Token::Divide => {
                let f2 = try!(f_expr(&token_list[index+1..]));
                if f2.value == 0.0 {
                    return Err(ParseError::OtherError("Divide by zero error".to_owned()));
                } else {
                    f1.value /= f2.value;
                    f1.tokens_read += f2.tokens_read + 1;
                }
            }
            Token::Number(ref n) => return Err(ParseError::UnexpectedToken(n.clone(),"operator")),
            _ => break,
        }
        index = f1.tokens_read;
    }
    Ok(f1)
}

// Exponentiation
pub fn f_expr(token_list: &[Token]) -> Result<IntermediateResult, ParseError> {
    let mut g1 = try!(g_expr(token_list));
    let mut index = g1.tokens_read;
    let token_len = token_list.len();
    while index < token_len {
        match token_list[index] {
            Token::Exponent => {
                let f = try!(f_expr(&token_list[index+1..]));
                g1.value = g1.value.powf(f.value);
                g1.tokens_read += f.tokens_read + 1;
            }
            Token::Number(ref n) => return Err(ParseError::UnexpectedToken(n.clone(),"operator")),
            _ => break,
        }
        index = g1.tokens_read;
    }
    Ok(g1)
}

// Numbers and parenthesized expressions
pub fn g_expr(token_list: &[Token]) -> Result<IntermediateResult, ParseError> {
    if !token_list.is_empty() {
        match token_list[0] {
            Token::Number(ref n) => {
                n.parse::<f64>()
                 .map_err(|_| ParseError::InvalidNumber(n.clone()))
                 .and_then(|num| Ok(IntermediateResult::new(num, 1)))
            }
            Token::Minus => {
                if token_list.len() > 1 {
                    if let Token::Number(ref n) = token_list[1] {
                        n.parse::<f64>()
                         .map_err(|_| ParseError::InvalidNumber(n.clone()))
                         .and_then(|num| Ok(IntermediateResult::new(-1.0 * num, 2)))
                    } else {
                        Err(ParseError::UnexpectedToken(token_list[1].to_string(), "number"))
                    }
                } else {
                    Err(ParseError::UnexpectedEndOfInput)
                }
            }
            Token::OpenParen => {
                let expr = e_expr(&token_list[1..]);
                match expr {
                    Ok(ir) => {
                        let close_paren = ir.tokens_read + 1;
                        if close_paren < token_list.len() {
                            match token_list[close_paren] {
                                Token::CloseParen => Ok(IntermediateResult::new(ir.value, close_paren+1)),
                                _ => Err(ParseError::UnexpectedToken(token_list[close_paren].to_string(), ")")),
                            }
                        } else {
                            Err(ParseError::OtherError("no matching close parenthesis found.".to_owned()))
                        }
                    }
                    Err(e) => Err(e),
                }
            }
            _ => Err(ParseError::UnexpectedToken(token_list[0].to_string(), "number"))
        }
    } else {
        Err(ParseError::UnexpectedEndOfInput)
    }
}

pub fn parse(tokens: Vec<Token>) -> Result<String, ParseError> {
    e_expr(&tokens).map(|answer| answer.value.to_string())
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn simple_addition() {
        assert_eq!(tokenize("12+3").and_then(parse).unwrap(), "15");
    }

    #[test]
    fn addition() {
        assert_eq!(tokenize("12+3+5").and_then(parse).unwrap(), "20");
    }

    #[test]
    fn simple_subtraction() {
        assert_eq!(tokenize("12-3").and_then(parse).unwrap(), "9");
    }

    #[test]
    fn subtraction() {
        assert_eq!(tokenize("12-3-4").and_then(parse).unwrap(), "5");
    }

    #[test]
    fn mixed_addition_and_subtraction() {
        assert_eq!(tokenize("12+3-4+8-2-3").and_then(parse).unwrap(), "14");
    }

    #[test]
    fn simple_parentheses() {
        assert_eq!(tokenize("((3))").and_then(parse).unwrap(), "3");
        assert_eq!(tokenize("(12+(2+3))").and_then(parse).unwrap(), "17");
        assert_eq!(tokenize("12+(2+3)").and_then(parse).unwrap(), "17");
    }

    #[test]
    fn parentheses() {
        assert_eq!(tokenize("12+(2+(3+5))+4+(((6)))").and_then(parse).unwrap(), "32");
    }

    #[test]
    fn multiplication() {
        assert_eq!(tokenize("3*3").and_then(parse).unwrap(), "9");
        assert_eq!(tokenize("3*5").and_then(parse).unwrap(), "15");
        assert_eq!(tokenize("0*5").and_then(parse).unwrap(), "0");
        assert_eq!(tokenize("5*4*3*2*1").and_then(parse).unwrap(), "120");
        assert_eq!(tokenize("(5*4)*3*(2*1)").and_then(parse).unwrap(), "120");
    }

    #[test]
    fn division() {
        assert_eq!(tokenize("12/4").and_then(parse).unwrap(), "3");
        assert_eq!(tokenize("12/3").and_then(parse).unwrap(), "4");
        assert_eq!(tokenize("5/2").and_then(parse).unwrap(), "2.5");
        assert_eq!(tokenize("120/5/4/3/2").and_then(parse).unwrap(), "1");
        assert_eq!(tokenize("(120/5)/4/(3/2)").and_then(parse).unwrap(), "4");
    }

    #[test]
    fn exponentiation() {
        assert_eq!(tokenize("3^2").and_then(parse).unwrap(), "9");
        assert_eq!(tokenize("2^3^2").and_then(parse).unwrap(), "512");
        assert_eq!(tokenize("2^(2+1)^2").and_then(parse).unwrap(), "512");
    }
}

fn eval(input: &str) -> String {
    match tokenize(input).and_then(parse) {
        Ok(s) => s,
        Err(e) => match e {
            ParseError::InvalidNumber(s) => ["Error: Invalid number: ", s.as_str() ].concat(),
            ParseError::UnrecognizedToken(s) => ["Error: Unrecognized token: ", s.as_str()].concat(),
            ParseError::UnexpectedToken(found, expected) => ["Error: Unexpected token: expected [", expected, "] but found '", found.as_str(), "'"].concat(),
            ParseError::UnexpectedEndOfInput => "Error: Unexpected end of input.".to_owned(),
            ParseError::OtherError(s) => s,
        }
    }
}

fn main() {
    let args = args();
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let mut stderr = io::stderr();
    if args.len() > 1 {
        let input: Vec<String> = args.skip(1).collect();
        stdout.writeln(eval(&input.join("")).as_bytes()).try(&mut stderr);
    } else {
        let prompt = "[]> ".as_bytes();
        loop {
            stdout.write(prompt).try(&mut stderr);
            stdout.flush().try(&mut stderr);
            let mut input = String::new();
            io::stdin().read_line(&mut input)
                       .try(&mut stderr);
            if input.is_empty() {
                break;
            } else {
                match input.trim() {
                    "" => (),
                    "exit" => break,
                    s => {
                        stdout.writeln(eval(s).as_bytes()).try(&mut stderr);
                        stdout.flush().try(&mut stderr);
                    },
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>//! An echo server that just writes back everything that's written to it.

extern crate futures;
extern crate futures_io;
extern crate futures_mio;

use std::env;
use std::net::SocketAddr;

use futures::Future;
use futures_io::{copy, TaskIo};
use futures::stream::Stream;

fn main() {
    let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
    let addr = addr.parse::<SocketAddr>().unwrap();

    // Create the event loop that will drive this server
    let mut l = futures_mio::Loop::new().unwrap();

    // Create a TCP listener which will listen for incoming connections
    let server = l.handle().tcp_listen(&addr);

    let done = server.and_then(move |socket| {
        // Once we've got the TCP listener, inform that we have it
        println!("Listenering on: {}", addr);

        // Pull out the stream of incoming connections and then for each new
        // one spin up a new task copying data. We put the `socket` into a
        // `TaskIo` structure which then allows us to `split` it into the read
        // and write halves of the socket.
        //
        // Finally we use the `io::copy` future to copy all data from the
        // reading half onto the writing half.
        socket.incoming().for_each(|(socket, addr)| {
            let io = TaskIo::new(socket);
            let pair = io.map(|io| io.split());
            let amt = pair.and_then(|(reader, writer)| {
                copy(reader, writer)
            });

            // Once all that is done we print out how much we wrote, and then
            // critically we *forget* this future which allows it to run
            // concurrently with other connections.
            amt.map(move |amt| {
                println!("wrote {} bytes to {}", amt, addr)
            }).forget();

            Ok(())
        })
    });
    l.run(done).unwrap();
}
<commit_msg>Listening vs Listenering<commit_after>//! An echo server that just writes back everything that's written to it.

extern crate futures;
extern crate futures_io;
extern crate futures_mio;

use std::env;
use std::net::SocketAddr;

use futures::Future;
use futures_io::{copy, TaskIo};
use futures::stream::Stream;

fn main() {
    let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
    let addr = addr.parse::<SocketAddr>().unwrap();

    // Create the event loop that will drive this server
    let mut l = futures_mio::Loop::new().unwrap();

    // Create a TCP listener which will listen for incoming connections
    let server = l.handle().tcp_listen(&addr);

    let done = server.and_then(move |socket| {
        // Once we've got the TCP listener, inform that we have it
        println!("Listening on: {}", addr);

        // Pull out the stream of incoming connections and then for each new
        // one spin up a new task copying data. We put the `socket` into a
        // `TaskIo` structure which then allows us to `split` it into the read
        // and write halves of the socket.
        //
        // Finally we use the `io::copy` future to copy all data from the
        // reading half onto the writing half.
        socket.incoming().for_each(|(socket, addr)| {
            let io = TaskIo::new(socket);
            let pair = io.map(|io| io.split());
            let amt = pair.and_then(|(reader, writer)| {
                copy(reader, writer)
            });

            // Once all that is done we print out how much we wrote, and then
            // critically we *forget* this future which allows it to run
            // concurrently with other connections.
            amt.map(move |amt| {
                println!("wrote {} bytes to {}", amt, addr)
            }).forget();

            Ok(())
        })
    });
    l.run(done).unwrap();
}
<|endoftext|>


<commit_before>use std::io;
use std::io::Write;
use std::process::Command;
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use mdbook::utils;
use mdbook::config;
use get_book_dir;

// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
    SubCommand::with_name("init")
        .about("Create boilerplate structure and files in the directory")
        // the {n} denotes a newline which will properly aligned in all help messages
        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory \
                         when omitted)'")
        .arg_from_usage("--theme 'Copies the default theme into your source folder'")
        .arg_from_usage("--force 'skip confirmation prompts'")
}

// Init command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
    let book_dir = get_book_dir(args);
    let mut builder = MDBook::init(&book_dir);
    let mut config = config::Config::default();

    // If flag `--theme` is present, copy theme to src
    if args.is_present("theme") {
        config.set("output.html.theme", "src/theme")?;
        // Skip this if `--force` is present
        if !args.is_present("force") {
            // Print warning
            println!();
            println!(
                "Copying the default theme to {}",
                builder.config().book.src.display()
            );
            println!("This could potentially overwrite files already present in that directory.");
            print!("\nAre you sure you want to continue? (y/n) ");

            // Read answer from user and exit if it's not 'yes'
            if confirm() {
                builder.copy_theme(true);
            }
        } else {
            builder.copy_theme(true);
        }
    }

    println!("\nDo you want a .gitignore to be created? (y/n)");

    if confirm() {
        builder.create_gitignore(true);
    }

    config.book.title = request_book_title();

    if let Some(author) = get_author_name() {
        debug!("Obtained user name from gitconfig: {:?}", author);
        config.book.authors.push(author);
        builder.with_config(config);
    }

    builder.build()?;
    println!("\nAll done, no errors...");

    Ok(())
}

/// Obtains author name from git config file by running the `git config` command.
fn get_author_name() -> Option<String> {
    let output = Command::new("git")
        .args(&["config", "--get", "user.name"])
        .output()
        .ok()?;

    if output.status.success() {
        Some(String::from_utf8_lossy(&output.stdout).trim().to_owned())
    } else {
        None
    }
}

/// Request book title from user and return if provided.
fn request_book_title() -> Option<String> {
    println!("What title would you like to give the book? ");
    io::stdout().flush().unwrap();
    let mut resp = String::new();
    io::stdin().read_line(&mut resp).unwrap();
    let resp = resp.trim();
    if resp.is_empty() {
        None
    } else {
        Some(resp.into())
    }
}

// Simple function for user confirmation
fn confirm() -> bool {
    io::stdout().flush().unwrap();
    let mut s = String::new();
    io::stdin().read_line(&mut s).ok();
    match &*s.trim() {
        "Y" | "y" | "yes" | "Yes" => true,
        _ => false,
    }
}
<commit_msg>Fix warning<commit_after>use std::io;
use std::io::Write;
use std::process::Command;
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use mdbook::config;
use get_book_dir;

// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
    SubCommand::with_name("init")
        .about("Create boilerplate structure and files in the directory")
        // the {n} denotes a newline which will properly aligned in all help messages
        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory \
                         when omitted)'")
        .arg_from_usage("--theme 'Copies the default theme into your source folder'")
        .arg_from_usage("--force 'skip confirmation prompts'")
}

// Init command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
    let book_dir = get_book_dir(args);
    let mut builder = MDBook::init(&book_dir);
    let mut config = config::Config::default();

    // If flag `--theme` is present, copy theme to src
    if args.is_present("theme") {
        config.set("output.html.theme", "src/theme")?;
        // Skip this if `--force` is present
        if !args.is_present("force") {
            // Print warning
            println!();
            println!(
                "Copying the default theme to {}",
                builder.config().book.src.display()
            );
            println!("This could potentially overwrite files already present in that directory.");
            print!("\nAre you sure you want to continue? (y/n) ");

            // Read answer from user and exit if it's not 'yes'
            if confirm() {
                builder.copy_theme(true);
            }
        } else {
            builder.copy_theme(true);
        }
    }

    println!("\nDo you want a .gitignore to be created? (y/n)");

    if confirm() {
        builder.create_gitignore(true);
    }

    config.book.title = request_book_title();

    if let Some(author) = get_author_name() {
        debug!("Obtained user name from gitconfig: {:?}", author);
        config.book.authors.push(author);
        builder.with_config(config);
    }

    builder.build()?;
    println!("\nAll done, no errors...");

    Ok(())
}

/// Obtains author name from git config file by running the `git config` command.
fn get_author_name() -> Option<String> {
    let output = Command::new("git")
        .args(&["config", "--get", "user.name"])
        .output()
        .ok()?;

    if output.status.success() {
        Some(String::from_utf8_lossy(&output.stdout).trim().to_owned())
    } else {
        None
    }
}

/// Request book title from user and return if provided.
fn request_book_title() -> Option<String> {
    println!("What title would you like to give the book? ");
    io::stdout().flush().unwrap();
    let mut resp = String::new();
    io::stdin().read_line(&mut resp).unwrap();
    let resp = resp.trim();
    if resp.is_empty() {
        None
    } else {
        Some(resp.into())
    }
}

// Simple function for user confirmation
fn confirm() -> bool {
    io::stdout().flush().unwrap();
    let mut s = String::new();
    io::stdin().read_line(&mut s).ok();
    match &*s.trim() {
        "Y" | "y" | "yes" | "Yes" => true,
        _ => false,
    }
}
<|endoftext|>


<commit_before>#[macro_use]
pub mod macros;
pub mod arguments;
pub mod configuration;
pub mod engines;
pub mod error;
pub mod network;
#[cfg(feature = "file_type_checking")]
pub mod magic;

extern crate std;
extern crate toml;

pub use self::engines::bitbucket::Bitbucket;
pub use self::engines::gist::Gist;
pub use self::engines::hastebin::Hastebin;
pub use self::engines::pastebin::Pastebin;
pub use self::engines::pastie::Pastie;
pub use self::engines::sprunge::Sprunge;

use bins::error::*;
use bins::arguments::Arguments;
use bins::configuration::BinsConfiguration;
use bins::engines::Bin;
use hyper::Url;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::ops::Range;
use std::path::Path;

cfg_if! {
  if #[cfg(feature = "file_type_checking")] {
    fn check_magic(bins: &Bins, pastes: &[PasteFile]) -> Result<()> {
      use bins::magic::MagicWrapper;
      let magic = try!(MagicWrapper::new(0, true));
      let disallowed_types = bins.config.get_general_disallowed_file_types();
      if let Some(disallowed_types) = disallowed_types {
        for file in pastes {
          let magic_type = try!(magic.magic_buffer(file.data.as_bytes()));
          if disallowed_types.contains(&magic_type.to_lowercase()) {
            return Err(format!("{} is a disallowed type ({}). use --force to force upload",
              file.name, magic_type).into());
          }
        }
      }
      Ok(())
    }
  } else {
    fn check_magic(_: &Bins, _: &[PasteFile]) -> Result<()> {
      Ok(())
    }
  }
}

#[derive(Clone)]
pub struct PasteFile {
  pub name: String,
  pub data: String
}

impl PasteFile {
  fn new(name: String, data: String) -> Self {
    PasteFile {
      name: name,
      data: data
    }
  }
}

pub struct Bins {
  pub config: BinsConfiguration,
  pub arguments: Arguments
}

impl Bins {
  pub fn new(config: BinsConfiguration, arguments: Arguments) -> Self {
    Bins {
      config: config,
      arguments: arguments
    }
  }

  pub fn get_engine(&self) -> Result<&Box<Bin>> {
    let service = match self.arguments.service {
      Some(ref s) => s,
      None => return Err("no service was specified and no default service was set.".into()),
    };
    match engines::get_bin_by_name(service) {
      Some(engine) => Ok(engine),
      None => Err(format!("unknown service \"{}\"", service).into()),
    }
  }

  fn read_file<P: AsRef<Path>>(&self, p: P) -> Result<String> {
    let path = p.as_ref();
    let name = some_or_err!(path.to_str(), "file name was not valid unicode".into());
    if !path.exists() {
      return Err(format!("{} does not exist", name).into());
    }
    if !path.is_file() {
      return Err(format!("{} is not a file", name).into());
    }
    let mut file = match File::open(path) {
      Ok(f) => f,
      Err(e) => return Err(format!("could not open {}: {}", name, e).into()),
    };
    let mut s = String::new();
    if let Err(e) = file.read_to_string(&mut s) {
      return Err(format!("could not read {}: {}", name, e).into());
    }
    Ok(s)
  }

  fn read_file_to_pastefile<P: AsRef<Path>>(&self, p: P) -> Result<PasteFile> {
    let path = p.as_ref();
    match self.read_file(path) {
      Ok(s) => {
        let n = some_or_err!(path.file_name(), "not a valid file name".into());
        Ok(PasteFile::new(n.to_string_lossy().into_owned(), s))
      }
      Err(s) => Err(s),
    }
  }

  pub fn get_to_paste(&self) -> Result<Vec<PasteFile>> {
    let arguments = &self.arguments;
    let message = &arguments.message;
    let paste_files: Vec<PasteFile> = if message.is_some() {
      let name = arguments.name.clone().unwrap_or_else(|| String::from("message"));
      vec![PasteFile::new(name, message.clone().unwrap())]
    } else if !arguments.files.is_empty() {
      let files = arguments.files.clone();
      let results = files.iter()
        .map(|s| Path::new(s))
        .map(|p| {
          if !self.arguments.force {
            let metadata = match p.metadata() {
              Ok(m) => m,
              Err(e) => return Err(e.to_string().into()),
            };
            let size = metadata.len();
            let limit = try!(self.config.get_general_file_size_limit());
            if let Some(limit) = limit {
              if size > limit {
                return Err(format!("{} ({} bytes) was larger than the upload limit ({} bytes). use --force to force \
                                    upload",
                                   p.to_string_lossy(),
                                   size,
                                   limit)
                  .into());
              }
            }
          }
          Ok(p)
        })
        .map(|p| p.and_then(|f| self.read_file_to_pastefile(f)))
        .map(|r| r.map_err(|e| e.iter().map(|x| x.to_string()).collect::<Vec<_>>().join("\n")))
        .collect::<Vec<_>>();
      for res in results.iter().cloned() {
        if res.is_err() {
          return Err(res.err().unwrap().into());
        }
      }
      let mut pastes =
        results.iter().cloned().map(|r| r.unwrap()).filter(|p| !p.data.trim().is_empty()).collect::<Vec<_>>();
      self.handle_duplicate_file_names(&mut pastes);
      if !self.arguments.force {
        if let Some((name, pattern)) = self.check_for_disallowed_files(&pastes) {
          return Err(format!("\"{}\" is disallowed by the pattern \"{}\". use --force to force upload",
                             name,
                             pattern)
            .into());
        }
        try!(check_magic(self, &pastes));
      }
      pastes
    } else {
      let mut buffer = String::new();
      if let Err(e) = std::io::stdin().read_to_string(&mut buffer) {
        return Err(format!("error reading stdin: {}", e).into());
      }
      let name = arguments.name.clone().unwrap_or_else(|| String::from("stdin"));
      vec![PasteFile::new(name, buffer)]
    };
    if paste_files.iter().filter(|p| !p.data.trim().is_empty()).count() < 1 {
      return Err("no files (or all empty files) to paste".into());
    }
    Ok(paste_files)
  }

  fn handle_duplicate_file_names(&self, pastes: &mut Vec<PasteFile>) {
    let mut names_map: HashMap<String, i32> = HashMap::new();
    for mut paste in pastes {
      let name = paste.name.clone();
      if names_map.contains_key(&name) {
        let parts = name.rsplit('.');
        let (beginning, end) = if parts.clone().count() > 1 {
          let mut beginning_parts = parts.clone().skip(1).collect::<Vec<_>>();
          beginning_parts.reverse();
          let beginning = beginning_parts.join(".");
          let end = parts.take(1).next().map_or(String::new(), |s| String::from(".") + s);
          (beginning, end)
        } else {
          (name.clone(), String::from(""))
        };
        let number = names_map.entry(name.clone()).or_insert(1);
        paste.name = format!("{}_{}{}", beginning, number, end);
        *number += 1;
      }
      names_map.entry(name.clone()).or_insert(1);
    }
  }

  fn get_engine_for_url<'a>(&'a self, url: &'a Url) -> Result<&Box<Bin>> {
    let domain = some_or_err!(url.domain(), "input url had no domain".into());
    let engine = some_or_err!(engines::get_bin_by_domain(domain),
                              format!("could not find a bin for domain {}", domain).into());
    Ok(engine)
  }

  fn get_raw(&self, url_string: &str) -> Result<String> {
    let url = try!(network::parse_url(url_string.as_ref()));
    let url_clone = url.clone();
    let bin = try!(self.get_engine_for_url(&url_clone));
    if !bin.verify_url(&url_clone) {
      return Err(format!("invalid url for {}", bin.get_name()).into());
    }
    Ok(try!(bin.produce_raw_contents(self, &url)))
  }

  fn check_for_disallowed_files(&self, to_paste: &[PasteFile]) -> Option<(String, String)> {
    for pattern in self.config.get_general_disallowed_file_patterns().unwrap_or(&[]) {
      let pattern = match pattern.as_str() {
        Some(s) => s,
        None => continue,
      };
      for file in to_paste {
        if file.name.matches_pattern(pattern) {
          return Some((file.name.clone(), pattern.to_owned()));
        }
      }
    }
    None
  }

  pub fn get_output(&self) -> Result<String> {
    if let Some(ref input) = self.arguments.input {
      return self.get_raw(input);
    }
    let mut to_paste = try!(self.get_to_paste());
    let engine = try!(self.get_engine());
    let upload_url = if to_paste.len() > 1 {
      try!(engine.upload_all(self, to_paste))
    } else if to_paste.len() == 1 {
      let file = to_paste.pop().expect("len() == 1 but no element from pop()");
      try!(engine.upload_paste(self, file))
    } else {
      return Err("no files to upload".into());
    };
    Ok(upload_url.as_str().to_owned())
  }
}

#[derive(Clone)]
pub struct FlexibleRange {
  pub ranges: Vec<Range<usize>>
}

impl FlexibleRange {
  pub fn parse<S: Into<String>>(string: S) -> Result<FlexibleRange> {
    let string = string.into();
    let parts = string.split(',').map(|s| s.trim());
    let mut range: Vec<Range<usize>> = Vec::new();
    for part in parts {
      let bounds = part.split('-').map(|s| s.trim()).collect::<Vec<_>>();
      match bounds.len() {
        1 => {
          let num = bounds[0];
          if num.is_empty() {
            return Err("empty part in range".into());
          }
          let num = try!(num.parse::<usize>());
          range.push(num..num + 1);
        }
        2 => {
          let lower = bounds[0];
          let upper = bounds[1];
          if lower.is_empty() || upper.is_empty() {
            return Err("incomplete part in range".into());
          }
          let lower: usize = try!(lower.parse());
          let upper: usize = try!(upper.parse());
          let r: Range<usize> = if lower > upper {
            lower + 1..upper
          } else {
            lower..upper + 1
          };
          range.push(r);
        }
        _ => return Err("too many dashes in range".into())
      }
    }
    Ok(FlexibleRange { ranges: range })
  }
}

impl Iterator for FlexibleRange {
  type Item = usize;

  fn next(&mut self) -> Option<Self::Item> {
    if self.ranges.len() < 1 {
      return None;
    }
    let n = {
      let mut range = &mut self.ranges[0];
      if range.start > range.end {
        let normalized = range.end..range.start;
        let ret = normalized.rev().next();
        if ret.is_some() {
          range.start -= 1;
        }
        ret
      } else {
        range.next()
      }
    };
    if n.is_none() {
      self.ranges.remove(0);
      return self.next();
    }
    n
  }
}

trait MatchesPattern<'a> {
  fn matches_pattern(&self, pattern: &'a str) -> bool;
}

impl<'a> MatchesPattern<'a> for String {
  fn matches_pattern(&self, pattern: &'a str) -> bool {
    if pattern == "*" || pattern == self {
      return true;
    }
    if self.is_empty() {
      return false;
    }
    let pattern_first = match pattern.chars().next() {
      Some(f) => f,
      None => return false,
    };
    let string_first = match self.chars().next() {
      Some(f) => f,
      None => return false,
    };
    if pattern_first == string_first {
      return (&self[1..]).to_owned().matches_pattern(&pattern[1..]);
    }
    if pattern_first == '*' {
      return self.matches_pattern(&pattern[1..]) || (&self[1..]).to_owned().matches_pattern(pattern);
    }
    false
  }
}
<commit_msg>refactor(bins): use Path to handle duplicate file names<commit_after>#[macro_use]
pub mod macros;
pub mod arguments;
pub mod configuration;
pub mod engines;
pub mod error;
pub mod network;
#[cfg(feature = "file_type_checking")]
pub mod magic;

extern crate std;
extern crate toml;

pub use self::engines::bitbucket::Bitbucket;
pub use self::engines::gist::Gist;
pub use self::engines::hastebin::Hastebin;
pub use self::engines::pastebin::Pastebin;
pub use self::engines::pastie::Pastie;
pub use self::engines::sprunge::Sprunge;

use bins::error::*;
use bins::arguments::Arguments;
use bins::configuration::BinsConfiguration;
use bins::engines::Bin;
use hyper::Url;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::ops::Range;
use std::path::Path;

cfg_if! {
  if #[cfg(feature = "file_type_checking")] {
    fn check_magic(bins: &Bins, pastes: &[PasteFile]) -> Result<()> {
      use bins::magic::MagicWrapper;
      let magic = try!(MagicWrapper::new(0, true));
      let disallowed_types = bins.config.get_general_disallowed_file_types();
      if let Some(disallowed_types) = disallowed_types {
        for file in pastes {
          let magic_type = try!(magic.magic_buffer(file.data.as_bytes()));
          if disallowed_types.contains(&magic_type.to_lowercase()) {
            return Err(format!("{} is a disallowed type ({}). use --force to force upload",
              file.name, magic_type).into());
          }
        }
      }
      Ok(())
    }
  } else {
    fn check_magic(_: &Bins, _: &[PasteFile]) -> Result<()> {
      Ok(())
    }
  }
}

#[derive(Clone)]
pub struct PasteFile {
  pub name: String,
  pub data: String
}

impl PasteFile {
  fn new(name: String, data: String) -> Self {
    PasteFile {
      name: name,
      data: data
    }
  }
}

pub struct Bins {
  pub config: BinsConfiguration,
  pub arguments: Arguments
}

impl Bins {
  pub fn new(config: BinsConfiguration, arguments: Arguments) -> Self {
    Bins {
      config: config,
      arguments: arguments
    }
  }

  pub fn get_engine(&self) -> Result<&Box<Bin>> {
    let service = match self.arguments.service {
      Some(ref s) => s,
      None => return Err("no service was specified and no default service was set.".into()),
    };
    match engines::get_bin_by_name(service) {
      Some(engine) => Ok(engine),
      None => Err(format!("unknown service \"{}\"", service).into()),
    }
  }

  fn read_file<P: AsRef<Path>>(&self, p: P) -> Result<String> {
    let path = p.as_ref();
    let name = some_or_err!(path.to_str(), "file name was not valid unicode".into());
    if !path.exists() {
      return Err(format!("{} does not exist", name).into());
    }
    if !path.is_file() {
      return Err(format!("{} is not a file", name).into());
    }
    let mut file = match File::open(path) {
      Ok(f) => f,
      Err(e) => return Err(format!("could not open {}: {}", name, e).into()),
    };
    let mut s = String::new();
    if let Err(e) = file.read_to_string(&mut s) {
      return Err(format!("could not read {}: {}", name, e).into());
    }
    Ok(s)
  }

  fn read_file_to_pastefile<P: AsRef<Path>>(&self, p: P) -> Result<PasteFile> {
    let path = p.as_ref();
    match self.read_file(path) {
      Ok(s) => {
        let n = some_or_err!(path.file_name(), "not a valid file name".into());
        Ok(PasteFile::new(n.to_string_lossy().into_owned(), s))
      }
      Err(s) => Err(s),
    }
  }

  pub fn get_to_paste(&self) -> Result<Vec<PasteFile>> {
    let arguments = &self.arguments;
    let message = &arguments.message;
    let paste_files: Vec<PasteFile> = if message.is_some() {
      let name = arguments.name.clone().unwrap_or_else(|| String::from("message"));
      vec![PasteFile::new(name, message.clone().unwrap())]
    } else if !arguments.files.is_empty() {
      let files = arguments.files.clone();
      let results = files.iter()
        .map(|s| Path::new(s))
        .map(|p| {
          if !self.arguments.force {
            let metadata = match p.metadata() {
              Ok(m) => m,
              Err(e) => return Err(e.to_string().into()),
            };
            let size = metadata.len();
            let limit = try!(self.config.get_general_file_size_limit());
            if let Some(limit) = limit {
              if size > limit {
                return Err(format!("{} ({} bytes) was larger than the upload limit ({} bytes). use --force to force \
                                    upload",
                                   p.to_string_lossy(),
                                   size,
                                   limit)
                  .into());
              }
            }
          }
          Ok(p)
        })
        .map(|p| p.and_then(|f| self.read_file_to_pastefile(f)))
        .map(|r| r.map_err(|e| e.iter().map(|x| x.to_string()).collect::<Vec<_>>().join("\n")))
        .collect::<Vec<_>>();
      for res in results.iter().cloned() {
        if res.is_err() {
          return Err(res.err().unwrap().into());
        }
      }
      let mut pastes =
        results.iter().cloned().map(|r| r.unwrap()).filter(|p| !p.data.trim().is_empty()).collect::<Vec<_>>();
      self.handle_duplicate_file_names(&mut pastes);
      if !self.arguments.force {
        if let Some((name, pattern)) = self.check_for_disallowed_files(&pastes) {
          return Err(format!("\"{}\" is disallowed by the pattern \"{}\". use --force to force upload",
                             name,
                             pattern)
            .into());
        }
        try!(check_magic(self, &pastes));
      }
      pastes
    } else {
      let mut buffer = String::new();
      if let Err(e) = std::io::stdin().read_to_string(&mut buffer) {
        return Err(format!("error reading stdin: {}", e).into());
      }
      let name = arguments.name.clone().unwrap_or_else(|| String::from("stdin"));
      vec![PasteFile::new(name, buffer)]
    };
    if paste_files.iter().filter(|p| !p.data.trim().is_empty()).count() < 1 {
      return Err("no files (or all empty files) to paste".into());
    }
    Ok(paste_files)
  }

  fn handle_duplicate_file_names(&self, pastes: &mut Vec<PasteFile>) {
    let mut names_map: HashMap<String, i32> = HashMap::new();
    for mut paste in pastes {
      let name = paste.name.clone();
      if names_map.contains_key(&name) {
        let (beginning, end) = {
          let path = Path::new(&name);
          let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
          let extension = path.extension().and_then(|s| s.to_str()).map_or_else(String::new, |s| String::from(".") + s);
          (stem, extension)
        };
        let number = names_map.entry(name.clone()).or_insert(1);
        paste.name = format!("{}_{}{}", beginning, number, end);
        *number += 1;
      }
      names_map.entry(name.clone()).or_insert(1);
    }
  }

  fn get_engine_for_url<'a>(&'a self, url: &'a Url) -> Result<&Box<Bin>> {
    let domain = some_or_err!(url.domain(), "input url had no domain".into());
    let engine = some_or_err!(engines::get_bin_by_domain(domain),
                              format!("could not find a bin for domain {}", domain).into());
    Ok(engine)
  }

  fn get_raw(&self, url_string: &str) -> Result<String> {
    let url = try!(network::parse_url(url_string.as_ref()));
    let url_clone = url.clone();
    let bin = try!(self.get_engine_for_url(&url_clone));
    if !bin.verify_url(&url_clone) {
      return Err(format!("invalid url for {}", bin.get_name()).into());
    }
    Ok(try!(bin.produce_raw_contents(self, &url)))
  }

  fn check_for_disallowed_files(&self, to_paste: &[PasteFile]) -> Option<(String, String)> {
    for pattern in self.config.get_general_disallowed_file_patterns().unwrap_or(&[]) {
      let pattern = match pattern.as_str() {
        Some(s) => s,
        None => continue,
      };
      for file in to_paste {
        if file.name.matches_pattern(pattern) {
          return Some((file.name.clone(), pattern.to_owned()));
        }
      }
    }
    None
  }

  pub fn get_output(&self) -> Result<String> {
    if let Some(ref input) = self.arguments.input {
      return self.get_raw(input);
    }
    let mut to_paste = try!(self.get_to_paste());
    let engine = try!(self.get_engine());
    let upload_url = if to_paste.len() > 1 {
      try!(engine.upload_all(self, to_paste))
    } else if to_paste.len() == 1 {
      let file = to_paste.pop().expect("len() == 1 but no element from pop()");
      try!(engine.upload_paste(self, file))
    } else {
      return Err("no files to upload".into());
    };
    Ok(upload_url.as_str().to_owned())
  }
}

#[derive(Clone)]
pub struct FlexibleRange {
  pub ranges: Vec<Range<usize>>
}

impl FlexibleRange {
  pub fn parse<S: Into<String>>(string: S) -> Result<FlexibleRange> {
    let string = string.into();
    let parts = string.split(',').map(|s| s.trim());
    let mut range: Vec<Range<usize>> = Vec::new();
    for part in parts {
      let bounds = part.split('-').map(|s| s.trim()).collect::<Vec<_>>();
      match bounds.len() {
        1 => {
          let num = bounds[0];
          if num.is_empty() {
            return Err("empty part in range".into());
          }
          let num = try!(num.parse::<usize>());
          range.push(num..num + 1);
        }
        2 => {
          let lower = bounds[0];
          let upper = bounds[1];
          if lower.is_empty() || upper.is_empty() {
            return Err("incomplete part in range".into());
          }
          let lower: usize = try!(lower.parse());
          let upper: usize = try!(upper.parse());
          let r: Range<usize> = if lower > upper {
            lower + 1..upper
          } else {
            lower..upper + 1
          };
          range.push(r);
        }
        _ => return Err("too many dashes in range".into())
      }
    }
    Ok(FlexibleRange { ranges: range })
  }
}

impl Iterator for FlexibleRange {
  type Item = usize;

  fn next(&mut self) -> Option<Self::Item> {
    if self.ranges.len() < 1 {
      return None;
    }
    let n = {
      let mut range = &mut self.ranges[0];
      if range.start > range.end {
        let normalized = range.end..range.start;
        let ret = normalized.rev().next();
        if ret.is_some() {
          range.start -= 1;
        }
        ret
      } else {
        range.next()
      }
    };
    if n.is_none() {
      self.ranges.remove(0);
      return self.next();
    }
    n
  }
}

trait MatchesPattern<'a> {
  fn matches_pattern(&self, pattern: &'a str) -> bool;
}

impl<'a> MatchesPattern<'a> for String {
  fn matches_pattern(&self, pattern: &'a str) -> bool {
    if pattern == "*" || pattern == self {
      return true;
    }
    if self.is_empty() {
      return false;
    }
    let pattern_first = match pattern.chars().next() {
      Some(f) => f,
      None => return false,
    };
    let string_first = match self.chars().next() {
      Some(f) => f,
      None => return false,
    };
    if pattern_first == string_first {
      return (&self[1..]).to_owned().matches_pattern(&pattern[1..]);
    }
    if pattern_first == '*' {
      return self.matches_pattern(&pattern[1..]) || (&self[1..]).to_owned().matches_pattern(pattern);
    }
    false
  }
}
<|endoftext|>


<commit_before>//! The internal representation of a book and infrastructure for loading it from
//! disk and building it.
//!
//! For examples on using `MDBook`, consult the [top-level documentation][1].
//!
//! [1]: ../index.html

#![deny(missing_docs)]

mod summary;
mod book;
mod init;

pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
pub use self::init::BookBuilder;

use std::path::PathBuf;
use std::io::Write;
use std::process::Command;
use tempdir::TempDir;
use toml::Value;

use utils;
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
use preprocess::{self, Preprocessor};
use errors::*;

use config::Config;

/// The object used to manage and build a book.
pub struct MDBook {
    /// The book's root directory.
    pub root: PathBuf,
    /// The configuration used to tweak now a book is built.
    pub config: Config,
    /// A representation of the book's contents in memory.
    pub book: Book,
    renderers: Vec<Box<Renderer>>,

    /// The URL used for live reloading when serving up the book.
    pub livereload: Option<String>,

    /// List of pre-processors to be run on the book
    preprocessors: Vec<Box<Preprocessor>>
}

impl MDBook {
    /// Load a book from its root directory on disk.
    pub fn load<P: Into<PathBuf>>(book_root: P) -> Result<MDBook> {
        let book_root = book_root.into();
        let config_location = book_root.join("book.toml");

        // the book.json file is no longer used, so we should emit a warning to
        // let people know to migrate to book.toml
        if book_root.join("book.json").exists() {
            warn!("It appears you are still using book.json for configuration.");
            warn!("This format is no longer used, so you should migrate to the");
            warn!("book.toml format.");
            warn!("Check the user guide for migration information:");
            warn!("\thttps://rust-lang-nursery.github.io/mdBook/format/config.html");
        }

        let mut config = if config_location.exists() {
            debug!("[*] Loading config from {}", config_location.display());
            Config::from_disk(&config_location)?
        } else {
            Config::default()
        };

        config.update_from_env();

        if log_enabled!(::log::Level::Trace) {
            for line in format!("Config: {:#?}", config).lines() {
                trace!("{}", line);
            }
        }

        MDBook::load_with_config(book_root, config)
    }

    /// Load a book from its root directory using a custom config.
    pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
        let root = book_root.into();

        let src_dir = root.join(&config.book.src);
        let book = book::load_book(&src_dir, &config.build)?;
        let livereload = None;

        let renderers = determine_renderers(&config);

        let preprocessors = determine_preprocessors(&config);

        Ok(MDBook {
            root,
            config,
            book,
            renderers,
            livereload,
            preprocessors,
        })
    }

    /// Returns a flat depth-first iterator over the elements of the book,
    /// it returns an [BookItem enum](bookitem.html):
    /// `(section: String, bookitem: &BookItem)`
    ///
    /// ```no_run
    /// # extern crate mdbook;
    /// # use mdbook::MDBook;
    /// # use mdbook::book::BookItem;
    /// # #[allow(unused_variables)]
    /// # fn main() {
    /// # let book = MDBook::load("mybook").unwrap();
    /// for item in book.iter() {
    ///     match *item {
    ///         BookItem::Chapter(ref chapter) => {},
    ///         BookItem::Separator => {},
    ///     }
    /// }
    ///
    /// // would print something like this:
    /// // 1. Chapter 1
    /// // 1.1 Sub Chapter
    /// // 1.2 Sub Chapter
    /// // 2. Chapter 2
    /// //
    /// // etc.
    /// # }
    /// ```
    pub fn iter(&self) -> BookItems {
        self.book.iter()
    }

    /// `init()` gives you a `BookBuilder` which you can use to setup a new book
    /// and its accompanying directory structure.
    ///
    /// The `BookBuilder` creates some boilerplate files and directories to get
    /// you started with your book.
    ///
    /// ```text
    /// book-test/
    /// ├── book
    /// └── src
    ///     ├── chapter_1.md
    ///     └── SUMMARY.md
    /// ```
    ///
    /// It uses the path provided as the root directory for your book, then adds
    /// in a `src/` directory containing a `SUMMARY.md` and `chapter_1.md` file
    /// to get you started.
    pub fn init<P: Into<PathBuf>>(book_root: P) -> BookBuilder {
        BookBuilder::new(book_root)
    }

    /// Tells the renderer to build our book and put it in the build directory.
    pub fn build(&self) -> Result<()> {
        debug!("[fn]: build");

        for renderer in &self.renderers {
            self.run_renderer(renderer.as_ref())?;
        }

        Ok(())
    }

    fn run_renderer(&self, renderer: &Renderer) -> Result<()> {
        let name = renderer.name();
        let build_dir = self.build_dir_for(name);
        if build_dir.exists() {
            debug!(
                "Cleaning build dir for the \"{}\" renderer ({})",
                name,
                build_dir.display()
            );

            utils::fs::remove_dir_content(&build_dir)
                .chain_err(|| "Unable to clear output directory")?;
        }

        let render_context = RenderContext::new(
            self.root.clone(),
            self.book.clone(),
            self.config.clone(),
            build_dir,
        );

        renderer
            .render(&render_context)
            .chain_err(|| "Rendering failed")
    }

    /// You can change the default renderer to another one by using this method.
    /// The only requirement is for your renderer to implement the [Renderer
    /// trait](../../renderer/renderer/trait.Renderer.html)
    pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
        self.renderers.push(Box::new(renderer));
        self
    }

    /// You can add a new preprocessor by using this method.
    /// The only requirement is for your renderer to implement the Preprocessor trait.
    pub fn with_preprecessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
        self.preprocessors.push(Box::new(preprocessor));
        self
    }

    /// Run `rustdoc` tests on the book, linking against the provided libraries.
    pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
        let library_args: Vec<&str> = (0..library_paths.len())
            .map(|_| "-L")
            .zip(library_paths.into_iter())
            .flat_map(|x| vec![x.0, x.1])
            .collect();

        let temp_dir = TempDir::new("mdbook")?;

        let replace_all_preprocessor = preprocess::links::ReplaceAllPreprocessor {
            src_dir: self.source_dir(),
        };

        replace_all_preprocessor.run(&mut self.book)?;

        for item in self.iter() {
            if let BookItem::Chapter(ref ch) = *item {
                if !ch.path.as_os_str().is_empty() {
                    let path = self.source_dir().join(&ch.path);
                    println!("[*]: Testing file: {:?}", path);
                    let content = utils::fs::file_to_string(&path)?;

                    // write preprocessed file to tempdir
                    let path = temp_dir.path().join(&ch.path);
                    let mut tmpf = utils::fs::create_file(&path)?;
                    tmpf.write_all(content.as_bytes())?;

                    let output = Command::new("rustdoc")
                        .arg(&path)
                        .arg("--test")
                        .args(&library_args)
                        .output()?;

                    if !output.status.success() {
                        bail!(ErrorKind::Subprocess(
                            "Rustdoc returned an error".to_string(),
                            output
                        ));
                    }
                }
            }
        }
        Ok(())
    }

    /// The logic for determining where a backend should put its build
    /// artefacts.
    ///
    /// If there is only 1 renderer, put it in the directory pointed to by the
    /// `build.build_dir` key in `Config`. If there is more than one then the
    /// renderer gets its own directory within the main build dir.
    ///
    /// i.e. If there were only one renderer (in this case, the HTML renderer):
    ///
    /// - build/
    ///   - index.html
    ///   - ...
    ///
    /// Otherwise if there are multiple:
    ///
    /// - build/
    ///   - epub/
    ///     - my_awesome_book.epub
    ///   - html/
    ///     - index.html
    ///     - ...
    ///   - latex/
    ///     - my_awesome_book.tex
    ///
    pub fn build_dir_for(&self, backend_name: &str) -> PathBuf {
        let build_dir = self.root.join(&self.config.build.build_dir);

        if self.renderers.len() <= 1 {
            build_dir
        } else {
            build_dir.join(backend_name)
        }
    }

    /// Get the directory containing this book's source files.
    pub fn source_dir(&self) -> PathBuf {
        self.root.join(&self.config.book.src)
    }

    // FIXME: This really belongs as part of the `HtmlConfig`.
    #[doc(hidden)]
    pub fn theme_dir(&self) -> PathBuf {
        match self.config.html_config().and_then(|h| h.theme) {
            Some(d) => self.root.join(d),
            None => self.root.join("theme"),
        }
    }
}

/// Look at the `Config` and try to figure out what renderers to use.
fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
    let mut renderers: Vec<Box<Renderer>> = Vec::new();

    if let Some(output_table) = config.get("output").and_then(|o| o.as_table()) {
        for (key, table) in output_table.iter() {
            // the "html" backend has its own Renderer
            if key == "html" {
                renderers.push(Box::new(HtmlHandlebars::new()));
            } else {
                let renderer = interpret_custom_renderer(key, table);
                renderers.push(renderer);
            }
        }
    }

    // if we couldn't find anything, add the HTML renderer as a default
    if renderers.is_empty() {
        renderers.push(Box::new(HtmlHandlebars::new()));
    }

    renderers
}

/// Look at the `Config` and try to figure out what preprocessors to run.
fn determine_preprocessors(config: &Config) -> Vec<Box<Preprocessor>> {
    let mut preprocessors: Vec<Box<Preprocessor>> = Vec::new();

    preprocessors
}

fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
    // look for the `command` field, falling back to using the key
    // prepended by "mdbook-"
    let table_dot_command = table
        .get("command")
        .and_then(|c| c.as_str())
        .map(|s| s.to_string());

    let command = table_dot_command.unwrap_or_else(|| format!("mdbook-{}", key));

    Box::new(CmdRenderer::new(key.to_string(), command.to_string()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use toml::value::{Table, Value};

    #[test]
    fn config_defaults_to_html_renderer_if_empty() {
        let cfg = Config::default();

        // make sure we haven't got anything in the `output` table
        assert!(cfg.get("output").is_none());

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "html");
    }

    #[test]
    fn add_a_random_renderer_to_the_config() {
        let mut cfg = Config::default();
        cfg.set("output.random", Table::new()).unwrap();

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "random");
    }

    #[test]
    fn add_a_random_renderer_with_custom_command_to_the_config() {
        let mut cfg = Config::default();

        let mut table = Table::new();
        table.insert("command".to_string(), Value::String("false".to_string()));
        cfg.set("output.random", table).unwrap();

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "random");
    }
}
<commit_msg>Start determining preprocessors<commit_after>//! The internal representation of a book and infrastructure for loading it from
//! disk and building it.
//!
//! For examples on using `MDBook`, consult the [top-level documentation][1].
//!
//! [1]: ../index.html

#![deny(missing_docs)]

mod summary;
mod book;
mod init;

pub use self::book::{load_book, Book, BookItem, BookItems, Chapter};
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
pub use self::init::BookBuilder;

use std::path::PathBuf;
use std::io::Write;
use std::process::Command;
use tempdir::TempDir;
use toml::Value;

use utils;
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
use preprocess::{self, Preprocessor};
use errors::*;

use config::Config;

/// The object used to manage and build a book.
pub struct MDBook {
    /// The book's root directory.
    pub root: PathBuf,
    /// The configuration used to tweak now a book is built.
    pub config: Config,
    /// A representation of the book's contents in memory.
    pub book: Book,
    renderers: Vec<Box<Renderer>>,

    /// The URL used for live reloading when serving up the book.
    pub livereload: Option<String>,

    /// List of pre-processors to be run on the book
    preprocessors: Vec<Box<Preprocessor>>
}

impl MDBook {
    /// Load a book from its root directory on disk.
    pub fn load<P: Into<PathBuf>>(book_root: P) -> Result<MDBook> {
        let book_root = book_root.into();
        let config_location = book_root.join("book.toml");

        // the book.json file is no longer used, so we should emit a warning to
        // let people know to migrate to book.toml
        if book_root.join("book.json").exists() {
            warn!("It appears you are still using book.json for configuration.");
            warn!("This format is no longer used, so you should migrate to the");
            warn!("book.toml format.");
            warn!("Check the user guide for migration information:");
            warn!("\thttps://rust-lang-nursery.github.io/mdBook/format/config.html");
        }

        let mut config = if config_location.exists() {
            debug!("[*] Loading config from {}", config_location.display());
            Config::from_disk(&config_location)?
        } else {
            Config::default()
        };

        config.update_from_env();

        if log_enabled!(::log::Level::Trace) {
            for line in format!("Config: {:#?}", config).lines() {
                trace!("{}", line);
            }
        }

        MDBook::load_with_config(book_root, config)
    }

    /// Load a book from its root directory using a custom config.
    pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
        let root = book_root.into();

        let src_dir = root.join(&config.book.src);
        let book = book::load_book(&src_dir, &config.build)?;
        let livereload = None;

        let renderers = determine_renderers(&config);

        let mut md_book = MDBook {
            root,
            config,
            book,
            renderers,
            livereload,
            preprocessors: vec![],
        };

        let preprocessors = determine_preprocessors(&md_book);
        md_book.preprocessors = preprocessors;

        Ok(md_book)
    }

    /// Returns a flat depth-first iterator over the elements of the book,
    /// it returns an [BookItem enum](bookitem.html):
    /// `(section: String, bookitem: &BookItem)`
    ///
    /// ```no_run
    /// # extern crate mdbook;
    /// # use mdbook::MDBook;
    /// # use mdbook::book::BookItem;
    /// # #[allow(unused_variables)]
    /// # fn main() {
    /// # let book = MDBook::load("mybook").unwrap();
    /// for item in book.iter() {
    ///     match *item {
    ///         BookItem::Chapter(ref chapter) => {},
    ///         BookItem::Separator => {},
    ///     }
    /// }
    ///
    /// // would print something like this:
    /// // 1. Chapter 1
    /// // 1.1 Sub Chapter
    /// // 1.2 Sub Chapter
    /// // 2. Chapter 2
    /// //
    /// // etc.
    /// # }
    /// ```
    pub fn iter(&self) -> BookItems {
        self.book.iter()
    }

    /// `init()` gives you a `BookBuilder` which you can use to setup a new book
    /// and its accompanying directory structure.
    ///
    /// The `BookBuilder` creates some boilerplate files and directories to get
    /// you started with your book.
    ///
    /// ```text
    /// book-test/
    /// ├── book
    /// └── src
    ///     ├── chapter_1.md
    ///     └── SUMMARY.md
    /// ```
    ///
    /// It uses the path provided as the root directory for your book, then adds
    /// in a `src/` directory containing a `SUMMARY.md` and `chapter_1.md` file
    /// to get you started.
    pub fn init<P: Into<PathBuf>>(book_root: P) -> BookBuilder {
        BookBuilder::new(book_root)
    }

    /// Tells the renderer to build our book and put it in the build directory.
    pub fn build(&self) -> Result<()> {
        debug!("[fn]: build");

        for renderer in &self.renderers {
            self.run_renderer(renderer.as_ref())?;
        }

        Ok(())
    }

    fn run_renderer(&self, renderer: &Renderer) -> Result<()> {
        let name = renderer.name();
        let build_dir = self.build_dir_for(name);
        if build_dir.exists() {
            debug!(
                "Cleaning build dir for the \"{}\" renderer ({})",
                name,
                build_dir.display()
            );

            utils::fs::remove_dir_content(&build_dir)
                .chain_err(|| "Unable to clear output directory")?;
        }

        let render_context = RenderContext::new(
            self.root.clone(),
            self.book.clone(),
            self.config.clone(),
            build_dir,
        );

        renderer
            .render(&render_context)
            .chain_err(|| "Rendering failed")
    }

    /// You can change the default renderer to another one by using this method.
    /// The only requirement is for your renderer to implement the [Renderer
    /// trait](../../renderer/renderer/trait.Renderer.html)
    pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
        self.renderers.push(Box::new(renderer));
        self
    }

    /// You can add a new preprocessor by using this method.
    /// The only requirement is for your renderer to implement the Preprocessor trait.
    pub fn with_preprecessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
        self.preprocessors.push(Box::new(preprocessor));
        self
    }

    /// Run `rustdoc` tests on the book, linking against the provided libraries.
    pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
        let library_args: Vec<&str> = (0..library_paths.len())
            .map(|_| "-L")
            .zip(library_paths.into_iter())
            .flat_map(|x| vec![x.0, x.1])
            .collect();

        let temp_dir = TempDir::new("mdbook")?;

        let replace_all_preprocessor = preprocess::links::ReplaceAllPreprocessor {
            src_dir: self.source_dir(),
        };

        replace_all_preprocessor.run(&mut self.book)?;

        for item in self.iter() {
            if let BookItem::Chapter(ref ch) = *item {
                if !ch.path.as_os_str().is_empty() {
                    let path = self.source_dir().join(&ch.path);
                    println!("[*]: Testing file: {:?}", path);
                    let content = utils::fs::file_to_string(&path)?;

                    // write preprocessed file to tempdir
                    let path = temp_dir.path().join(&ch.path);
                    let mut tmpf = utils::fs::create_file(&path)?;
                    tmpf.write_all(content.as_bytes())?;

                    let output = Command::new("rustdoc")
                        .arg(&path)
                        .arg("--test")
                        .args(&library_args)
                        .output()?;

                    if !output.status.success() {
                        bail!(ErrorKind::Subprocess(
                            "Rustdoc returned an error".to_string(),
                            output
                        ));
                    }
                }
            }
        }
        Ok(())
    }

    /// The logic for determining where a backend should put its build
    /// artefacts.
    ///
    /// If there is only 1 renderer, put it in the directory pointed to by the
    /// `build.build_dir` key in `Config`. If there is more than one then the
    /// renderer gets its own directory within the main build dir.
    ///
    /// i.e. If there were only one renderer (in this case, the HTML renderer):
    ///
    /// - build/
    ///   - index.html
    ///   - ...
    ///
    /// Otherwise if there are multiple:
    ///
    /// - build/
    ///   - epub/
    ///     - my_awesome_book.epub
    ///   - html/
    ///     - index.html
    ///     - ...
    ///   - latex/
    ///     - my_awesome_book.tex
    ///
    pub fn build_dir_for(&self, backend_name: &str) -> PathBuf {
        let build_dir = self.root.join(&self.config.build.build_dir);

        if self.renderers.len() <= 1 {
            build_dir
        } else {
            build_dir.join(backend_name)
        }
    }

    /// Get the directory containing this book's source files.
    pub fn source_dir(&self) -> PathBuf {
        self.root.join(&self.config.book.src)
    }

    // FIXME: This really belongs as part of the `HtmlConfig`.
    #[doc(hidden)]
    pub fn theme_dir(&self) -> PathBuf {
        match self.config.html_config().and_then(|h| h.theme) {
            Some(d) => self.root.join(d),
            None => self.root.join("theme"),
        }
    }
}

/// Look at the `Config` and try to figure out what renderers to use.
fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
    let mut renderers: Vec<Box<Renderer>> = Vec::new();

    if let Some(output_table) = config.get("output").and_then(|o| o.as_table()) {
        for (key, table) in output_table.iter() {
            // the "html" backend has its own Renderer
            if key == "html" {
                renderers.push(Box::new(HtmlHandlebars::new()));
            } else {
                let renderer = interpret_custom_renderer(key, table);
                renderers.push(renderer);
            }
        }
    }

    // if we couldn't find anything, add the HTML renderer as a default
    if renderers.is_empty() {
        renderers.push(Box::new(HtmlHandlebars::new()));
    }

    renderers
}

/// Look at the `MDBook` and try to figure out what preprocessors to run.
fn determine_preprocessors(md_book: &MDBook) -> Vec<Box<Preprocessor>> {
    let mut preprocessors: Vec<Box<Preprocessor>> = Vec::new();

    if let Some(preprocess_array) = md_book.config.get("pre_process").and_then(|o| o.as_array()) {
        for key in preprocess_array.iter() {
            if key.as_str().map_or(false, |key| key == "links") {
                let preprocessor = preprocess::links::ReplaceAllPreprocessor {
                    src_dir: md_book.source_dir(),
                };

                preprocessors.push(Box::new(preprocessor))
            }
        }
    }

    preprocessors
}

fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
    // look for the `command` field, falling back to using the key
    // prepended by "mdbook-"
    let table_dot_command = table
        .get("command")
        .and_then(|c| c.as_str())
        .map(|s| s.to_string());

    let command = table_dot_command.unwrap_or_else(|| format!("mdbook-{}", key));

    Box::new(CmdRenderer::new(key.to_string(), command.to_string()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use toml::value::{Table, Value};

    #[test]
    fn config_defaults_to_html_renderer_if_empty() {
        let cfg = Config::default();

        // make sure we haven't got anything in the `output` table
        assert!(cfg.get("output").is_none());

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "html");
    }

    #[test]
    fn add_a_random_renderer_to_the_config() {
        let mut cfg = Config::default();
        cfg.set("output.random", Table::new()).unwrap();

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "random");
    }

    #[test]
    fn add_a_random_renderer_with_custom_command_to_the_config() {
        let mut cfg = Config::default();

        let mut table = Table::new();
        table.insert("command".to_string(), Value::String("false".to_string()));
        cfg.set("output.random", table).unwrap();

        let got = determine_renderers(&cfg);

        assert_eq!(got.len(), 1);
        assert_eq!(got[0].name(), "random");
    }
}
<|endoftext|>


<commit_before>#![recursion_limit = "1024"]
#[macro_use]
extern crate quote;
extern crate proc_macro;

use proc_macro::TokenStream;
use std::{env, path::Path};
use syn::{export::TokenStream2, Data, DeriveInput, Fields, Lit, Meta};

#[cfg(all(debug_assertions, not(feature = "debug-embed")))]
fn generate_assets(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
  quote! {
      impl #ident {
          pub fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
              use std::fs::File;
              use std::io::Read;
              use std::path::Path;

              let file_path = Path::new(#folder_path).join(file_path);
              let mut file = match File::open(file_path) {
                  Ok(mut file) => file,
                  Err(_e) => {
                      return None
                  }
              };
              let mut data: Vec<u8> = Vec::new();
              match file.read_to_end(&mut data) {
                  Ok(_) => Some(std::borrow::Cow::from(data)),
                  Err(_e) =>  {
                      return None
                  }
              }
          }

          pub fn iter() -> impl Iterator<Item = std::borrow::Cow<'static, str>> {
              use std::path::Path;
              rust_embed::utils::get_files(String::from(#folder_path)).map(|e| std::borrow::Cow::from(e.rel_path))
          }
      }
      impl rust_embed::RustEmbed for #ident {
        fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
          #ident::get(file_path)
        }
        fn iter() -> rust_embed::Filenames {
          // the return type of iter() is unnamable, so we have to box it
          rust_embed::Filenames::Dynamic(Box::new(#ident::iter()))
        }
      }
  }
}

#[cfg(all(not(feature = "compression"), any(not(debug_assertions), feature = "debug-embed")))]
fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
  quote! {
    #rel_path => {
        let bytes = &include_bytes!(#full_canonical_path)[..];
        Some(std::borrow::Cow::from(bytes))
    },
  }
}

#[cfg(all(feature = "compression", any(not(debug_assertions), feature = "debug-embed")))]
fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
  quote! {
    #rel_path => {
        rust_embed::flate!(static FILE: [u8] from #full_canonical_path);

        let bytes = &FILE[..];
        Some(std::borrow::Cow::from(bytes))
    },
  }
}

#[cfg(any(not(debug_assertions), feature = "debug-embed"))]
fn generate_assets(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
  extern crate rust_embed_utils;

  let mut match_values = Vec::<TokenStream2>::new();
  let mut list_values = Vec::<String>::new();

  for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in rust_embed_utils::get_files(folder_path) {
    match_values.push(embed_file(&rel_path, &full_canonical_path));
    list_values.push(rel_path);
  }

  let array_len = list_values.len();

  quote! {
      impl #ident {
          pub fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
              match file_path {
                  #(#match_values)*
                  _ => None,
              }
          }

          fn names() -> std::slice::Iter<'static, &'static str> {
            const items: [&str; #array_len] = [#(#list_values),*];
            items.iter()
          }
          pub fn iter() -> impl Iterator<Item = std::borrow::Cow<'static, str>> {
              Self::names().map(|x| std::borrow::Cow::from(*x))
          }
      }
      impl rust_embed::RustEmbed for #ident {
        fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
          #ident::get(file_path)
        }
        fn iter() -> rust_embed::Filenames {
          rust_embed::Filenames::Embedded(#ident::names())
        }
      }
  }
}

fn impl_rust_embed(ast: &syn::DeriveInput) -> TokenStream2 {
  match ast.data {
    Data::Struct(ref data) => match data.fields {
      Fields::Unit => {}
      _ => panic!("RustEmbed can only be derived for unit structs"),
    },
    _ => panic!("RustEmbed can only be derived for unit structs"),
  };

  let attribute = ast
    .attrs
    .iter()
    .find(|value| value.path.is_ident("folder"))
    .expect("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
  let meta = attribute
    .parse_meta()
    .expect("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
  let literal_value = match meta {
    Meta::NameValue(ref data) => &data.lit,
    _ => panic!("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]"),
  };
  let folder_path = match literal_value {
    Lit::Str(ref val) => val.clone().value(),
    _ => {
      panic!("#[derive(RustEmbed)] attribute value must be a string literal");
    }
  };

  #[cfg(feature = "interpolate-folder-path")]
  let folder_path = shellexpand::full(&folder_path).unwrap().to_string();

  // Base relative paths on the Cargo.toml location
  let folder_path = if Path::new(&folder_path).is_relative() {
    Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
      .join(folder_path)
      .to_string_lossy()
      .to_string()
  } else {
    folder_path
  };

  if !Path::new(&folder_path).exists() {
    let mut message = format!(
      "#[derive(RustEmbed)] folder '{}' does not exist. cwd: '{}'",
      folder_path,
      std::env::current_dir().unwrap().to_str().unwrap()
    );

    // Add a message about the interpolate-folder-path feature if the path may
    // include a variable
    if folder_path.contains('$') && cfg!(not(feature = "interpolate-folder-path")) {
      message += "\nA variable has been detected. RustEmbed can expand variables \
                  when the `interpolate-folder-path` feature is enabled.";
    }

    panic!(message);
  };

  generate_assets(&ast.ident, folder_path)
}

#[proc_macro_derive(RustEmbed, attributes(folder))]
pub fn derive_input_object(input: TokenStream) -> TokenStream {
  let ast: DeriveInput = syn::parse(input).unwrap();
  let gen = impl_rust_embed(&ast);
  gen.into()
}
<commit_msg>Use to_str instead of to_string_lossy<commit_after>#![recursion_limit = "1024"]
#[macro_use]
extern crate quote;
extern crate proc_macro;

use proc_macro::TokenStream;
use std::{env, path::Path};
use syn::{export::TokenStream2, Data, DeriveInput, Fields, Lit, Meta};

#[cfg(all(debug_assertions, not(feature = "debug-embed")))]
fn generate_assets(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
  quote! {
      impl #ident {
          pub fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
              use std::fs::File;
              use std::io::Read;
              use std::path::Path;

              let file_path = Path::new(#folder_path).join(file_path);
              let mut file = match File::open(file_path) {
                  Ok(mut file) => file,
                  Err(_e) => {
                      return None
                  }
              };
              let mut data: Vec<u8> = Vec::new();
              match file.read_to_end(&mut data) {
                  Ok(_) => Some(std::borrow::Cow::from(data)),
                  Err(_e) =>  {
                      return None
                  }
              }
          }

          pub fn iter() -> impl Iterator<Item = std::borrow::Cow<'static, str>> {
              use std::path::Path;
              rust_embed::utils::get_files(String::from(#folder_path)).map(|e| std::borrow::Cow::from(e.rel_path))
          }
      }
      impl rust_embed::RustEmbed for #ident {
        fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
          #ident::get(file_path)
        }
        fn iter() -> rust_embed::Filenames {
          // the return type of iter() is unnamable, so we have to box it
          rust_embed::Filenames::Dynamic(Box::new(#ident::iter()))
        }
      }
  }
}

#[cfg(all(not(feature = "compression"), any(not(debug_assertions), feature = "debug-embed")))]
fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
  quote! {
    #rel_path => {
        let bytes = &include_bytes!(#full_canonical_path)[..];
        Some(std::borrow::Cow::from(bytes))
    },
  }
}

#[cfg(all(feature = "compression", any(not(debug_assertions), feature = "debug-embed")))]
fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
  quote! {
    #rel_path => {
        rust_embed::flate!(static FILE: [u8] from #full_canonical_path);

        let bytes = &FILE[..];
        Some(std::borrow::Cow::from(bytes))
    },
  }
}

#[cfg(any(not(debug_assertions), feature = "debug-embed"))]
fn generate_assets(ident: &syn::Ident, folder_path: String) -> TokenStream2 {
  extern crate rust_embed_utils;

  let mut match_values = Vec::<TokenStream2>::new();
  let mut list_values = Vec::<String>::new();

  for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in rust_embed_utils::get_files(folder_path) {
    match_values.push(embed_file(&rel_path, &full_canonical_path));
    list_values.push(rel_path);
  }

  let array_len = list_values.len();

  quote! {
      impl #ident {
          pub fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
              match file_path {
                  #(#match_values)*
                  _ => None,
              }
          }

          fn names() -> std::slice::Iter<'static, &'static str> {
            const items: [&str; #array_len] = [#(#list_values),*];
            items.iter()
          }
          pub fn iter() -> impl Iterator<Item = std::borrow::Cow<'static, str>> {
              Self::names().map(|x| std::borrow::Cow::from(*x))
          }
      }
      impl rust_embed::RustEmbed for #ident {
        fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
          #ident::get(file_path)
        }
        fn iter() -> rust_embed::Filenames {
          rust_embed::Filenames::Embedded(#ident::names())
        }
      }
  }
}

fn impl_rust_embed(ast: &syn::DeriveInput) -> TokenStream2 {
  match ast.data {
    Data::Struct(ref data) => match data.fields {
      Fields::Unit => {}
      _ => panic!("RustEmbed can only be derived for unit structs"),
    },
    _ => panic!("RustEmbed can only be derived for unit structs"),
  };

  let attribute = ast
    .attrs
    .iter()
    .find(|value| value.path.is_ident("folder"))
    .expect("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
  let meta = attribute
    .parse_meta()
    .expect("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]");
  let literal_value = match meta {
    Meta::NameValue(ref data) => &data.lit,
    _ => panic!("#[derive(RustEmbed)] should contain one attribute like this #[folder = \"examples/public/\"]"),
  };
  let folder_path = match literal_value {
    Lit::Str(ref val) => val.clone().value(),
    _ => {
      panic!("#[derive(RustEmbed)] attribute value must be a string literal");
    }
  };

  #[cfg(feature = "interpolate-folder-path")]
  let folder_path = shellexpand::full(&folder_path).unwrap().to_string();

  // Base relative paths on the Cargo.toml location
  let folder_path = if Path::new(&folder_path).is_relative() {
    Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
      .join(folder_path)
      .to_str()
      .unwrap()
      .to_owned()
  } else {
    folder_path
  };

  if !Path::new(&folder_path).exists() {
    let mut message = format!(
      "#[derive(RustEmbed)] folder '{}' does not exist. cwd: '{}'",
      folder_path,
      std::env::current_dir().unwrap().to_str().unwrap()
    );

    // Add a message about the interpolate-folder-path feature if the path may
    // include a variable
    if folder_path.contains('$') && cfg!(not(feature = "interpolate-folder-path")) {
      message += "\nA variable has been detected. RustEmbed can expand variables \
                  when the `interpolate-folder-path` feature is enabled.";
    }

    panic!(message);
  };

  generate_assets(&ast.ident, folder_path)
}

#[proc_macro_derive(RustEmbed, attributes(folder))]
pub fn derive_input_object(input: TokenStream) -> TokenStream {
  let ast: DeriveInput = syn::parse(input).unwrap();
  let gen = impl_rust_embed(&ast);
  gen.into()
}
<|endoftext|>