<commit_before>use std::io;
use std::io::Write;

#[derive(Debug, PartialEq, Clone)]
pub enum Token {
    Unknown(String), // Invalid test (basically non-ascii)
    Literal(String), // Numeric literal number
    Func(Function), // Pre-defined function (like cos() )
    Const(Constant), // Constant like pi or e
    Var(String), // str arbitrary single char variable name
    Op(Operator), // Any of the 4 operators (+-*/)
    Open, // Open parens '('
    Close, // Closing parens ')'
    Set(Vec<Token>), // [ (, tokens..., ) ]
}

#[derive(Debug, PartialEq, Clone)]
pub enum Function {
    Sqrt(Vec<Token>),
    Ln(Vec<Token>),
    Log(f64, Vec<Token>),
    Sin(Vec<Token>),
    Cos(Vec<Token>),
    Tan(Vec<Token>),
    Asin(Vec<Token>),
    Acos(Vec<Token>),
    Atan(Vec<Token>),
    Sinh(Vec<Token>),
    Cosh(Vec<Token>),
    Tanh(Vec<Token>),
    Asinh(Vec<Token>),
    Acosh(Vec<Token>),
    Atanh(Vec<Token>),
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Constant {
	Pi,
	E,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Operator {
    Add,
    Sub,
    Div,
    Mul,
    Pow,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
pub enum Assoc {
    Left,
    Right,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
pub enum Order {
    AddSub = 1,
    MulDiv = 2,
    IMul = 3, // Implicit multiplication
    Pow = 4,
    Paren = 5,
}

#[derive(Debug, PartialEq, Clone)]
struct Expression {
    tokens: Vec<Token>,
    constant: f64,
}

struct ExpressionIter<'a> {
	expr: &'a Expression,
	count: usize,
}

impl<'a> Iterator for ExpressionIter<'a> {
	type Item = &'a Token;
    fn next(&mut self) -> Option<&'a Token> {
    	let token = self.expr.get_tokens().get(self.count);
    	self.count += 1;
    	token
    }
}

#[allow(dead_code)]
impl Expression {
	fn new(tokens: Vec<Token>, constant: f64) -> Self {
		Expression {tokens: tokens, constant: constant}
	}

	fn push(&mut self, token: Token) {
		self.tokens.push(token);
	}

	fn update_constant(&mut self, constant: &f64) {
		self.constant += *constant;
	}

	fn get_constant(&self) -> f64 {
		self.constant
	}

	fn get_tokens(&self) -> &[Token] {
		self.tokens.as_slice()
	}

	fn get_mut_tokens(&mut self) -> &mut [Token] {
		self.tokens.as_mut_slice()
	}

	fn replace_token(&mut self, index: usize, new_token: Token) {
		self.tokens.remove(index);
		self.tokens.insert(index, new_token);
	}

	fn replace_all_tokens(&mut self, new_token: Vec<Token>) {
		self.tokens = new_token
	}

	fn find_first(&self, token: &Token) -> Option<usize> {
		for i in 0..self.tokens.len() {
			if self.tokens.get(i).unwrap() == token {
				return Some(i);
			}
		}
		None
	}

	fn find_last(&self, token: &Token) -> Option<usize> {
		for i in (0..self.tokens.len()).rev() {
			if self.tokens.get(i).unwrap() == token {
				return Some(i);
			}
		}
		None
	}

	fn split_at(&self, index: usize) -> (&[Token], &[Token]) {
		self.tokens.split_at(index)
	}

	fn len(&self) -> usize {
		self.tokens.len()
	}

	fn iter(&self) -> ExpressionIter {
		ExpressionIter {expr: &self, count: 0}
	}
}

fn main() {
    println!("Welcome to Rust-Calculus!");
    println!("To evaluate an expression, simply type one in and hit RETURN.");
    println!("To set a variable, simply type VAR_NAME=EXPRESSION and hit RETURN.");
    println!("Valid commands are: sym_int, int, sym_def, and def.");
    println!("Type 'quit' to exit.");
    let mut input = String::new();
    let stdin = io::stdin();
    let mut stdout = io::stdout();
    loop {
    	input.clear();
    	print!(">>>> ");
    	stdout.flush().ok();
        if let Err(x) = stdin.read_line(&mut input) {
        	println!("There was a problem reading stdin: {:?}", x);
        	print!("Exiting...");;
        	break;
        }
        input = strip_white_space(&input).to_lowercase();
        if input == "quit" {
        	print!("Exiting...");
        	break;
        }
        let expr = match parse_input(&input) {
        		Ok(x) => x,
        		Err(x) => {println!("Encountered an error while parsing: {:?}", x); print!("Exiting..."); break;},
        	};
        println!("{:?}", &expr);
    }
}

fn strip_white_space(input: &String) -> String {
	input.split_whitespace().collect::<Vec<&str>>().join("")
}

fn parse_input(input: &String) -> Result<Expression, String> {
	let mut expr: Expression = Expression::new(Vec::new(), 0.0);
	for c in input.chars() {
		match c {
			'-' => {
				expr.push(Token::Op(Operator::Sub));
			},
			'+' => {
				expr.push(Token::Op(Operator::Add));
			},
			'/' => {
				expr.push(Token::Op(Operator::Div));
			},
			'*' => {
				expr.push(Token::Op(Operator::Mul));
			},
			'^' => {
				expr.push(Token::Op(Operator::Pow));
			},
			'(' => {
				expr.push(Token::Open);
			},
			')' => {
				expr.push(Token::Close);
			},
			_ => continue,
		}
	}
	// Once we find an outermost (), split the rest of the expression after that off into a new vector
	//let mut expr_vecs: Vec<Vec<Token>> = Vec::new();
	while let Some(index) = expr.find_first(&Token::Open) { // If we find an index of an Open
		let end_set: usize = expr.find_last(&Token::Close).unwrap(); // Finds the last Close, need to find the one that actually matches the proper Open (if two Sets are on same depth)
		let mut lhs: Vec<Token> = Vec::new();
		{
			let (temp_lhs, temp_rhs) = expr.split_at(index); // [0, where set should be placed)
			lhs = temp_lhs.to_vec();
			let mut rhs = temp_rhs.to_vec(); // [where set should be placed, len)
			rhs.remove(0);
			let (left_rhs, right_rhs) = rhs.split_at(end_set-1);
			let (left_rhs, mut right_rhs) = (left_rhs.to_vec(), right_rhs.to_vec());
			right_rhs.remove(0);
			lhs.push(Token::Set(left_rhs));
			lhs.append(&mut right_rhs);
		}
		for i in (0..lhs.len()).rev() {
			if lhs.get(i).unwrap() == &Token::Close {
				lhs.remove(i);
				break;
			}
		}
		expr.replace_all_tokens(lhs);
	}
	Ok(expr)
}

#[test]
fn test_parse() {
	let input: String = String::from("+");
	assert_eq!(Expression::new(vec![Token::Op(Operator::Add)], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("-");
	assert_eq!(Expression::new(vec![Token::Op(Operator::Sub)], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("/");
	assert_eq!(Expression::new(vec![Token::Op(Operator::Div)], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("*");
	assert_eq!(Expression::new(vec![Token::Op(Operator::Mul)], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("^");
	assert_eq!(Expression::new(vec![Token::Op(Operator::Pow)], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("()");
	assert_eq!(Expression::new(vec![Token::Set(Vec::new())], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(+)");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Op(Operator::Add)])], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(-)");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Op(Operator::Sub)])], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(*)");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Op(Operator::Mul)])], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(/)");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Op(Operator::Div)])], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(^)");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Op(Operator::Pow)])], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(())");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Set(Vec::new())])], 0.0), parse_input(&input).unwrap());
}<commit_msg>Somewhat improved parsing<commit_after>use std::io;
use std::io::Write;

#[derive(Debug, PartialEq, Clone)]
pub enum Token {
    Unknown(String), // Invalid test (basically non-ascii)
    Literal(String), // Numeric literal number
    Func(Function), // Pre-defined function (like cos() )
    Const(Constant), // Constant like pi or e
    Var(String), // str arbitrary single char variable name
    Op(Operator), // Any of the 4 operators (+-*/)
    Open(u8), // Open parens '('
    Close(u8), // Closing parens ')'
    Set(Vec<Token>), // [ (, tokens..., ) ]
}

#[derive(Debug, PartialEq, Clone)]
pub enum Function {
    Sqrt(Vec<Token>),
    Ln(Vec<Token>),
    Log(f64, Vec<Token>),
    Sin(Vec<Token>),
    Cos(Vec<Token>),
    Tan(Vec<Token>),
    Asin(Vec<Token>),
    Acos(Vec<Token>),
    Atan(Vec<Token>),
    Sinh(Vec<Token>),
    Cosh(Vec<Token>),
    Tanh(Vec<Token>),
    Asinh(Vec<Token>),
    Acosh(Vec<Token>),
    Atanh(Vec<Token>),
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Constant {
	Pi,
	E,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Operator {
    Add,
    Sub,
    Div,
    Mul,
    Pow,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
pub enum Assoc {
    Left,
    Right,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
pub enum Order {
    AddSub = 1,
    MulDiv = 2,
    IMul = 3, // Implicit multiplication
    Pow = 4,
    Paren = 5,
}

#[derive(Debug, PartialEq, Clone)]
struct Expression {
    tokens: Vec<Token>,
    constant: f64,
}

struct ExpressionIter<'a> {
	expr: &'a Expression,
	count: usize,
}

impl<'a> Iterator for ExpressionIter<'a> {
	type Item = &'a Token;
    fn next(&mut self) -> Option<&'a Token> {
    	let token = self.expr.get_tokens().get(self.count);
    	self.count += 1;
    	token
    }
}

#[allow(dead_code)]
impl Expression {
	fn new(tokens: Vec<Token>, constant: f64) -> Self {
		Expression {tokens: tokens, constant: constant}
	}

	fn push(&mut self, token: Token) {
		self.tokens.push(token);
	}

	fn update_constant(&mut self, constant: &f64) {
		self.constant += *constant;
	}

	fn get_constant(&self) -> f64 {
		self.constant
	}

	fn get_tokens(&self) -> &[Token] {
		self.tokens.as_slice()
	}

	fn get_mut_tokens(&mut self) -> &mut [Token] {
		self.tokens.as_mut_slice()
	}

	fn replace_token(&mut self, index: usize, new_token: Token) {
		self.tokens.remove(index);
		self.tokens.insert(index, new_token);
	}

	fn replace_all_tokens(&mut self, new_token: Vec<Token>) {
		self.tokens = new_token
	}

	fn find_first(&self, token: &Token) -> Option<usize> {
		for i in 0..self.tokens.len() {
			//if self.tokens.get(i).unwrap() == Token::Set {
				println!("HERE {:?}", self.tokens.get(i).unwrap().0);
			//}
			if self.tokens.get(i).unwrap() == token {
				return Some(i);
			}
		}
		None
	}

	fn find_last(&self, token: &Token) -> Option<usize> {
		for i in (0..self.tokens.len()).rev() {
			if self.tokens.get(i).unwrap() == token {
				return Some(i);
			}
		}
		None
	}

	fn split_at(&self, index: usize) -> (&[Token], &[Token]) {
		self.tokens.split_at(index)
	}

	fn len(&self) -> usize {
		self.tokens.len()
	}

	fn iter(&self) -> ExpressionIter {
		ExpressionIter {expr: &self, count: 0}
	}
}

fn main() {
    println!("Welcome to Rust-Calculus!");
    println!("To evaluate an expression, simply type one in and hit RETURN.");
    println!("To set a variable, simply type VAR_NAME=EXPRESSION and hit RETURN.");
    println!("Valid commands are: sym_int, int, sym_def, and def.");
    println!("Type 'quit' to exit.");
    let mut input = String::new();
    let stdin = io::stdin();
    let mut stdout = io::stdout();
    loop {
    	input.clear();
    	print!(">>>> ");
    	stdout.flush().ok();
        if let Err(x) = stdin.read_line(&mut input) {
        	println!("There was a problem reading stdin: {:?}", x);
        	print!("Exiting...");;
        	break;
        }
        input = strip_white_space(&input).to_lowercase();
        if input == "quit" {
        	print!("Exiting...");
        	break;
        }
        let expr = match parse_input(&input) {
        		Ok(x) => x,
        		Err(x) => {println!("Encountered an error while parsing: {:?}", x); print!("Exiting..."); break;},
        	};
        println!("{:?}", &expr);
    }
}

fn strip_white_space(input: &String) -> String {
	input.split_whitespace().collect::<Vec<&str>>().join("")
}

fn parse_input(input: &String) -> Result<Expression, String> {
	let mut expr: Expression = Expression::new(Vec::new(), 0.0);
	let mut depth: u8 = 0;
	let mut lastParens = ' ';
	let mut maxDepth: u8 = 0;
	for c in input.chars() {
		match c {
			'-' => {
				expr.push(Token::Op(Operator::Sub));
			},
			'+' => {
				expr.push(Token::Op(Operator::Add));
			},
			'/' => {
				expr.push(Token::Op(Operator::Div));
			},
			'*' => {
				expr.push(Token::Op(Operator::Mul));
			},
			'^' => {
				expr.push(Token::Op(Operator::Pow));
			},
			'(' => {
				if lastParens == '(' {
					depth += 1;
					if maxDepth < depth {maxDepth = depth}
				}
				expr.push(Token::Open(depth));
				lastParens = '(';
			},
			')' => {
				if lastParens == ')' {
					depth -= 1;
				}
				expr.push(Token::Close(depth));
				lastParens = ')';

			},
			_ => continue,
		}
	}
	assert!(depth == 0); // Depth should always end at 0
	println!("ORIG EXPR {:?}", &expr);
	loop {
		let mut index: Option<usize> = None;
		depth = 0;
		while depth <= maxDepth {
			if let Some(x) = expr.find_first(&Token::Open(depth)) {
				index = Some(x);
				break;
			}
			depth += 1;
		}
		if index == None {break;}
		let index = index.unwrap(); // If we reach here, we know index is not None
		let mut end_set: usize = expr.find_first(&Token::Close(depth)).unwrap();
		let mut lhs: Vec<Token> = Vec::new();
		{
			let (temp_lhs, temp_rhs) = expr.split_at(index); // [0, where set should be placed)
			lhs = temp_lhs.to_vec();
			let mut rhs = temp_rhs.to_vec(); // [where set should be placed, len)
			rhs.remove(0);
			end_set = end_set - (index + 1); // Compensate for part of vec split off and the removal of one element
			let (left_rhs, right_rhs) = rhs.split_at(end_set);
			let (left_rhs, mut right_rhs) = (left_rhs.to_vec(), right_rhs.to_vec());
			right_rhs.remove(0);
			lhs.push(Token::Set(left_rhs));
			lhs.append(&mut right_rhs);
		}

		expr.replace_all_tokens(lhs);
		println!("NEW EXPR {:?}", &expr);
		depth += 1;
		if depth > maxDepth {break;}
	}
	Ok(expr)
}

#[test]
fn test_parse() {
	let input: String = String::from("+");
	assert_eq!(Expression::new(vec![Token::Op(Operator::Add)], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("-");
	assert_eq!(Expression::new(vec![Token::Op(Operator::Sub)], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("/");
	assert_eq!(Expression::new(vec![Token::Op(Operator::Div)], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("*");
	assert_eq!(Expression::new(vec![Token::Op(Operator::Mul)], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("^");
	assert_eq!(Expression::new(vec![Token::Op(Operator::Pow)], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("()");
	assert_eq!(Expression::new(vec![Token::Set(Vec::new())], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(+)");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Op(Operator::Add)])], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(-)");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Op(Operator::Sub)])], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(*)");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Op(Operator::Mul)])], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(/)");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Op(Operator::Div)])], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(^)");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Op(Operator::Pow)])], 0.0), parse_input(&input).unwrap());
	let input: String = String::from("(())");
	assert_eq!(Expression::new(vec![Token::Set(vec![Token::Set(Vec::new())])], 0.0), parse_input(&input).unwrap());
}<|endoftext|>


<commit_before>//! # Hexdino
//!
//! A hex editor with vim like keybindings written in Rust.

#![doc(html_logo_url = "https://raw.githubusercontent.com/Luz/hexdino/master/logo.png")]
#![deny(trivial_casts)]

use std::cmp;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::io::SeekFrom;
use std::path::Path;

mod draw;
use draw::draw;
use draw::get_absolute_draw_indices;
mod find;
use find::FindOptSubset;

extern crate ncurses;
use ncurses::*;

extern crate getopts;
use getopts::Options;

extern crate pest;
#[macro_use]
extern crate pest_derive;

use pest::Parser;

#[derive(Parser)]
#[grammar = "cmd.pest"]
struct IdentParser;

extern crate memmem;
use memmem::{Searcher, TwoWaySearcher};

#[derive(PartialEq, Copy, Clone)]
pub enum CursorSelects {
    LeftNibble,
    RightNibble,
    AsciiChar,
}

#[derive(Copy, Clone)]
pub struct CursorState {
    pos: usize,
    sel: CursorSelects,
}

fn main() {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let mut buf = vec![];
    let mut cursor = CursorState {
        pos: 0,
        sel: CursorSelects::LeftNibble,
    };
    // 0 = display data from first line of file
    let mut screenoffset: usize = 0;
    const COLS: usize = 16;
    let mut command = String::new();
    let mut lastcommand = String::new();
    let mut debug = String::new();

    // start ncursesw
    initscr();
    let screenheight = getmaxy(stdscr()) as usize;
    // ctrl+z and fg works with this
    cbreak();
    noecho();
    start_color();
    use_default_colors();
    init_pair(1, COLOR_GREEN, -1);

    let args: Vec<_> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");
    opts.optflag("v", "version", "print the version");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            endwin();
            println!("{}", f.to_string());
            println!("Usage: {} FILE [options]", program);
            return;
        }
    };
    if matches.opt_present("v") {
        endwin();
        println!("Version: {}", VERSION);
        return;
    }
    if matches.opt_present("h") {
        endwin();
        println!("Usage: {} FILE [options]", program);
        return;
    }

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

    let patharg = match matches.free.is_empty() {
        true => String::new(),
        false => matches.free[0].clone(),
    };
    let path = Path::new(&patharg);

    if patharg.is_empty() {
        endwin();
        println!("Patharg is empty!\n");
        return;
    }

    let mut file = match OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(&path)
    {
        Err(why) => {
            endwin();
            println!("Could not open {}: {}", path.display(), why.to_string());
            return;
        }
        Ok(file) => file,
    };
    file.read_to_end(&mut buf)
        .ok()
        .expect("File could not be read.");

    let draw_range = get_absolute_draw_indices(buf.len(), COLS, screenoffset);
    draw(
        &buf[draw_range.0..draw_range.1],
        COLS,
        &command,
        &mut debug,
        cursor,
        screenoffset,
    );

    let mut quitnow = false;
    let mut autoparse = false;
    while quitnow == false {
        if !autoparse {
            let key = std::char::from_u32(getch() as u32).unwrap();
            command.push_str(&key.clone().to_string());
        }
        autoparse = false;

        let parsethisstring = command.clone();
        let commands = IdentParser::parse(Rule::cmd_list, &parsethisstring)
            .unwrap_or_else(|e| panic!("{}", e));

        let mut clear = true;
        let mut save = false;
        for cmd in commands {
            match cmd.as_rule() {
                Rule::down => {
                    // debug.push_str(&format!("{:?}", cmd.as_rule()));
                    if cursor.pos + COLS < buf.len() {
                        // not at end
                        cursor.pos += COLS;
                    } else {
                        // when at end
                        if buf.len() != 0 {
                            // Suppress underflow
                            cursor.pos = buf.len() - 1;
                        }
                    }
                }
                Rule::up => {
                    if cursor.pos >= COLS {
                        cursor.pos -= COLS;
                    }
                }
                Rule::left => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        if cursor.pos > 0 {
                            cursor.pos -= 1;
                        }
                    } else if cursor.sel == CursorSelects::RightNibble {
                        cursor.sel = CursorSelects::LeftNibble;
                    } else if cursor.sel == CursorSelects::LeftNibble {
                        if cursor.pos > 0 {
                            // not at start
                            cursor.sel = CursorSelects::RightNibble;
                            cursor.pos -= 1;
                        }
                    }
                }
                Rule::right => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        if cursor.pos + 1 < buf.len() {
                            // not at end
                            cursor.pos += 1;
                        }
                    } else if cursor.sel == CursorSelects::LeftNibble {
                        cursor.sel = CursorSelects::RightNibble;
                    } else if cursor.sel == CursorSelects::RightNibble {
                        if cursor.pos + 1 < buf.len() {
                            // not at end
                            cursor.sel = CursorSelects::LeftNibble;
                            cursor.pos += 1;
                        }
                    }
                }
                Rule::start => {
                    cursor.pos -= cursor.pos % COLS; // jump to start of line
                    if cursor.sel == CursorSelects::RightNibble {
                        cursor.sel = CursorSelects::LeftNibble;
                    }
                }
                Rule::end => {
                    // check if no overflow
                    if cursor.pos - (cursor.pos % COLS) + (COLS - 1) < buf.len() {
                        // jump to end of line
                        cursor.pos = cursor.pos - (cursor.pos % COLS) + (COLS - 1);
                    } else {
                        // jump to end of line
                        cursor.pos = buf.len() - 1
                    }
                    if cursor.sel == CursorSelects::LeftNibble {
                        cursor.sel = CursorSelects::RightNibble;
                    }
                }
                Rule::bottom => {
                    cursor.pos = buf.len() - 1;
                    cursor.pos -= cursor.pos % COLS; // jump to start of line
                }
                Rule::replace => {
                    // debug.push_str("next char will be the replacement!");
                    clear = false;
                }
                Rule::remove => {
                    // check if in valid range
                    if buf.len() > 0 && cursor.pos < buf.len() {
                        // remove the current char
                        buf.remove(cursor.pos);
                    }
                    // always perform the movement if possible
                    if cursor.pos > 0 && cursor.pos >= buf.len() {
                        cursor.pos -= 1;
                    }
                    lastcommand = command.clone();
                }
                Rule::insert => {
                    // debug.push_str("next chars will be inserted!");
                    clear = false;
                }
                Rule::jumpascii => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        cursor.sel = CursorSelects::LeftNibble;
                    } else {
                        cursor.sel = CursorSelects::AsciiChar;
                    }
                }
                Rule::helpfile => {
                    command.push_str("No helpfile yet");
                }
                Rule::repeat => {
                    command = lastcommand.clone();
                    clear = false;
                    autoparse = true;
                }
                Rule::backspace => {
                    command.pop();
                    command.pop();
                    clear = false;
                }
                Rule::saveandexit => {
                    save = true;
                    quitnow = true;
                }
                Rule::exit => quitnow = true,
                Rule::save => save = true,

                _ => (),
            }

            for inner_cmd in cmd.into_inner() {
                match inner_cmd.as_rule() {
                    Rule::replacement => {
                        let key = inner_cmd.as_str().chars().nth(0).unwrap_or('x');
                        if cursor.sel == CursorSelects::AsciiChar {
                            if cursor.pos >= buf.len() {
                                buf.insert(cursor.pos, 0);
                            }
                            // buf[cursor.pos] = inner_cmd.as_str();
                            buf[cursor.pos] = key as u8;
                        } else {
                            let mask = if cursor.sel == CursorSelects::LeftNibble {
                                0x0F
                            } else {
                                0xF0
                            };
                            let shift = if cursor.sel == CursorSelects::LeftNibble {
                                4
                            } else {
                                0
                            };
                            if cursor.pos >= buf.len() {
                                buf.insert(cursor.pos, 0);
                            }
                            // Change the selected nibble
                            if let Some(c) = key.to_digit(16) {
                                buf[cursor.pos] = buf[cursor.pos] & mask | (c as u8) << shift;
                            }
                        }
                        lastcommand.clear();
                        lastcommand.push_str(&format!(
                            "{}{}",
                            command.chars().nth(0).unwrap_or('?'),
                            key
                        ));
                    }
                    Rule::dd_lines => {
                        let amount: usize = inner_cmd.as_str().parse().unwrap_or(1);
                        // check if in valid range
                        if buf.len() > 0 && cursor.pos < buf.len() {
                            let startofline = cursor.pos - cursor.pos % COLS;
                            let mut endofline = cursor.pos - (cursor.pos % COLS) + (COLS * amount);
                            endofline = cmp::min(endofline, buf.len());
                            buf.drain(startofline..endofline);
                            if buf.len() > 0 && cursor.pos >= buf.len() {
                                cursor.pos = buf.len() - 1;
                            }
                        }
                        lastcommand = command.clone();
                    }
                    Rule::insertment => {
                        let key = inner_cmd.as_str().chars().nth(0).unwrap_or('x');
                        lastcommand = command.clone();
                        // debug.push_str(&format!("Inserted: {:?}", inner_cmd.as_str()));
                        command.pop(); // remove the just inserted thing
                        clear = false;

                        if cursor.sel == CursorSelects::LeftNibble {
                            // Left nibble
                            if let Some(c) = key.to_digit(16) {
                                buf.insert(cursor.pos, (c as u8) << 4);
                                cursor.sel = CursorSelects::RightNibble;
                            }
                        } else if cursor.sel == CursorSelects::RightNibble {
                            // Right nibble
                            if cursor.pos == buf.len() {
                                buf.insert(cursor.pos, 0);
                            }
                            if let Some(c) = key.to_digit(16) {
                                buf[cursor.pos] = buf[cursor.pos] & 0xF0 | c as u8;
                                cursor.sel = CursorSelects::LeftNibble;
                                cursor.pos += 1;
                            }
                        } else if cursor.sel == CursorSelects::AsciiChar {
                            buf.insert(cursor.pos, key as u8);
                            cursor.pos += 1;
                        }
                        lastcommand.clear();
                        lastcommand.push_str(&format!(
                            "Command repeation for {:?} not yet implemented.",
                            inner_cmd.as_rule()
                        ));
                    }
                    Rule::searchstr => {
                        let search = inner_cmd.as_str().as_bytes();
                        let foundpos = TwoWaySearcher::new(&search);
                        cursor.pos = foundpos.search_in(&buf).unwrap_or(cursor.pos);
                    }
                    Rule::searchbytes => {
                        let search = inner_cmd.as_str().as_bytes();
                        let mut needle = vec![];
                        for i in 0..search.len() {
                            let nibble = match search[i] as u8 {
                                c @ 48..=57 => c - 48, // Numbers from 0 to 9
                                b'x' => 0x10,          // x is the wildcard
                                b'X' => 0x10,          // X is the wildcard
                                c @ b'a'..=b'f' => c - 87,
                                c @ b'A'..=b'F' => c - 55,
                                _ => panic!("Should not get to this position!"),
                            };
                            needle.push(nibble);
                        }
                        cursor.pos = buf.find_subset(&needle).unwrap_or(cursor.pos);
                        // debug.push_str(&format!("Searching for: {:?}", needle ));
                    }
                    Rule::gg_line => {
                        let linenr: usize = inner_cmd.as_str().parse().unwrap_or(0);
                        cursor.pos = linenr * COLS; // jump to the line
                        if cursor.pos > buf.len() {
                            // detect file end
                            cursor.pos = buf.len();
                        }
                        cursor.pos -= cursor.pos % COLS; // jump to start of line
                    }
                    Rule::escape => (),
                    Rule::gatherone => clear = false,
                    _ => {
                        command.push_str(&format!("no rule for {:?} ", inner_cmd.as_rule()));
                        clear = false;
                    }
                };
            }
            if save {
                if path.exists() {
                    let mut file = match OpenOptions::new()
                        .read(true)
                        .write(true)
                        .create(true)
                        .open(&path)
                    {
                        Err(why) => {
                            panic!("Could not open {}: {}", path.display(), why.to_string())
                        }
                        Ok(file) => file,
                    };
                    file.seek(SeekFrom::Start(0))
                        .ok()
                        .expect("Filepointer could not be set to 0");
                    file.write_all(&mut buf)
                        .ok()
                        .expect("File could not be written.");
                    file.set_len(buf.len() as u64)
                        .ok()
                        .expect("File could not be set to correct lenght.");
                    command.push_str("File saved!");
                } else {
                    command.push_str("Careful, file could not be saved!");
                }
                // TODO: define filename during runtime
                save = false;
            }
            if clear {
                command.clear();
            }

            // Always move screen when cursor leaves screen
            if cursor.pos > (screenheight + screenoffset - 1) * COLS - 1 {
                screenoffset = 2 + cursor.pos / COLS - screenheight;
            }
            if cursor.pos < screenoffset * COLS {
                screenoffset = cursor.pos / COLS;
            }
        }

        let draw_range = get_absolute_draw_indices(buf.len(), COLS, screenoffset);
        draw(
            &buf[draw_range.0..draw_range.1],
            COLS,
            &command,
            &mut debug,
            cursor,
            screenoffset,
        );
    }

    refresh();
    endwin();
}
<commit_msg>Fix message regarding helpfile<commit_after>//! # Hexdino
//!
//! A hex editor with vim like keybindings written in Rust.

#![doc(html_logo_url = "https://raw.githubusercontent.com/Luz/hexdino/master/logo.png")]
#![deny(trivial_casts)]

use std::cmp;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::io::SeekFrom;
use std::path::Path;

mod draw;
use draw::draw;
use draw::get_absolute_draw_indices;
mod find;
use find::FindOptSubset;

extern crate ncurses;
use ncurses::*;

extern crate getopts;
use getopts::Options;

extern crate pest;
#[macro_use]
extern crate pest_derive;

use pest::Parser;

#[derive(Parser)]
#[grammar = "cmd.pest"]
struct IdentParser;

extern crate memmem;
use memmem::{Searcher, TwoWaySearcher};

#[derive(PartialEq, Copy, Clone)]
pub enum CursorSelects {
    LeftNibble,
    RightNibble,
    AsciiChar,
}

#[derive(Copy, Clone)]
pub struct CursorState {
    pos: usize,
    sel: CursorSelects,
}

fn main() {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    let mut buf = vec![];
    let mut cursor = CursorState {
        pos: 0,
        sel: CursorSelects::LeftNibble,
    };
    // 0 = display data from first line of file
    let mut screenoffset: usize = 0;
    const COLS: usize = 16;
    let mut command = String::new();
    let mut lastcommand = String::new();
    let mut debug = String::new();

    // start ncursesw
    initscr();
    let screenheight = getmaxy(stdscr()) as usize;
    // ctrl+z and fg works with this
    cbreak();
    noecho();
    start_color();
    use_default_colors();
    init_pair(1, COLOR_GREEN, -1);

    let args: Vec<_> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");
    opts.optflag("v", "version", "print the version");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            endwin();
            println!("{}", f.to_string());
            println!("Usage: {} FILE [options]", program);
            return;
        }
    };
    if matches.opt_present("v") {
        endwin();
        println!("Version: {}", VERSION);
        return;
    }
    if matches.opt_present("h") {
        endwin();
        println!("Usage: {} FILE [options]", program);
        return;
    }

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

    let patharg = match matches.free.is_empty() {
        true => String::new(),
        false => matches.free[0].clone(),
    };
    let path = Path::new(&patharg);

    if patharg.is_empty() {
        endwin();
        println!("Patharg is empty!\n");
        return;
    }

    let mut file = match OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(&path)
    {
        Err(why) => {
            endwin();
            println!("Could not open {}: {}", path.display(), why.to_string());
            return;
        }
        Ok(file) => file,
    };
    file.read_to_end(&mut buf)
        .ok()
        .expect("File could not be read.");

    let draw_range = get_absolute_draw_indices(buf.len(), COLS, screenoffset);
    draw(
        &buf[draw_range.0..draw_range.1],
        COLS,
        &command,
        &mut debug,
        cursor,
        screenoffset,
    );

    let mut quitnow = false;
    let mut autoparse = false;
    while quitnow == false {
        if !autoparse {
            let key = std::char::from_u32(getch() as u32).unwrap();
            command.push_str(&key.clone().to_string());
        }
        autoparse = false;

        let parsethisstring = command.clone();
        let commands = IdentParser::parse(Rule::cmd_list, &parsethisstring)
            .unwrap_or_else(|e| panic!("{}", e));

        let mut clear = true;
        let mut save = false;
        for cmd in commands {
            match cmd.as_rule() {
                Rule::down => {
                    // debug.push_str(&format!("{:?}", cmd.as_rule()));
                    if cursor.pos + COLS < buf.len() {
                        // not at end
                        cursor.pos += COLS;
                    } else {
                        // when at end
                        if buf.len() != 0 {
                            // Suppress underflow
                            cursor.pos = buf.len() - 1;
                        }
                    }
                }
                Rule::up => {
                    if cursor.pos >= COLS {
                        cursor.pos -= COLS;
                    }
                }
                Rule::left => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        if cursor.pos > 0 {
                            cursor.pos -= 1;
                        }
                    } else if cursor.sel == CursorSelects::RightNibble {
                        cursor.sel = CursorSelects::LeftNibble;
                    } else if cursor.sel == CursorSelects::LeftNibble {
                        if cursor.pos > 0 {
                            // not at start
                            cursor.sel = CursorSelects::RightNibble;
                            cursor.pos -= 1;
                        }
                    }
                }
                Rule::right => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        if cursor.pos + 1 < buf.len() {
                            // not at end
                            cursor.pos += 1;
                        }
                    } else if cursor.sel == CursorSelects::LeftNibble {
                        cursor.sel = CursorSelects::RightNibble;
                    } else if cursor.sel == CursorSelects::RightNibble {
                        if cursor.pos + 1 < buf.len() {
                            // not at end
                            cursor.sel = CursorSelects::LeftNibble;
                            cursor.pos += 1;
                        }
                    }
                }
                Rule::start => {
                    cursor.pos -= cursor.pos % COLS; // jump to start of line
                    if cursor.sel == CursorSelects::RightNibble {
                        cursor.sel = CursorSelects::LeftNibble;
                    }
                }
                Rule::end => {
                    // check if no overflow
                    if cursor.pos - (cursor.pos % COLS) + (COLS - 1) < buf.len() {
                        // jump to end of line
                        cursor.pos = cursor.pos - (cursor.pos % COLS) + (COLS - 1);
                    } else {
                        // jump to end of line
                        cursor.pos = buf.len() - 1
                    }
                    if cursor.sel == CursorSelects::LeftNibble {
                        cursor.sel = CursorSelects::RightNibble;
                    }
                }
                Rule::bottom => {
                    cursor.pos = buf.len() - 1;
                    cursor.pos -= cursor.pos % COLS; // jump to start of line
                }
                Rule::replace => {
                    // debug.push_str("next char will be the replacement!");
                    clear = false;
                }
                Rule::remove => {
                    // check if in valid range
                    if buf.len() > 0 && cursor.pos < buf.len() {
                        // remove the current char
                        buf.remove(cursor.pos);
                    }
                    // always perform the movement if possible
                    if cursor.pos > 0 && cursor.pos >= buf.len() {
                        cursor.pos -= 1;
                    }
                    lastcommand = command.clone();
                }
                Rule::insert => {
                    // debug.push_str("next chars will be inserted!");
                    clear = false;
                }
                Rule::jumpascii => {
                    if cursor.sel == CursorSelects::AsciiChar {
                        cursor.sel = CursorSelects::LeftNibble;
                    } else {
                        cursor.sel = CursorSelects::AsciiChar;
                    }
                }
                Rule::helpfile => {
                    command.pop();
                    command.push_str("No helpfile yet");
                    clear = false;
                }
                Rule::repeat => {
                    command = lastcommand.clone();
                    clear = false;
                    autoparse = true;
                }
                Rule::backspace => {
                    command.pop();
                    command.pop();
                    clear = false;
                }
                Rule::saveandexit => {
                    save = true;
                    quitnow = true;
                }
                Rule::exit => quitnow = true,
                Rule::save => save = true,

                _ => (),
            }

            for inner_cmd in cmd.into_inner() {
                match inner_cmd.as_rule() {
                    Rule::replacement => {
                        let key = inner_cmd.as_str().chars().nth(0).unwrap_or('x');
                        if cursor.sel == CursorSelects::AsciiChar {
                            if cursor.pos >= buf.len() {
                                buf.insert(cursor.pos, 0);
                            }
                            // buf[cursor.pos] = inner_cmd.as_str();
                            buf[cursor.pos] = key as u8;
                        } else {
                            let mask = if cursor.sel == CursorSelects::LeftNibble {
                                0x0F
                            } else {
                                0xF0
                            };
                            let shift = if cursor.sel == CursorSelects::LeftNibble {
                                4
                            } else {
                                0
                            };
                            if cursor.pos >= buf.len() {
                                buf.insert(cursor.pos, 0);
                            }
                            // Change the selected nibble
                            if let Some(c) = key.to_digit(16) {
                                buf[cursor.pos] = buf[cursor.pos] & mask | (c as u8) << shift;
                            }
                        }
                        lastcommand.clear();
                        lastcommand.push_str(&format!(
                            "{}{}",
                            command.chars().nth(0).unwrap_or('?'),
                            key
                        ));
                    }
                    Rule::dd_lines => {
                        let amount: usize = inner_cmd.as_str().parse().unwrap_or(1);
                        // check if in valid range
                        if buf.len() > 0 && cursor.pos < buf.len() {
                            let startofline = cursor.pos - cursor.pos % COLS;
                            let mut endofline = cursor.pos - (cursor.pos % COLS) + (COLS * amount);
                            endofline = cmp::min(endofline, buf.len());
                            buf.drain(startofline..endofline);
                            if buf.len() > 0 && cursor.pos >= buf.len() {
                                cursor.pos = buf.len() - 1;
                            }
                        }
                        lastcommand = command.clone();
                    }
                    Rule::insertment => {
                        let key = inner_cmd.as_str().chars().nth(0).unwrap_or('x');
                        lastcommand = command.clone();
                        // debug.push_str(&format!("Inserted: {:?}", inner_cmd.as_str()));
                        command.pop(); // remove the just inserted thing
                        clear = false;

                        if cursor.sel == CursorSelects::LeftNibble {
                            // Left nibble
                            if let Some(c) = key.to_digit(16) {
                                buf.insert(cursor.pos, (c as u8) << 4);
                                cursor.sel = CursorSelects::RightNibble;
                            }
                        } else if cursor.sel == CursorSelects::RightNibble {
                            // Right nibble
                            if cursor.pos == buf.len() {
                                buf.insert(cursor.pos, 0);
                            }
                            if let Some(c) = key.to_digit(16) {
                                buf[cursor.pos] = buf[cursor.pos] & 0xF0 | c as u8;
                                cursor.sel = CursorSelects::LeftNibble;
                                cursor.pos += 1;
                            }
                        } else if cursor.sel == CursorSelects::AsciiChar {
                            buf.insert(cursor.pos, key as u8);
                            cursor.pos += 1;
                        }
                        lastcommand.clear();
                        lastcommand.push_str(&format!(
                            "Command repeation for {:?} not yet implemented.",
                            inner_cmd.as_rule()
                        ));
                    }
                    Rule::searchstr => {
                        let search = inner_cmd.as_str().as_bytes();
                        let foundpos = TwoWaySearcher::new(&search);
                        cursor.pos = foundpos.search_in(&buf).unwrap_or(cursor.pos);
                    }
                    Rule::searchbytes => {
                        let search = inner_cmd.as_str().as_bytes();
                        let mut needle = vec![];
                        for i in 0..search.len() {
                            let nibble = match search[i] as u8 {
                                c @ 48..=57 => c - 48, // Numbers from 0 to 9
                                b'x' => 0x10,          // x is the wildcard
                                b'X' => 0x10,          // X is the wildcard
                                c @ b'a'..=b'f' => c - 87,
                                c @ b'A'..=b'F' => c - 55,
                                _ => panic!("Should not get to this position!"),
                            };
                            needle.push(nibble);
                        }
                        cursor.pos = buf.find_subset(&needle).unwrap_or(cursor.pos);
                        // debug.push_str(&format!("Searching for: {:?}", needle ));
                    }
                    Rule::gg_line => {
                        let linenr: usize = inner_cmd.as_str().parse().unwrap_or(0);
                        cursor.pos = linenr * COLS; // jump to the line
                        if cursor.pos > buf.len() {
                            // detect file end
                            cursor.pos = buf.len();
                        }
                        cursor.pos -= cursor.pos % COLS; // jump to start of line
                    }
                    Rule::escape => (),
                    Rule::gatherone => clear = false,
                    _ => {
                        command.push_str(&format!("no rule for {:?} ", inner_cmd.as_rule()));
                        clear = false;
                    }
                };
            }
            if save {
                if path.exists() {
                    let mut file = match OpenOptions::new()
                        .read(true)
                        .write(true)
                        .create(true)
                        .open(&path)
                    {
                        Err(why) => {
                            panic!("Could not open {}: {}", path.display(), why.to_string())
                        }
                        Ok(file) => file,
                    };
                    file.seek(SeekFrom::Start(0))
                        .ok()
                        .expect("Filepointer could not be set to 0");
                    file.write_all(&mut buf)
                        .ok()
                        .expect("File could not be written.");
                    file.set_len(buf.len() as u64)
                        .ok()
                        .expect("File could not be set to correct lenght.");
                    command.push_str("File saved!");
                } else {
                    command.push_str("Careful, file could not be saved!");
                }
                // TODO: define filename during runtime
                save = false;
            }
            if clear {
                command.clear();
            }

            // Always move screen when cursor leaves screen
            if cursor.pos > (screenheight + screenoffset - 1) * COLS - 1 {
                screenoffset = 2 + cursor.pos / COLS - screenheight;
            }
            if cursor.pos < screenoffset * COLS {
                screenoffset = cursor.pos / COLS;
            }
        }

        let draw_range = get_absolute_draw_indices(buf.len(), COLS, screenoffset);
        draw(
            &buf[draw_range.0..draw_range.1],
            COLS,
            &command,
            &mut debug,
            cursor,
            screenoffset,
        );
    }

    refresh();
    endwin();
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;
mod utils;
mod preflight;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use travis_after_all::Build;
use utils::user_repo_from_url;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w W, --write=W        Write changes to files (default: yes if CI is set, otherwise no).
  -r R, --release=R      Create release on GitHub and publish on crates.io (only in write mode) [default: yes].
  -b B, --branch=B       The branch on which releases should happen. [default: master].
";

const COMMITTER_ERROR_MESSAGE: &'static str = r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr($fmt);
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: Option<String>,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn push_to_github(config: &config::Config, tag_name: &str) {
    logger::stdout("Pushing new commit and tag");
    git::push(&config, &tag_name)
        .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

    logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
    thread::sleep(Duration::from_secs(1));
}

fn release_on_github(config: &config::Config, tag_message: &str, tag_name: &str) {
    if github::can_release(&config) {
        logger::stdout("Creating GitHub release");
        github::release(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));
    } else {
        logger::stdout("Project not hosted on GitHub. Skipping release step");
    }
}

fn release_on_cratesio(config: &config::Config) {
    logger::stdout("Publishing crate on crates.io");
    if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
        print_exit!("Failed to publish on crates.io");
    }
}

fn generate_changelog(repository_path: &str, version: &Version, new_version: &String) -> String {
    logger::stdout(format!("New version would be: {}", new_version));
    logger::stdout("Would write the following Changelog:");
    match changelog::generate(repository_path, &version.to_string(), new_version) {
        Ok(log) => log,
        Err(err) => {
            logger::stderr(format!("Generating Changelog failed: {:?}", err));
            process::exit(1)
        }
    }
}

fn write_changelog(repository_path: &str, version: &Version, new_version: &str) {
    logger::stdout("Writing Changelog");
    changelog::write(repository_path, &version.to_string(), &new_version)
        .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));
}

fn print_changelog(changelog: &str) {
    logger::stdout("====================================");
    logger::stdout(changelog);
    logger::stdout("====================================");
    logger::stdout("Would create annotated git tag");
}

fn package_crate(config: &config::Config, repository_path: &str, new_version: &str) {
    if config.release_mode {
        logger::stdout("Updating lockfile");
        if !cargo::update_lockfile(repository_path) {
            print_exit!("`cargo fetch` failed. See above for the cargo error message.");
        }
    }

    git::commit_files(&config, &new_version)
        .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

    logger::stdout("Package crate");
    if !cargo::package(repository_path) {
        print_exit!("`cargo package` failed. See above for the cargo error message.");
    }
}

fn get_repo(repository_path: &str) -> git2::Repository {
    match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    }
}

fn get_repository_path(args: &Args) -> String {
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is
                                                            not a directory"));
    let repo_path = path.to_str().unwrap_or_else(|| print_exit!("Path is not valid unicode"));
    repo_path.to_string()
}

fn get_signature<'a>(repository_path: String) -> git2::Signature<'a> {
    let repo = get_repo(&repository_path);
    let signature = match git::get_signature(&repo) {
        Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(COMMITTER_ERROR_MESSAGE);
                process::exit(1);
            }
    };

    signature.to_owned()
}

fn get_user_and_repo(repository_path: &str) -> Option<(String, String)> {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");
    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            let (user, repo_name) = user_repo_from_url(&url)
                .unwrap_or_else(|e| print_exit!("Could not extract user and repository name from URL: {:?}", e));

            Some((user, repo_name))
        },
        Err(err) => {
            logger::warn(format!("Could not determine the origin remote url: {:?}", err));
            logger::warn("semantic-rs can't push changes or create a release on GitHub");
            None
        }
    }
}

fn get_github_token(repository_path: &str) -> Option<String> {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");
    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            if github::is_github_url(&url) {
                env::var("GH_TOKEN").ok()
            } else {
                None
            }
        },
        Err(_) => None
    }
}

fn get_cargo_token() -> Option<String> {
    env::var("CARGO_TOKEN").ok()
}

fn assemble_configuration(args: Args) -> config::Config {
    let mut config_builder = ConfigBuilder::new();

    // If write mode is requested OR denied,
    // adhere to the user's wish,
    // otherwise we decide based on whether we are running in CI.
    let write_mode = match args.flag_write {
        None => ci_env_set(),
        Some(ref flag) => string_to_bool(flag)
    };

    // We can only release, if we are allowed to write
    let release_mode = write_mode && string_to_bool(&args.flag_release);
    let repository_path = get_repository_path(&args);

    config_builder.write(write_mode);
    config_builder.release(release_mode);
    config_builder.branch(args.flag_branch.clone());
    config_builder.repository_path(repository_path.clone());
    config_builder.signature(get_signature(repository_path.clone()));
    if let Some((user, repo)) = get_user_and_repo(&repository_path) {
        config_builder.user(user);
        config_builder.repository_name(repo);
    }
    if let Some(gh_token)  = get_github_token(&repository_path) {
        config_builder.gh_token(gh_token);
    }
    if let Some(cargo_token) = get_cargo_token() {
        config_builder.cargo_token(cargo_token);
    }
    let repo = get_repo(&repository_path);
    match repo.find_remote("origin") {
        Ok(r) => config_builder.remote(Ok(r.name().unwrap().to_string())),
        Err(err) => config_builder.remote(Err(err.description().to_string()))
    };

    config_builder.repository(repo);
    config_builder.build()
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");
    println!("semantic.rs 🚀");

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    let config = assemble_configuration(args);

    let branch = current_branch(&config.repository)
        .unwrap_or_else(|| print_exit!("Could not determine current branch."));

    if !is_release_branch(&branch, &config.branch) {
        println!("Current branch is '{}', releases are only done from branch '{}'", branch, config.branch);
        println!("No release done from a pull request either.");
        process::exit(0);
    }

    //Before we actually start, we do perform some preflight checks
    //Here we check if everything is in place to do a GitHub release and a
    //release on crates.io.
    //The important bit is, if something's missing, we do not abort since the user can still do all
    //other things except publishing

    logger::stdout("Performing preflight checks now");
    let warnings = preflight::check(&config);

    if warnings.len() == 0 {
        logger::stdout("Checks done. Everything is ok");
    }

    for warning in warnings {
        logger::warn(format!(">> {}", warning));
    }

    if config.release_mode && ci_env_set() {
        let build_run = Build::from_env()
            .unwrap_or_else(|e| print_exit!("CI mode, but can't check other builds. Error: {:?}", e));

        if !build_run.is_leader() {
            println!("Not the build leader. Nothing to do. Bye.");
            process::exit(0);
        }

        println!("I am the build leader. Waiting for other jobs to finish.");
        match build_run.wait_for_others() {
            Ok(()) => println!("Other jobs finished and succeeded. Doing my work now."),
            Err(travis_after_all::Error::FailedBuilds) => {
                print_exit!("Some builds failed. Stopping here.");
            },
            Err(e) => print_exit!("Waiting for other builds failed. Reason: {:?}", e),
        }
    }

    let version = toml_file::read_from_file(&config.repository_path)
        .unwrap_or_else(|err| print_exit!("Reading `Cargo.toml` failed: {:?}", err));

    let version = Version::parse(&version).expect("Not a valid version");
    logger::stdout(format!("Current version: {}", version.to_string()));

    logger::stdout("Analyzing commits");

    let bump = git::version_bump_since_latest(&config.repository);
    if config.write_mode {
        logger::stdout(format!("Commits analyzed. Bump will be {:?}", bump));
    } else {
        logger::stdout(format!("Commits analyzed. Bump would be {:?}", bump));
    }
    let new_version = match version_bump(&version, bump) {
        Some(new_version) => new_version.to_string(),
            None => {
                logger::stdout("No version bump. Nothing to do.");
                process::exit(0);
            }
    };

    if !config.write_mode {
        let changelog = generate_changelog(&config.repository_path, &version, &new_version);
        print_changelog(&changelog);
    } else {
        logger::stdout(format!("New version: {}", new_version));

        toml_file::write_new_version(&config.repository_path, &new_version)
            .unwrap_or_else(|err| print_exit!("Writing `Cargo.toml` failed: {:?}", err));

        write_changelog(&config.repository_path, &version, &new_version);
        package_crate(&config, &config.repository_path, &new_version);

        logger::stdout("Creating annotated git tag");
        let tag_message = changelog::generate(&config.repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Can't generate changelog: {:?}", err));

        let tag_name = format!("v{}", new_version);
        git::tag(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create git tag: {:?}", err));

        if config.release_mode && config.can_push() {
            push_to_github(&config, &tag_name);
            release_on_github(&config, &tag_message, &tag_name);
            release_on_cratesio(&config);
            println!("{} v{} is released. 🚀🚀🚀", config.repository_name.unwrap(), new_version);
        }
    }
}
<commit_msg>refactor: use warnings.is_empty() instead of warnings.len() == 0<commit_after>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;
mod utils;
mod preflight;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use travis_after_all::Build;
use utils::user_repo_from_url;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w W, --write=W        Write changes to files (default: yes if CI is set, otherwise no).
  -r R, --release=R      Create release on GitHub and publish on crates.io (only in write mode) [default: yes].
  -b B, --branch=B       The branch on which releases should happen. [default: master].
";

const COMMITTER_ERROR_MESSAGE: &'static str = r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr($fmt);
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: Option<String>,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn push_to_github(config: &config::Config, tag_name: &str) {
    logger::stdout("Pushing new commit and tag");
    git::push(&config, &tag_name)
        .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

    logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
    thread::sleep(Duration::from_secs(1));
}

fn release_on_github(config: &config::Config, tag_message: &str, tag_name: &str) {
    if github::can_release(&config) {
        logger::stdout("Creating GitHub release");
        github::release(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));
    } else {
        logger::stdout("Project not hosted on GitHub. Skipping release step");
    }
}

fn release_on_cratesio(config: &config::Config) {
    logger::stdout("Publishing crate on crates.io");
    if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
        print_exit!("Failed to publish on crates.io");
    }
}

fn generate_changelog(repository_path: &str, version: &Version, new_version: &String) -> String {
    logger::stdout(format!("New version would be: {}", new_version));
    logger::stdout("Would write the following Changelog:");
    match changelog::generate(repository_path, &version.to_string(), new_version) {
        Ok(log) => log,
        Err(err) => {
            logger::stderr(format!("Generating Changelog failed: {:?}", err));
            process::exit(1)
        }
    }
}

fn write_changelog(repository_path: &str, version: &Version, new_version: &str) {
    logger::stdout("Writing Changelog");
    changelog::write(repository_path, &version.to_string(), &new_version)
        .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));
}

fn print_changelog(changelog: &str) {
    logger::stdout("====================================");
    logger::stdout(changelog);
    logger::stdout("====================================");
    logger::stdout("Would create annotated git tag");
}

fn package_crate(config: &config::Config, repository_path: &str, new_version: &str) {
    if config.release_mode {
        logger::stdout("Updating lockfile");
        if !cargo::update_lockfile(repository_path) {
            print_exit!("`cargo fetch` failed. See above for the cargo error message.");
        }
    }

    git::commit_files(&config, &new_version)
        .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

    logger::stdout("Package crate");
    if !cargo::package(repository_path) {
        print_exit!("`cargo package` failed. See above for the cargo error message.");
    }
}

fn get_repo(repository_path: &str) -> git2::Repository {
    match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    }
}

fn get_repository_path(args: &Args) -> String {
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is
                                                            not a directory"));
    let repo_path = path.to_str().unwrap_or_else(|| print_exit!("Path is not valid unicode"));
    repo_path.to_string()
}

fn get_signature<'a>(repository_path: String) -> git2::Signature<'a> {
    let repo = get_repo(&repository_path);
    let signature = match git::get_signature(&repo) {
        Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(COMMITTER_ERROR_MESSAGE);
                process::exit(1);
            }
    };

    signature.to_owned()
}

fn get_user_and_repo(repository_path: &str) -> Option<(String, String)> {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");
    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            let (user, repo_name) = user_repo_from_url(&url)
                .unwrap_or_else(|e| print_exit!("Could not extract user and repository name from URL: {:?}", e));

            Some((user, repo_name))
        },
        Err(err) => {
            logger::warn(format!("Could not determine the origin remote url: {:?}", err));
            logger::warn("semantic-rs can't push changes or create a release on GitHub");
            None
        }
    }
}

fn get_github_token(repository_path: &str) -> Option<String> {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");
    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            if github::is_github_url(&url) {
                env::var("GH_TOKEN").ok()
            } else {
                None
            }
        },
        Err(_) => None
    }
}

fn get_cargo_token() -> Option<String> {
    env::var("CARGO_TOKEN").ok()
}

fn assemble_configuration(args: Args) -> config::Config {
    let mut config_builder = ConfigBuilder::new();

    // If write mode is requested OR denied,
    // adhere to the user's wish,
    // otherwise we decide based on whether we are running in CI.
    let write_mode = match args.flag_write {
        None => ci_env_set(),
        Some(ref flag) => string_to_bool(flag)
    };

    // We can only release, if we are allowed to write
    let release_mode = write_mode && string_to_bool(&args.flag_release);
    let repository_path = get_repository_path(&args);

    config_builder.write(write_mode);
    config_builder.release(release_mode);
    config_builder.branch(args.flag_branch.clone());
    config_builder.repository_path(repository_path.clone());
    config_builder.signature(get_signature(repository_path.clone()));
    if let Some((user, repo)) = get_user_and_repo(&repository_path) {
        config_builder.user(user);
        config_builder.repository_name(repo);
    }
    if let Some(gh_token)  = get_github_token(&repository_path) {
        config_builder.gh_token(gh_token);
    }
    if let Some(cargo_token) = get_cargo_token() {
        config_builder.cargo_token(cargo_token);
    }
    let repo = get_repo(&repository_path);
    match repo.find_remote("origin") {
        Ok(r) => config_builder.remote(Ok(r.name().unwrap().to_string())),
        Err(err) => config_builder.remote(Err(err.description().to_string()))
    };

    config_builder.repository(repo);
    config_builder.build()
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");
    println!("semantic.rs 🚀");

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    let config = assemble_configuration(args);

    let branch = current_branch(&config.repository)
        .unwrap_or_else(|| print_exit!("Could not determine current branch."));

    if !is_release_branch(&branch, &config.branch) {
        println!("Current branch is '{}', releases are only done from branch '{}'", branch, config.branch);
        println!("No release done from a pull request either.");
        process::exit(0);
    }

    //Before we actually start, we do perform some preflight checks
    //Here we check if everything is in place to do a GitHub release and a
    //release on crates.io.
    //The important bit is, if something's missing, we do not abort since the user can still do all
    //other things except publishing

    logger::stdout("Performing preflight checks now");
    let warnings = preflight::check(&config);

    if warnings.is_empty() {
        logger::stdout("Checks done. Everything is ok");
    }

    for warning in warnings {
        logger::warn(format!(">> {}", warning));
    }

    if config.release_mode && ci_env_set() {
        let build_run = Build::from_env()
            .unwrap_or_else(|e| print_exit!("CI mode, but can't check other builds. Error: {:?}", e));

        if !build_run.is_leader() {
            println!("Not the build leader. Nothing to do. Bye.");
            process::exit(0);
        }

        println!("I am the build leader. Waiting for other jobs to finish.");
        match build_run.wait_for_others() {
            Ok(()) => println!("Other jobs finished and succeeded. Doing my work now."),
            Err(travis_after_all::Error::FailedBuilds) => {
                print_exit!("Some builds failed. Stopping here.");
            },
            Err(e) => print_exit!("Waiting for other builds failed. Reason: {:?}", e),
        }
    }

    let version = toml_file::read_from_file(&config.repository_path)
        .unwrap_or_else(|err| print_exit!("Reading `Cargo.toml` failed: {:?}", err));

    let version = Version::parse(&version).expect("Not a valid version");
    logger::stdout(format!("Current version: {}", version.to_string()));

    logger::stdout("Analyzing commits");

    let bump = git::version_bump_since_latest(&config.repository);
    if config.write_mode {
        logger::stdout(format!("Commits analyzed. Bump will be {:?}", bump));
    } else {
        logger::stdout(format!("Commits analyzed. Bump would be {:?}", bump));
    }
    let new_version = match version_bump(&version, bump) {
        Some(new_version) => new_version.to_string(),
            None => {
                logger::stdout("No version bump. Nothing to do.");
                process::exit(0);
            }
    };

    if !config.write_mode {
        let changelog = generate_changelog(&config.repository_path, &version, &new_version);
        print_changelog(&changelog);
    } else {
        logger::stdout(format!("New version: {}", new_version));

        toml_file::write_new_version(&config.repository_path, &new_version)
            .unwrap_or_else(|err| print_exit!("Writing `Cargo.toml` failed: {:?}", err));

        write_changelog(&config.repository_path, &version, &new_version);
        package_crate(&config, &config.repository_path, &new_version);

        logger::stdout("Creating annotated git tag");
        let tag_message = changelog::generate(&config.repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Can't generate changelog: {:?}", err));

        let tag_name = format!("v{}", new_version);
        git::tag(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create git tag: {:?}", err));

        if config.release_mode && config.can_push() {
            push_to_github(&config, &tag_name);
            release_on_github(&config, &tag_message, &tag_name);
            release_on_cratesio(&config);
            println!("{} v{} is released. 🚀🚀🚀", config.repository_name.unwrap(), new_version);
        }
    }
}
<|endoftext|>


<commit_before>extern crate r2d2;
extern crate r2d2_postgres;
extern crate openssl;
#[macro_use] extern crate nickel;
extern crate nickel_postgres;
extern crate uuid;
extern crate serde_json;

use std::env;
use std::io::Read;
use r2d2::{NopErrorHandler, PooledConnection};
use r2d2_postgres::{SslMode, PostgresConnectionManager};
use nickel::{Nickel, HttpRouter, QueryString};
use nickel_postgres::{PostgresMiddleware, PostgresRequestExtensions};
use uuid::Uuid;
use nickel::status::StatusCode;
use serde_json::Value;

macro_rules! try_or_return {
    ( $op:expr, $error:expr ) => {
        match $op {
            Some(v) => v,
            None => return $error
        }
    }
}

fn create_tables(conn: PooledConnection<PostgresConnectionManager>) {
    let _r = conn.execute(
            "CREATE TABLE IF NOT EXISTS services (
                key UUID PRIMARY KEY,
                name VARCHAR NOT NULL
                )",
            &[]
        );

    let _r = conn.execute(
            "CREATE TABLE IF NOT EXISTS permissions (
                key UUID REFERENCES services,
                username VARCHAR NOT NULL,
                permissions JSONB,
                PRIMARY KEY (key, username)
                )",
            &[]
        );
}

fn main() {
    let mut app = Nickel::new();

    let postgres_url = env::var("DATABASE_URL")
        .expect("Failed to get DATABASE_URL");

    let dbpool = PostgresMiddleware::new(&*postgres_url,
                                         SslMode::None,
                                         5,
                                         Box::new(NopErrorHandler)).unwrap();

    create_tables(dbpool.pool.clone().get().unwrap());
    app.utilize(dbpool);
    app.get("/register", middleware! { |request, response|
        let conn = request.db_conn();

        let name = try_or_return!(request.query().get("service_name"),
            response.error(
                StatusCode::BadRequest, "No service_name specified"));

        let key = Uuid::new_v4();

        let r = conn.execute(
            "INSERT INTO services (key, name) VALUES ($1, $2)",
            &[&key, &name]
            );

        println!("{:?}", r);

        key.to_hyphenated_string()
    });

    app.post("/adduser", middleware! { |request, response|
        let conn = request.db_conn();

        let mut body = vec![];
        try_or_return!(request.origin.read_to_end(&mut body).ok(),
                       response.error(StatusCode::InternalServerError,
                                      "Failed"));

        let permissions = try_or_return!(String::from_utf8(body).ok(),
            response.error(StatusCode::InternalServerError, "Failed"));

        let data: Value = try_or_return!(
            serde_json::from_str(&permissions).ok(),
            response.error(StatusCode::InternalServerError, "Failed"));

        let q = request.query();

        let username = try_or_return!(q.get("username"),
            response.error(StatusCode::BadRequest, "No username specified"));
        let key = try_or_return!(q.get("key").map(|s| Uuid::parse_str(s).ok()),
            response.error(StatusCode::BadRequest, "No key specified"));

        let r = conn.execute(
            "INSERT INTO permissions (key, username, permissions)
                VALUES ($1, $2, $3)
                ON CONFLICT (key, username) DO UPDATE SET
                permissions=excluded.permissions",
                &[&key, &username, &data]
            );
        println!("{:?}", r); // TODO: something with r
        format!("You posted {}", permissions)
    });

    app.get("/getperms", middleware! { |request, response|
        let conn = request.db_conn();
        let q = request.query();
        let key = try_or_return!(q.get("key").map(|s| Uuid::parse_str(s).ok()),
            response.error(StatusCode::BadRequest, "No key specified"));

        let username = try_or_return!(q.get("username"),
            response.error(StatusCode::BadRequest, "No username specified"));

        let r = try_or_return!(conn.query(
            "SELECT permissions FROM permissions
            WHERE key=$1 and username=$2",
            &[&key, &username]
            ).ok(), response.error(StatusCode::InternalServerError,
                              "Failed"));

        let row = r.get(0);
        let s: Value = row.get(0);

        format!("{:?}", s)
    });

    let listen = match env::var("LISTEN") {
        Ok(s) => s,
        Err(_) => "127.0.0.1:8080".to_string()
    };

    app.listen(&listen[..]);
}
<commit_msg>Fix some errors<commit_after>extern crate r2d2;
extern crate r2d2_postgres;
extern crate openssl;
#[macro_use] extern crate nickel;
extern crate nickel_postgres;
extern crate uuid;
extern crate serde_json;

use std::env;
use std::io::Read;
use r2d2::{NopErrorHandler, PooledConnection};
use r2d2_postgres::{SslMode, PostgresConnectionManager};
use nickel::{Nickel, HttpRouter, QueryString};
use nickel_postgres::{PostgresMiddleware, PostgresRequestExtensions};
use uuid::Uuid;
use nickel::status::StatusCode;
use serde_json::Value;

macro_rules! try_or_return {
    ( $op:expr, $error:expr ) => {
        match $op {
            Some(v) => v,
            None => return $error
        }
    }
}

fn create_tables(conn: PooledConnection<PostgresConnectionManager>) {
    let _r = conn.execute(
            "CREATE TABLE IF NOT EXISTS services (
                key UUID PRIMARY KEY,
                name VARCHAR NOT NULL
                )",
            &[]
        );

    let _r = conn.execute(
            "CREATE TABLE IF NOT EXISTS permissions (
                key UUID REFERENCES services,
                username VARCHAR NOT NULL,
                permissions JSONB,
                PRIMARY KEY (key, username)
                )",
            &[]
        );
}

fn main() {
    let mut app = Nickel::new();

    let postgres_url = env::var("DATABASE_URL")
        .expect("Failed to get DATABASE_URL");

    let dbpool = PostgresMiddleware::new(&*postgres_url,
                                         SslMode::None,
                                         5,
                                         Box::new(NopErrorHandler)).unwrap();

    create_tables(dbpool.pool.clone().get().unwrap());
    app.utilize(dbpool);
    app.get("/register", middleware! { |request, response|
        let conn = request.db_conn();

        let name = try_or_return!(request.query().get("service_name"),
            response.error(
                StatusCode::BadRequest, "No service_name specified"));

        let key = Uuid::new_v4();

        let r = conn.execute(
            "INSERT INTO services (key, name) VALUES ($1, $2)",
            &[&key, &name]
            );

        println!("{:?}", r);

        key.to_hyphenated_string()
    });

    app.post("/adduser", middleware! { |request, response|
        let conn = request.db_conn();

        let mut body = vec![];
        try_or_return!(request.origin.read_to_end(&mut body).ok(),
                       response.error(StatusCode::InternalServerError,
                                      "Failed"));

        let permissions = try_or_return!(String::from_utf8(body).ok(),
            response.error(StatusCode::InternalServerError, "Failed"));

        let data: Value = try_or_return!(
            serde_json::from_str(&permissions).ok(),
            response.error(StatusCode::InternalServerError, "Failed"));

        let q = request.query();

        let username = try_or_return!(q.get("username"),
            response.error(StatusCode::BadRequest, "No username specified"));
        let key = try_or_return!(q.get("key").map(|s| Uuid::parse_str(s).ok()),
            response.error(StatusCode::BadRequest, "No key specified"));

        let r = try_or_return!(conn.execute(
            "INSERT INTO permissions (key, username, permissions)
                VALUES ($1, $2, $3)
                ON CONFLICT (key, username) DO UPDATE SET
                permissions=excluded.permissions",
                &[&key, &username, &data]
            ).ok(),
            response.error(StatusCode::InternalServerError, "Server error"));
        println!("{:?}", r); // TODO: something with r
        format!("You posted {}", permissions)
    });

    app.get("/getperms", middleware! { |request, response|
        let conn = request.db_conn();
        let q = request.query();
        let key = try_or_return!(q.get("key").map(|s| Uuid::parse_str(s).ok()),
            response.error(StatusCode::BadRequest, "No key specified"));

        let username = try_or_return!(q.get("username"),
            response.error(StatusCode::BadRequest, "No username specified"));

        let r = try_or_return!(conn.query(
            "SELECT permissions FROM permissions
            WHERE key=$1 and username=$2",
            &[&key, &username]
            ).ok(), response.error(StatusCode::InternalServerError,
                              "Server Error"));

        if r.len() < 1 {
            return response.error(StatusCode::NotFound, "User not found");
        }
        let row = r.get(0);
        let s: Value = row.get(0);

        format!("{:?}", s)
    });

    let listen = match env::var("LISTEN") {
        Ok(s) => s,
        Err(_) => "127.0.0.1:8080".to_string()
    };

    app.listen(&listen[..]);
}
<|endoftext|>