<commit_before>extern crate termion;
extern crate thread_scoped;
extern crate time;
extern crate voodoo;

mod ai;
mod info_view;
mod map_view;
mod level;
mod program;

use std::io::{Write};
use std::sync::mpsc::channel;

use termion::event::{Key, Event, MouseEvent};
use termion::input::{TermRead};

use voodoo::color::ColorValue;
use voodoo::window::{Point};

use info_view::InfoView;
use map_view::MapView;
use level::Level;
use program::{Ability, Program, ProgramRef, StatusEffect, Team};

const LEVEL_DESCR: [&'static str; 20] = [
    "                                                          ",
    "                                                          ",
    "          .........................                       ",
    "          .........................                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "   o................             .....                    ",
    "   o................             .....                    ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          .........................                       ",
    "          .........................                       ",
    "                                                          ",
    "                                                          ",
    "                                                          ",
];

#[derive(Clone,Copy,Debug)]
enum UiState {
    Unselected,
    Selected,
    SelectTarget(Ability),
    // Damage(ProgramRef, usize),
    Animating,
}

enum UiEvent {
    Quit,
    Tick,
    ClickMap(Point),
    ClickInfo(Point),
    EndTurn,
}

enum GameState {
    // Setup,
    PlayerTurn,
    AITurn,
    AITurnTransition,
    PlayerTurnTransition,
    // AIExecute,
    Quit,
}

struct UiModelView {
    info: InfoView,
    map: MapView,
}

impl UiState {
    fn select_program(point: Point, level: &Level, map: &mut MapView, info: &mut InfoView) -> UiState {
        use UiState::*;

        for program in level.programs.iter() {
            if program.borrow().intersects(point) && program.borrow().team == Team::Player {
                map.highlight(program.clone(), &level);
                info.display_program(&program.borrow());
                map.set_help("Click arrows to move; click ability at left to use");
                return Selected;
            }
        }
        Unselected
    }

    fn next(self, event: UiEvent, level: &mut Level, mv: &mut UiModelView) -> UiState {
        use UiEvent::*;
        use UiState::*;

        let UiModelView { ref mut info, ref mut map } = *mv;

        let result = match (self, event) {
            (Unselected, ClickMap(p)) => {
                Self::select_program(p, level, map, info)
            }
            (Selected, ClickMap(p)) => {
                let result = map.translate_click(p);
                if let Some(p) = result {
                    if let Some(ref mut program) = map.get_highlight() {
                        program.borrow_mut().move_to(p);
                        info.update_program(&program.borrow());
                    }
                    map.update_highlight(&level);
                    Selected
                }
                else {
                    map.clear_highlight();
                    info.clear();
                    Self::select_program(p, level, map, info)
                }
            }
            (Unselected, ClickInfo(_)) => Unselected,
            (Selected, ClickInfo(p)) => {
                if let Some(ability) = info.translate_click(p) {
                    match ability {
                        Ability::Destroy { damage, range } => {
                            map.set_help(format!("Select target. Damage: 0x{:x} Range: 0x{:x}", damage, range));
                            map.highlight_range(range, level);
                        }
                    }
                    return SelectTarget(ability);
                }
                Selected
            }
            (SelectTarget(ability), ClickMap(p)) => {
                let result = map.translate_click(p);
                info.clear_ability();
                map.clear_range();
                map.update_highlight(level);
                if let Some(p) = result {
                    match level.contents_of(p) {
                        level::CellContents::Program(p) => {
                            ability.apply(&mut p.borrow_mut());
                            if let Some(caster) = map.get_highlight() {
                                caster.borrow_mut().turn_state.ability_used = true;
                            }
                            Animating
                        },
                        _ => Selected,
                    }
                }
                else {
                    Selected
                }
            }
            (SelectTarget(_), ClickInfo(p)) => {
                let result = info.translate_click(p);
                if let Some(ability) = result {
                    // TODO: refactor this out
                    match ability {
                        Ability::Destroy { damage, range } => {
                            map.set_help(format!("Select target. Damage: 0x{:x} Range: 0x{:x}", damage, range));
                            map.highlight_range(range, level);
                        }
                    }
                    SelectTarget(ability)
                }
                else {
                    info.clear_ability();
                    map.clear_range();
                    map.update_highlight(level);
                    Selected
                }
            }
            (state, Tick) => {
                let modified = update_programs(level, map);

                match state {
                    Animating => {
                        if !modified {
                            if map.get_highlight().is_some() {
                                Selected
                            }
                            else {
                                info.clear();
                                Unselected
                            }
                        }
                        else {
                            state
                        }
                    }
                    _ => state,
                }
            }
            (Animating, _) => Animating,
            (state, Quit) | (state, EndTurn) => { state },
        };

        if let Unselected = result {
            map.set_help("Click program to control it");
        }

        result
    }
}

struct GameModelView {
    ui_modelview: UiModelView,
}

struct State(GameState, UiState);

impl State {
    fn translate_event(&self, event: termion::event::Event, level: &mut Level, mv: &mut GameModelView) -> Option<UiEvent> {
        use GameState::*;
        match (self, event) {
            (_, Event::Key(Key::Char('q'))) => Some(UiEvent::Quit),
            (&State(PlayerTurn, _), Event::Mouse(MouseEvent::Press(_, x, y))) => {
                if let Some(p) = mv.ui_modelview.map.from_global_frame(Point::new(x, y)) {
                    Some(UiEvent::ClickMap(p))
                }
                else if let Some(p) = mv.ui_modelview.info.from_global_frame(Point::new(x, y)) {
                    if p.y == 23 {
                        Some(UiEvent::EndTurn)
                    }
                    else {
                        Some(UiEvent::ClickInfo(p))
                    }
                }
                else {
                    None
                }
            }
            _ => None,
        }
    }

    fn next(self, event: termion::event::Event, level: &mut Level, mv: &mut GameModelView) -> State {
        use GameState::*;
        if let Some(event) = self.translate_event(event, level, mv) {
            match (self, event) {
                (State(PlayerTurn, ui), UiEvent::EndTurn) => State(AITurnTransition, UiState::Unselected),
                (State(PlayerTurn, ui), event) => Self::next_player_turn(ui, event, level, mv),
                (State(AITurnTransition, ui), _) => State(AITurnTransition, ui),
                (State(AITurn, ui), _) => State(AITurn, ui),
                _ => unimplemented!(),
            }
        }
        else {
            self
        }
    }

    fn tick(self, level: &mut Level, mv: &mut GameModelView) -> State {
        use GameState::*;
        match self {
            State(PlayerTurn, ui) => Self::next_player_turn(ui, UiEvent::Tick, level, mv),
            State(AITurnTransition, _) => {
                begin_turn(Team::Enemy, level, mv);
                State(AITurn, UiState::Unselected)
            }
            State(PlayerTurnTransition, _) => {
                begin_turn(Team::Player, level, mv);
                State(PlayerTurn, UiState::Unselected)
            }
            State(AITurn, UiState::Animating) => {
                let modified = update_programs(level, &mut mv.ui_modelview.map);

                if !modified {
                    begin_turn(Team::Player, level, mv);
                    State(AITurn, UiState::Unselected)
                }
                else {
                    State(AITurn, UiState::Animating)
                }
            }
            State(AITurn, _) => {
                let ai_state = ai::ai_tick(level, &mut mv.ui_modelview.map);
                mv.ui_modelview.map.set_help(format!("AI STATUS: {:?}", ai_state));
                match ai_state {
                    ai::AIState::Done => State(PlayerTurnTransition, UiState::Unselected),
                    ai::AIState::Plotting => State(AITurn, UiState::Unselected),
                    ai::AIState::WaitingAnimation => State(AITurn, UiState::Animating),
                }
            },
            _ => unimplemented!(),
        }
    }

    fn next_player_turn(ui_state: UiState, event: UiEvent, level: &mut Level, mv: &mut GameModelView) -> State {
        use GameState::*;
        match event {
            UiEvent::Quit => State(Quit, ui_state),
            click@UiEvent::ClickMap(_) => {
                State(PlayerTurn, ui_state.next(click, level, &mut mv.ui_modelview))
            }
            click@UiEvent::ClickInfo(_) => {
                State(PlayerTurn, ui_state.next(click, level, &mut mv.ui_modelview))
            }
            UiEvent::Tick => {
                State(PlayerTurn, ui_state.next(event, level, &mut mv.ui_modelview))
            }
            UiEvent::EndTurn => unreachable!(),
        }
    }
}

fn begin_turn(team: Team, level: &mut Level, mv: &mut GameModelView) {
    mv.ui_modelview.info.set_team(team);
    mv.ui_modelview.info.clear();
    mv.ui_modelview.map.clear_range();
    mv.ui_modelview.map.clear_highlight();
    mv.ui_modelview.map.update_highlight(level);
    level.begin_turn();
}

fn update_programs(level: &mut Level, map: &mut MapView) -> bool {
    let mut modified = false;
    let mut killed = vec![];
    for program in level.programs.iter_mut() {
        let mut p = program.borrow_mut();
        let position = p.position;
        let mut damaged = false;
        for effect in p.status_effects.iter_mut() {
            match *effect {
                StatusEffect::Damage(damage) => {
                    modified = true;
                    damaged = true;
                    *effect = StatusEffect::Damage(damage - 1);
                }
            }
        }
        p.status_effects.retain(|effect| {
            match *effect {
                StatusEffect::Damage(0) => false,
                StatusEffect::Damage(_) => true,
            }
        });

        if damaged {
            let lived = p.damage();
            if !lived {
                killed.push(position);
                map.clear_highlight();
            }
        }
    }

    for position in killed {
        level.remove_program_at(position);
    }

    modified
}

fn main() {
    use std::sync::mpsc::TryRecvError::*;
    use std::thread;
    use std::time::Duration;

    use voodoo::terminal::{Mode, Terminal};

    let mut level = Level::new(&LEVEL_DESCR);
    level.add_program(Program::new(Team::Player, Point::new(11, 9), "Hack"));
    level.add_program(Program::new(Team::Player, Point::new(5, 10), "Hack"));
    level.add_program(Program::new(Team::Enemy, Point::new(7, 10), "Hack"));
    level.add_program(Program::new(Team::Enemy, Point::new(9, 10), "Hack"));

    let mut terminal = Terminal::new();
    terminal.cursor(Mode::Disabled);
    terminal.clear_color(ColorValue::Black);
    let Terminal { ref mut stdin, ref mut stdout } = terminal;

    stdout.flush().unwrap();

    let mut info = voodoo::window::Window::new(Point::new(0, 0), 20, 24);
    let mut map = voodoo::window::Window::new(Point::new(20, 0), 60, 24);
    info.border();
    map.border();

    let mut info_view = InfoView::new(info);
    let mut map_view = MapView::new(map);
    info_view.refresh(stdout);
    map_view.display(&level);
    map_view.refresh(stdout);

    let ui_modelview = UiModelView {
        info: info_view,
        map: map_view,
    };
    let ui_state = UiState::Unselected;

    let mut state = State(GameState::PlayerTurnTransition, ui_state);
    let mut mv = GameModelView {
        ui_modelview: ui_modelview,
    };

    let (tx, rx) = channel();
    let guard = unsafe {
        thread_scoped::scoped(move || {
            for c in stdin.events() {
                let evt = c.unwrap();
                if let Event::Key(Key::Char('q')) = evt {
                    break;
                }
                tx.send(evt).unwrap();
            }
        })
    };

    let mut t = time::precise_time_ns();
    let mut dt = 0;

    'main: loop {
        loop {
            // Handle all pending events
            let msg = rx.try_recv();
            match msg {
                Ok(evt) => {
                    state = state.next(evt, &mut level, &mut mv);
                    if let State(GameState::Quit, _) = state {
                        break 'main;
                    }
                },
                Err(Disconnected) => break 'main,
                Err(Empty) => break,
            }
        }

        let now = time::precise_time_ns();
        dt += now - t;

        // TODO: use constant
        while dt >= 100000000 {
            state = state.tick(&mut level, &mut mv);
            dt -= 100000000;
        }

        mv.ui_modelview.info.refresh(stdout);
        mv.ui_modelview.map.display(&level);
        mv.ui_modelview.map.refresh(stdout);
        t = now;

        thread::sleep(Duration::from_millis(100 - dt / 1000000));
    }
    guard.join();
}
<commit_msg>fix: don't let AI accidentally double-move<commit_after>extern crate termion;
extern crate thread_scoped;
extern crate time;
extern crate voodoo;

mod ai;
mod info_view;
mod map_view;
mod level;
mod program;

use std::io::{Write};
use std::sync::mpsc::channel;

use termion::event::{Key, Event, MouseEvent};
use termion::input::{TermRead};

use voodoo::color::ColorValue;
use voodoo::window::{Point};

use info_view::InfoView;
use map_view::MapView;
use level::Level;
use program::{Ability, Program, ProgramRef, StatusEffect, Team};

const LEVEL_DESCR: [&'static str; 20] = [
    "                                                          ",
    "                                                          ",
    "          .........................                       ",
    "          .........................                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "   o................             .....                    ",
    "   o................             .....                    ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          ..                     ..                       ",
    "          .........................                       ",
    "          .........................                       ",
    "                                                          ",
    "                                                          ",
    "                                                          ",
];

#[derive(Clone,Copy,Debug)]
enum UiState {
    Unselected,
    Selected,
    SelectTarget(Ability),
    // Damage(ProgramRef, usize),
    Animating,
}

enum UiEvent {
    Quit,
    Tick,
    ClickMap(Point),
    ClickInfo(Point),
    EndTurn,
}

enum GameState {
    // Setup,
    PlayerTurn,
    AITurn,
    AITurnTransition,
    PlayerTurnTransition,
    // AIExecute,
    Quit,
}

struct UiModelView {
    info: InfoView,
    map: MapView,
}

impl UiState {
    fn select_program(point: Point, level: &Level, map: &mut MapView, info: &mut InfoView) -> UiState {
        use UiState::*;

        for program in level.programs.iter() {
            if program.borrow().intersects(point) && program.borrow().team == Team::Player {
                map.highlight(program.clone(), &level);
                info.display_program(&program.borrow());
                map.set_help("Click arrows to move; click ability at left to use");
                return Selected;
            }
        }
        Unselected
    }

    fn next(self, event: UiEvent, level: &mut Level, mv: &mut UiModelView) -> UiState {
        use UiEvent::*;
        use UiState::*;

        let UiModelView { ref mut info, ref mut map } = *mv;

        let result = match (self, event) {
            (Unselected, ClickMap(p)) => {
                Self::select_program(p, level, map, info)
            }
            (Selected, ClickMap(p)) => {
                let result = map.translate_click(p);
                if let Some(p) = result {
                    if let Some(ref mut program) = map.get_highlight() {
                        program.borrow_mut().move_to(p);
                        info.update_program(&program.borrow());
                    }
                    map.update_highlight(&level);
                    Selected
                }
                else {
                    map.clear_highlight();
                    info.clear();
                    Self::select_program(p, level, map, info)
                }
            }
            (Unselected, ClickInfo(_)) => Unselected,
            (Selected, ClickInfo(p)) => {
                if let Some(ability) = info.translate_click(p) {
                    match ability {
                        Ability::Destroy { damage, range } => {
                            map.set_help(format!("Select target. Damage: 0x{:x} Range: 0x{:x}", damage, range));
                            map.highlight_range(range, level);
                        }
                    }
                    return SelectTarget(ability);
                }
                Selected
            }
            (SelectTarget(ability), ClickMap(p)) => {
                let result = map.translate_click(p);
                info.clear_ability();
                map.clear_range();
                map.update_highlight(level);
                if let Some(p) = result {
                    match level.contents_of(p) {
                        level::CellContents::Program(p) => {
                            ability.apply(&mut p.borrow_mut());
                            if let Some(caster) = map.get_highlight() {
                                caster.borrow_mut().turn_state.ability_used = true;
                            }
                            Animating
                        },
                        _ => Selected,
                    }
                }
                else {
                    Selected
                }
            }
            (SelectTarget(_), ClickInfo(p)) => {
                let result = info.translate_click(p);
                if let Some(ability) = result {
                    // TODO: refactor this out
                    match ability {
                        Ability::Destroy { damage, range } => {
                            map.set_help(format!("Select target. Damage: 0x{:x} Range: 0x{:x}", damage, range));
                            map.highlight_range(range, level);
                        }
                    }
                    SelectTarget(ability)
                }
                else {
                    info.clear_ability();
                    map.clear_range();
                    map.update_highlight(level);
                    Selected
                }
            }
            (state, Tick) => {
                let modified = update_programs(level, map);

                match state {
                    Animating => {
                        if !modified {
                            if map.get_highlight().is_some() {
                                Selected
                            }
                            else {
                                info.clear();
                                Unselected
                            }
                        }
                        else {
                            state
                        }
                    }
                    _ => state,
                }
            }
            (Animating, _) => Animating,
            (state, Quit) | (state, EndTurn) => { state },
        };

        if let Unselected = result {
            map.set_help("Click program to control it");
        }

        result
    }
}

struct GameModelView {
    ui_modelview: UiModelView,
}

struct State(GameState, UiState);

impl State {
    fn translate_event(&self, event: termion::event::Event, level: &mut Level, mv: &mut GameModelView) -> Option<UiEvent> {
        use GameState::*;
        match (self, event) {
            (_, Event::Key(Key::Char('q'))) => Some(UiEvent::Quit),
            (&State(PlayerTurn, _), Event::Mouse(MouseEvent::Press(_, x, y))) => {
                if let Some(p) = mv.ui_modelview.map.from_global_frame(Point::new(x, y)) {
                    Some(UiEvent::ClickMap(p))
                }
                else if let Some(p) = mv.ui_modelview.info.from_global_frame(Point::new(x, y)) {
                    if p.y == 23 {
                        Some(UiEvent::EndTurn)
                    }
                    else {
                        Some(UiEvent::ClickInfo(p))
                    }
                }
                else {
                    None
                }
            }
            _ => None,
        }
    }

    fn next(self, event: termion::event::Event, level: &mut Level, mv: &mut GameModelView) -> State {
        use GameState::*;
        if let Some(event) = self.translate_event(event, level, mv) {
            match (self, event) {
                (State(PlayerTurn, ui), UiEvent::EndTurn) => State(AITurnTransition, UiState::Unselected),
                (State(PlayerTurn, ui), event) => Self::next_player_turn(ui, event, level, mv),
                (State(AITurnTransition, ui), _) => State(AITurnTransition, ui),
                (State(AITurn, ui), _) => State(AITurn, ui),
                _ => unimplemented!(),
            }
        }
        else {
            self
        }
    }

    fn tick(self, level: &mut Level, mv: &mut GameModelView) -> State {
        use GameState::*;
        match self {
            State(PlayerTurn, ui) => Self::next_player_turn(ui, UiEvent::Tick, level, mv),
            State(AITurnTransition, _) => {
                begin_turn(Team::Enemy, level, mv);
                State(AITurn, UiState::Unselected)
            }
            State(PlayerTurnTransition, _) => {
                begin_turn(Team::Player, level, mv);
                State(PlayerTurn, UiState::Unselected)
            }
            State(AITurn, UiState::Animating) => {
                let modified = update_programs(level, &mut mv.ui_modelview.map);

                if !modified {
                    State(AITurn, UiState::Unselected)
                }
                else {
                    State(AITurn, UiState::Animating)
                }
            }
            State(AITurn, _) => {
                let ai_state = ai::ai_tick(level, &mut mv.ui_modelview.map);
                mv.ui_modelview.map.set_help(format!("AI STATUS: {:?}", ai_state));
                match ai_state {
                    ai::AIState::Done => State(PlayerTurnTransition, UiState::Unselected),
                    ai::AIState::Plotting => State(AITurn, UiState::Unselected),
                    ai::AIState::WaitingAnimation => State(AITurn, UiState::Animating),
                }
            },
            _ => unimplemented!(),
        }
    }

    fn next_player_turn(ui_state: UiState, event: UiEvent, level: &mut Level, mv: &mut GameModelView) -> State {
        use GameState::*;
        match event {
            UiEvent::Quit => State(Quit, ui_state),
            click@UiEvent::ClickMap(_) => {
                State(PlayerTurn, ui_state.next(click, level, &mut mv.ui_modelview))
            }
            click@UiEvent::ClickInfo(_) => {
                State(PlayerTurn, ui_state.next(click, level, &mut mv.ui_modelview))
            }
            UiEvent::Tick => {
                State(PlayerTurn, ui_state.next(event, level, &mut mv.ui_modelview))
            }
            UiEvent::EndTurn => unreachable!(),
        }
    }
}

fn begin_turn(team: Team, level: &mut Level, mv: &mut GameModelView) {
    mv.ui_modelview.info.set_team(team);
    mv.ui_modelview.info.clear();
    mv.ui_modelview.map.clear_range();
    mv.ui_modelview.map.clear_highlight();
    mv.ui_modelview.map.update_highlight(level);
    level.begin_turn();
}

fn update_programs(level: &mut Level, map: &mut MapView) -> bool {
    let mut modified = false;
    let mut killed = vec![];
    for program in level.programs.iter_mut() {
        let mut p = program.borrow_mut();
        let position = p.position;
        let mut damaged = false;
        for effect in p.status_effects.iter_mut() {
            match *effect {
                StatusEffect::Damage(damage) => {
                    modified = true;
                    damaged = true;
                    *effect = StatusEffect::Damage(damage - 1);
                }
            }
        }
        p.status_effects.retain(|effect| {
            match *effect {
                StatusEffect::Damage(0) => false,
                StatusEffect::Damage(_) => true,
            }
        });

        if damaged {
            let lived = p.damage();
            if !lived {
                killed.push(position);
                map.clear_highlight();
            }
        }
    }

    for position in killed {
        level.remove_program_at(position);
    }

    modified
}

const MS: u64 = 1_000_000;
const TICK_TIME: u64 = 250;

fn main() {
    use std::sync::mpsc::TryRecvError::*;
    use std::thread;
    use std::time::Duration;

    use voodoo::terminal::{Mode, Terminal};

    let mut level = Level::new(&LEVEL_DESCR);
    level.add_program(Program::new(Team::Player, Point::new(11, 9), "Hack"));
    level.add_program(Program::new(Team::Player, Point::new(5, 10), "Hack"));
    level.add_program(Program::new(Team::Enemy, Point::new(7, 10), "Hack"));
    level.add_program(Program::new(Team::Enemy, Point::new(9, 10), "Hack"));

    let mut terminal = Terminal::new();
    terminal.cursor(Mode::Disabled);
    terminal.clear_color(ColorValue::Black);
    let Terminal { ref mut stdin, ref mut stdout } = terminal;

    stdout.flush().unwrap();

    let mut info = voodoo::window::Window::new(Point::new(0, 0), 20, 24);
    let mut map = voodoo::window::Window::new(Point::new(20, 0), 60, 24);
    info.border();
    map.border();

    let mut info_view = InfoView::new(info);
    let mut map_view = MapView::new(map);
    info_view.refresh(stdout);
    map_view.display(&level);
    map_view.refresh(stdout);

    let ui_modelview = UiModelView {
        info: info_view,
        map: map_view,
    };
    let ui_state = UiState::Unselected;

    let mut state = State(GameState::PlayerTurnTransition, ui_state);
    let mut mv = GameModelView {
        ui_modelview: ui_modelview,
    };

    let (tx, rx) = channel();
    let guard = unsafe {
        thread_scoped::scoped(move || {
            for c in stdin.events() {
                let evt = c.unwrap();
                if let Event::Key(Key::Char('q')) = evt {
                    break;
                }
                tx.send(evt).unwrap();
            }
        })
    };

    let mut t = time::precise_time_ns();
    let mut dt = 0;

    'main: loop {
        loop {
            // Handle all pending events
            let msg = rx.try_recv();
            match msg {
                Ok(evt) => {
                    state = state.next(evt, &mut level, &mut mv);
                    if let State(GameState::Quit, _) = state {
                        break 'main;
                    }
                },
                Err(Disconnected) => break 'main,
                Err(Empty) => break,
            }
        }

        let now = time::precise_time_ns();
        dt += now - t;

        // TODO: use constant
        while dt >= TICK_TIME * MS {
            state = state.tick(&mut level, &mut mv);
            dt -= TICK_TIME * MS;
        }

        mv.ui_modelview.info.refresh(stdout);
        mv.ui_modelview.map.display(&level);
        mv.ui_modelview.map.refresh(stdout);
        t = now;

        thread::sleep(Duration::from_millis((TICK_TIME - dt / MS) / 2));
    }
    guard.join();
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
#[macro_use]
extern crate nom;

extern crate bip_bencode;
extern crate bip_metainfo;
extern crate bip_utracker;
extern crate bip_util;
extern crate chrono;
extern crate hyper;
extern crate pnet;
extern crate pnet_macros_support;
extern crate pretty_env_logger;
extern crate rustyline;
extern crate url;

mod btclient;
mod packet;

use bip_bencode::Bencode;
use bip_metainfo::MetainfoFile;
use bip_utracker::contact::CompactPeersV4;
use chrono::{TimeZone, UTC};
use hyper::Client;
use hyper::header::Connection;
use packet::peer_pkt::{MutablePeerHandshakePacket, MutablePeerMessagePacket};
use pnet::packet::Packet;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use url::Url;

use std::fs::File;
use std::io::prelude::*;
use std::net::TcpStream;
use std::str;
use std::string::String;
use std::time::{SystemTime, UNIX_EPOCH};
use std::thread;

const HISTORY_FILE: &'static str = ".rustyline.history";
const PEER_HANDSHAKE_STRUCT_SZ: usize = 68;
const PEER_REQ_PKT_SZ: usize = 14;

/// Print file list from the torrent.
fn print_files(bytes: &[u8]) {
    let metainfo = MetainfoFile::from_bytes(bytes).unwrap();
    let info = metainfo.info();

    println!("File List:");
    println!("Size (bytes)\tPath");
    println!("------------\t----------------------------------------------");
    for file in info.files() {
        println!("{:12}\t{}",
                 file.length(),
                 file.paths().next().unwrap_or("<unknown>"));
    }
}

/// Print general information about the torrent.
fn print_metainfo_overview(bytes: &[u8]) {
    let metainfo = MetainfoFile::from_bytes(bytes).unwrap();
    let info = metainfo.info();
    let info_hash_hex = metainfo.info_hash()
        .as_ref()
        .iter()
        .map(|b| format!("{:02x}", b))
        .fold(String::new(), |mut acc, nex| {
            acc.push_str(&nex);
            acc
        });
    let utc_creation_date = metainfo.creation_date().map(|c| UTC.timestamp(c, 0));

    println!("------Metainfo File Overview-----");

    println!("InfoHash: {}", info_hash_hex);
    println!("Main Tracker: {:?}", metainfo.main_tracker());
    println!("Comment: {:?}", metainfo.comment());
    println!("Creator: {:?}", metainfo.created_by());
    println!("Creation Date: {:?}", utc_creation_date);

    println!("Directory: {:?}", info.directory());
    println!("Piece Length: {:?}", info.piece_length());
    println!("Number Of Pieces: {}", info.pieces().count());
    println!("Number Of Files: {}", info.files().count());
    println!("Total File Size: {}\n",
             info.files().fold(0, |acc, nex| acc + nex.length()));

    print_files(bytes);
}

fn connect_to_tracker(metainfo: MetainfoFile,
                      peer_id: &str,
                      port: u16)
                      -> Result<(Vec<String>, String), String> {
    debug!("connecting to tracker: {:?}", metainfo.main_tracker());

    let info_hash = metainfo.info_hash();
    // TODO figure can this conversion to url-encoded form be done safely?
    let info_hash_str = unsafe { str::from_utf8_unchecked(info_hash.as_ref()) };

    // TODO this needs to be calculated based on what we have
    let total_len = metainfo.info().files().fold(0, |acc, nex| acc + nex.length());

    let mut url = Url::parse(metainfo.main_tracker().unwrap()).unwrap();
    url.query_pairs_mut()
        .append_pair("info_hash", info_hash_str)
        .append_pair("peer_id", peer_id)
        .append_pair("port", &(port.to_string()))
        // TODO parametrize this
        .append_pair("uploaded", "0")
        // TODO parametrize this
        .append_pair("downloaded", "0")
        // TODO see note on total_len above
        .append_pair("left", &(total_len.to_string()))
        .append_pair("compact", "1")
        .append_pair("event", "started")
        .append_pair("supportcrypto", "0");
    trace!("URL {:?}", url);

    let client = Client::new();
    let mut res = client.get(url).header(Connection::close()).send().unwrap();
    let mut buffer = Vec::new();
    res.read_to_end(&mut buffer).unwrap();
    debug!("{:?}", res);
    let bencode = Bencode::decode(&buffer).unwrap();
    trace!("{:?}", bencode);
    let (_, peers) = CompactPeersV4::from_bytes(bencode.dict()
            .unwrap()
            .lookup("peers")
            .unwrap()
            .bytes()
            .unwrap())
        .unwrap();
    trace!("{:?}", peers);
    let mut ip_ports: Vec<String> = Vec::new();
    debug!("Peer list received:");
    for peer in peers.iter() {
        debug!("{:?}", peer);
        ip_ports.push(peer.to_string());
    }
    Ok((ip_ports, info_hash_str.to_string()))
}

fn peer_connections(peer_ip_ports: Vec<String>,
                    info_hash: &str,
                    peer_id: &str)
                    -> Result<(), String> {
    for peer_ip_port in peer_ip_ports {
        let peer_ip_port_cl = peer_ip_port.clone();
        let info_hash_cl = info_hash.to_string().clone();
        let peer_id_cl = peer_id.to_string().clone();
        thread::spawn(move || {
            match handshake_peer(&peer_ip_port_cl, &info_hash_cl, &peer_id_cl) {
                Ok(mut stream) => {
                    debug!("Communicating with peer {:?}", peer_ip_port_cl);
                    let mut buff = [0; PEER_HANDSHAKE_STRUCT_SZ];
                    match stream.read(&mut buff) {
                        Ok(_) => {
                            let mut buf = vec![0u8; PEER_REQ_PKT_SZ];
                            let mut peer_msg_pkt = MutablePeerMessagePacket::new(&mut buf).unwrap();
                            peer_msg_pkt.set_len(13);
                            peer_msg_pkt.set_id(6);
                            // TODO fill length properly
                            let index_begin_length = vec![0, 0, 10];
                            peer_msg_pkt.set_payload(&index_begin_length);
                            match stream.write(peer_msg_pkt.packet()) {
                                Ok(_) => {
                                    let mut buf_read = vec![0; 2048];
                                    match stream.read(&mut buf_read) {
                                        Ok(bytes_read) => {
                                            debug!("Bytes read: {:?}", bytes_read);
                                        }
                                        Err(e) => error!("Read failed! {:?}", e),
                                    }
                                }
                                Err(e) => error!("Write to stream failed! {:?}", e),
                            }
                        }
                        Err(e) => error!("Reading from the stream failed! {:?}", e),
                    }
                }
                Err(_) => {
                    error!("Closing thread with peer {:?}", peer_ip_port_cl);
                }
            }
        });
    }
    Ok(())
}

fn handshake_peer(peer_ip_port: &str, info_hash: &str, peer_id: &str) -> Result<TcpStream, String> {
    let mut buf = vec![0u8; PEER_HANDSHAKE_STRUCT_SZ];
    let mut ph = MutablePeerHandshakePacket::new(&mut buf).unwrap();
    ph.set_pstrlen("BitTorrent protocol".len() as u8);
    ph.set_pstr(&String::from("BitTorrent protocol").into_bytes());
    ph.set_reserved(&[0; 8]);
    ph.set_info_hash(info_hash.as_bytes());
    ph.set_peer_id(peer_id.as_bytes());
    match TcpStream::connect(peer_ip_port) {
        Ok(mut stream) => {
            debug!("Connection to peer {:?} successful!", peer_ip_port);
            match stream.write(ph.packet()) {
                Ok(_) => {
                    debug!("Sending message successful!");
                    trace!("{:?}", ph.packet());
                    Ok((stream))
                }
                Err(_) => {
                    error!("Sending message failed!");
                    Err("Sending message failed!".to_owned())
                }
            }
        }
        Err(e) => {
            error!("Connection to peer {:?} failed! {:?}", peer_ip_port, e);
            Err("Connection to peer failed!".to_owned())
        }
    }
}

fn main() {
    pretty_env_logger::init();

    let mut rl = Editor::<()>::new();
    if rl.load_history(HISTORY_FILE).is_err() {
        info!("No previous history!");
    }

    let now = SystemTime::now();
    let duration = now.duration_since(UNIX_EPOCH).unwrap();
    let client_id = "-bittorrent-rs-".to_owned() + &format!("{}", duration.as_secs() % 100_000);

    loop {
        let readline = rl.readline("> ");
        match readline {
            Ok(cmd) => {
                rl.add_history_entry(&cmd);

                let cmd = cmd.trim().split(' ').collect::<Vec<&str>>();

                match cmd[0] {
                    "help" | "h" => {
                        println!("Commands:
parse/p <torrent file path>      - show Metainfo File Overview
connect/c <torrent file path>    - initiate connection to tracker, and handshake with peers
showfiles/sf <torrent file path> - show files in the torrent
help/h                           - show this help");
                    }
                    "parse" | "p" => {
                        if cmd.len() != 2 {
                            error!("usage: parse <torrent file>");
                        } else {
                            let path = cmd[1];
                            debug!("MetainfoFile: {:?}", path);

                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();
                                    print_metainfo_overview(&bytes);
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    "connect" | "c" => {
                        if cmd.len() != 2 {
                            error!("usage: connect <torrent file>");
                        } else {
                            let path = cmd[1];
                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();

                                    // TODO: generate peer ID
                                    let result =
                                        connect_to_tracker(MetainfoFile::from_bytes(&bytes)
                                                               .unwrap(),
                                                           &client_id,
                                                           6882)
                                            .unwrap();
                                    peer_connections(result.0, &result.1, &client_id).unwrap();
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    "showfiles" | "sf" => {
                        if cmd.len() != 2 {
                            error!("usage: connect <torrent file>");
                        } else {
                            let path = cmd[1];
                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();

                                    print_files(&bytes);
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    "" => {}
                    _ => {
                        println!("invalid command, see \"help\"");
                    }
                }
            }
            Err(ReadlineError::Interrupted) |
            Err(ReadlineError::Eof) => {
                break;
            }
            Err(err) => {
                error!("Error: {:?}", err);
                break;
            }
        }
    }
    rl.save_history(HISTORY_FILE).unwrap();
}
<commit_msg>Implementation of request_piece function<commit_after>#[macro_use]
extern crate log;
#[macro_use]
extern crate nom;

extern crate bip_bencode;
extern crate bip_metainfo;
extern crate bip_utracker;
extern crate bip_util;
extern crate chrono;
extern crate hyper;
extern crate pnet;
extern crate pnet_macros_support;
extern crate pretty_env_logger;
extern crate rustyline;
extern crate url;

mod btclient;
mod packet;

use bip_bencode::Bencode;
use bip_metainfo::MetainfoFile;
use bip_utracker::contact::CompactPeersV4;
use chrono::{TimeZone, UTC};
use hyper::Client;
use hyper::header::Connection;
use packet::peer_pkt::{MutablePeerHandshakePacket, MutablePeerMessagePacket};
use pnet::packet::Packet;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use url::Url;

use std::fs::File;
use std::io::prelude::*;
use std::net::TcpStream;
use std::str;
use std::string::String;
use std::time::{SystemTime, UNIX_EPOCH};
use std::thread;

const HISTORY_FILE: &'static str = ".rustyline.history";
const PEER_HANDSHAKE_STRUCT_SZ: usize = 68;
const PEER_REQ_PKT_SZ: usize = 14;

/// Print file list from the torrent.
fn print_files(bytes: &[u8]) {
    let metainfo = MetainfoFile::from_bytes(bytes).unwrap();
    let info = metainfo.info();

    println!("File List:");
    println!("Size (bytes)\tPath");
    println!("------------\t----------------------------------------------");
    for file in info.files() {
        println!("{:12}\t{}",
                 file.length(),
                 file.paths().next().unwrap_or("<unknown>"));
    }
}

/// Print general information about the torrent.
fn print_metainfo_overview(bytes: &[u8]) {
    let metainfo = MetainfoFile::from_bytes(bytes).unwrap();
    let info = metainfo.info();
    let info_hash_hex = metainfo.info_hash()
        .as_ref()
        .iter()
        .map(|b| format!("{:02x}", b))
        .fold(String::new(), |mut acc, nex| {
            acc.push_str(&nex);
            acc
        });
    let utc_creation_date = metainfo.creation_date().map(|c| UTC.timestamp(c, 0));

    println!("------Metainfo File Overview-----");

    println!("InfoHash: {}", info_hash_hex);
    println!("Main Tracker: {:?}", metainfo.main_tracker());
    println!("Comment: {:?}", metainfo.comment());
    println!("Creator: {:?}", metainfo.created_by());
    println!("Creation Date: {:?}", utc_creation_date);

    println!("Directory: {:?}", info.directory());
    println!("Piece Length: {:?}", info.piece_length());
    println!("Number Of Pieces: {}", info.pieces().count());
    println!("Number Of Files: {}", info.files().count());
    println!("Total File Size: {}\n",
             info.files().fold(0, |acc, nex| acc + nex.length()));

    print_files(bytes);
}

fn connect_to_tracker(metainfo: MetainfoFile,
                      peer_id: &str,
                      port: u16)
                      -> Result<(Vec<String>, String), String> {
    debug!("connecting to tracker: {:?}", metainfo.main_tracker());

    let info_hash = metainfo.info_hash();
    // TODO figure can this conversion to url-encoded form be done safely?
    let info_hash_str = unsafe { str::from_utf8_unchecked(info_hash.as_ref()) };

    // TODO this needs to be calculated based on what we have
    let total_len = metainfo.info().files().fold(0, |acc, nex| acc + nex.length());

    let mut url = Url::parse(metainfo.main_tracker().unwrap()).unwrap();
    url.query_pairs_mut()
        .append_pair("info_hash", info_hash_str)
        .append_pair("peer_id", peer_id)
        .append_pair("port", &(port.to_string()))
        // TODO parametrize this
        .append_pair("uploaded", "0")
        // TODO parametrize this
        .append_pair("downloaded", "0")
        // TODO see note on total_len above
        .append_pair("left", &(total_len.to_string()))
        .append_pair("compact", "1")
        .append_pair("event", "started")
        .append_pair("supportcrypto", "0");
    trace!("URL {:?}", url);

    let client = Client::new();
    let mut res = client.get(url).header(Connection::close()).send().unwrap();
    let mut buffer = Vec::new();
    res.read_to_end(&mut buffer).unwrap();
    debug!("{:?}", res);
    let bencode = Bencode::decode(&buffer).unwrap();
    trace!("{:?}", bencode);
    let (_, peers) = CompactPeersV4::from_bytes(bencode.dict()
            .unwrap()
            .lookup("peers")
            .unwrap()
            .bytes()
            .unwrap())
        .unwrap();
    trace!("{:?}", peers);
    let mut ip_ports: Vec<String> = Vec::new();
    debug!("Peer list received:");
    for peer in peers.iter() {
        debug!("{:?}", peer);
        ip_ports.push(peer.to_string());
    }
    Ok((ip_ports, info_hash_str.to_string()))
}

fn peer_connections(peer_ip_ports: Vec<String>,
                    info_hash: &str,
                    peer_id: &str)
                    -> Result<(), String> {
    for peer_ip_port in peer_ip_ports {
        let peer_ip_port_cl = peer_ip_port.clone();
        let info_hash_cl = info_hash.to_string().clone();
        let peer_id_cl = peer_id.to_string().clone();
        thread::spawn(move || {
            match handshake_peer(&peer_ip_port_cl, &info_hash_cl, &peer_id_cl) {
                Ok(mut stream) => {
                    debug!("Communicating with peer {:?}", peer_ip_port_cl);
                    let mut buff = [0; PEER_HANDSHAKE_STRUCT_SZ];
                    match stream.read(&mut buff) {
                        Ok(_) => {
                            // TODO determine the below three parameters logically
                            let index = 0;
                            let begin = 0;
                            let length = 10;
                            match request_piece(stream, index, begin, length) {
                                Ok(buf_read) => {
                                    debug!("buf_read: {:?}", buf_read);
                                }
                                Err(_) => error!("Requesting piece failed!"),
                            }
                        }
                        Err(e) => error!("Reading from the stream failed! {:?}", e),
                    }
                }
                Err(_) => {
                    error!("Closing thread with peer {:?}", peer_ip_port_cl);
                }
            }
        });
    }
    Ok(())
}

fn request_piece(mut stream: TcpStream,
                 index: u32,
                 begin: u32,
                 length: u32)
                 -> Result<Vec<u8>, String> {
    let mut buf = vec![0u8; PEER_REQ_PKT_SZ];
    let mut peer_msg_pkt = MutablePeerMessagePacket::new(&mut buf).unwrap();
    peer_msg_pkt.set_len(13);
    peer_msg_pkt.set_id(6);
    let mut index_begin_length: Vec<u32> = Vec::new();
    index_begin_length.push(index);
    index_begin_length.push(begin);
    index_begin_length.push(length);
    let byte_arr = unsafe { std::mem::transmute::<Vec<u32>, Vec<u8>>(index_begin_length) };

    peer_msg_pkt.set_payload(&byte_arr);

    match stream.write(peer_msg_pkt.packet()) {
        Ok(_) => {
            let mut buf_read = vec![0; 2048];
            match stream.read(&mut buf_read) {
                Ok(bytes_read) => {
                    debug!("Bytes read: {:?}", bytes_read);
                    Ok(buf_read[..bytes_read].to_vec())
                }
                Err(e) => {
                    error!("Read failed! {:?}", e);
                    Err("Read failed!".to_owned())
                }
            }
        }
        Err(e) => {
            error!("Write to stream failed! {:?}", e);
            Err("Write to stream failed!".to_owned())
        }
    }
}

fn handshake_peer(peer_ip_port: &str, info_hash: &str, peer_id: &str) -> Result<TcpStream, String> {
    let mut buf = vec![0u8; PEER_HANDSHAKE_STRUCT_SZ];
    let mut ph = MutablePeerHandshakePacket::new(&mut buf).unwrap();
    ph.set_pstrlen("BitTorrent protocol".len() as u8);
    ph.set_pstr(&String::from("BitTorrent protocol").into_bytes());
    ph.set_reserved(&[0; 8]);
    ph.set_info_hash(info_hash.as_bytes());
    ph.set_peer_id(peer_id.as_bytes());
    match TcpStream::connect(peer_ip_port) {
        Ok(mut stream) => {
            debug!("Connection to peer {:?} successful!", peer_ip_port);
            match stream.write(ph.packet()) {
                Ok(_) => {
                    debug!("Sending message successful!");
                    trace!("{:?}", ph.packet());
                    Ok((stream))
                }
                Err(_) => {
                    error!("Sending message failed!");
                    Err("Sending message failed!".to_owned())
                }
            }
        }
        Err(e) => {
            error!("Connection to peer {:?} failed! {:?}", peer_ip_port, e);
            Err("Connection to peer failed!".to_owned())
        }
    }
}

fn main() {
    pretty_env_logger::init();

    let mut rl = Editor::<()>::new();
    if rl.load_history(HISTORY_FILE).is_err() {
        info!("No previous history!");
    }

    let now = SystemTime::now();
    let duration = now.duration_since(UNIX_EPOCH).unwrap();
    let client_id = "-bittorrent-rs-".to_owned() + &format!("{}", duration.as_secs() % 100_000);

    loop {
        let readline = rl.readline("> ");
        match readline {
            Ok(cmd) => {
                rl.add_history_entry(&cmd);

                let cmd = cmd.trim().split(' ').collect::<Vec<&str>>();

                match cmd[0] {
                    "help" | "h" => {
                        println!("Commands:
parse/p <torrent file path>      - show Metainfo File Overview
connect/c <torrent file path>    - initiate connection to tracker, and handshake with peers
showfiles/sf <torrent file path> - show files in the torrent
help/h                           - show this help");
                    }
                    "parse" | "p" => {
                        if cmd.len() != 2 {
                            error!("usage: parse <torrent file>");
                        } else {
                            let path = cmd[1];
                            debug!("MetainfoFile: {:?}", path);

                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();
                                    print_metainfo_overview(&bytes);
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    "connect" | "c" => {
                        if cmd.len() != 2 {
                            error!("usage: connect <torrent file>");
                        } else {
                            let path = cmd[1];
                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();

                                    // TODO: generate peer ID
                                    let result =
                                        connect_to_tracker(MetainfoFile::from_bytes(&bytes)
                                                               .unwrap(),
                                                           &client_id,
                                                           6882)
                                            .unwrap();
                                    peer_connections(result.0, &result.1, &client_id).unwrap();
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    "showfiles" | "sf" => {
                        if cmd.len() != 2 {
                            error!("usage: connect <torrent file>");
                        } else {
                            let path = cmd[1];
                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();

                                    print_files(&bytes);
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    "" => {}
                    _ => {
                        println!("invalid command, see \"help\"");
                    }
                }
            }
            Err(ReadlineError::Interrupted) |
            Err(ReadlineError::Eof) => {
                break;
            }
            Err(err) => {
                error!("Error: {:?}", err);
                break;
            }
        }
    }
    rl.save_history(HISTORY_FILE).unwrap();
}
<|endoftext|>


<commit_before>use std::io;
use std::io::Write; // need it to flush stdout
use std::env;
use std::process;

fn execute(cmd: &str) {
    let ret = process::Command::new(cmd).output().unwrap();
    println!("{}", String::from_utf8_lossy(&ret.stdout).trim());
}

fn get_prompt(prompt: &mut String) {
    // get username
    prompt.push_str(env::var("USER").unwrap().trim());

    prompt.push('@');

    // get hostname
    let hn = process::Command::new("/bin/hostname").output().unwrap();
    prompt.push_str(String::from_utf8_lossy(&hn.stdout).trim());

    prompt.push(' ');

    // get cwd
    prompt.push_str(env::var("PWD").unwrap().trim());

    prompt.push_str(" $ ");
}

fn main() {

    // we allocate a String for the user input
    let mut input: String = String::new();
    let mut prompt: String = String::new();
    get_prompt(&mut prompt);

    loop {

        print!("{}", prompt);
        if let Err(why) = io::stdout().flush() {
            println!("error: {}", why);
            continue;
        }

        // input probably has stuff in it from the last command, so clear
        // it out
        input.clear();

        // read input into our String. if there was an error, print the
        // error message and continue
        if let Err(why) = io::stdin().read_line(&mut input){
            println!("error: {}", why);
            continue;
        }

        // catch builtins, otherwise feed to execute function
        match input.trim() {
            "exit" => {
                println!("Exiting!");
                break;
            },
            cmd => execute(&cmd)
        }
    }
}
<commit_msg>Catch empty input<commit_after>use std::io;
use std::io::Write; // need it to flush stdout
use std::env;
use std::process;

fn execute(cmd: &str) {
    let ret = process::Command::new(cmd).output().unwrap();
    println!("{}", String::from_utf8_lossy(&ret.stdout).trim());
}

fn get_prompt(prompt: &mut String) {
    // get username
    prompt.push_str(env::var("USER").unwrap().trim());

    prompt.push('@');

    // get hostname
    let hn = process::Command::new("/bin/hostname").output().unwrap();
    prompt.push_str(String::from_utf8_lossy(&hn.stdout).trim());

    prompt.push(' ');

    // get cwd
    prompt.push_str(env::var("PWD").unwrap().trim());

    prompt.push_str(" $ ");
}

fn main() {

    // we allocate a String for the user input
    let mut input: String = String::new();
    let mut prompt: String = String::new();
    get_prompt(&mut prompt);

    loop {

        print!("{}", prompt);
        if let Err(why) = io::stdout().flush() {
            println!("error: {}", why);
            continue;
        }

        // input probably has stuff in it from the last command, so clear
        // it out
        input.clear();

        // read input into our String. if there was an error, print the
        // error message and continue
        if let Err(why) = io::stdin().read_line(&mut input){
            println!("error: {}", why);
            continue;
        }

        // catch builtins, otherwise feed to execute function
        match input.trim() {
            "" => continue,
            "exit" => {
                println!("Exiting!");
                break;
            },
            cmd => execute(&cmd)
        }
    }
}
<|endoftext|>


<commit_before>#![feature(iterator_step_by)]

extern crate easy_hash;

use easy_hash::{Sha256, Hasher, HashResult};
use std::thread;
use std::sync::mpsc;

const BASE: usize = 42;
const THREADS: usize = 4;
static DIFFICULTY: &'static str = "000000";

struct Solution(usize, String);

fn search_for_solution(start_at: usize, sender: mpsc::Sender<Option<Solution>>) {
    for i in (start_at..).step_by(THREADS) {
        let hash: String = Sha256::hash(format!("{}", i * BASE).as_bytes()).hex();

        let result = if hash.ends_with(DIFFICULTY) {
            Some(Solution(i, hash))
        } else {
            None
        };

        match sender.send(result) {
            Ok(_)  => {},
            Err(_) => {
                println!("Receiver has stopped receiving, dropping worker number {}", start_at);
                break;
            },
        }
    }
}

fn main() {
    println!("Attempting to find a number, which - while multiplied by {} and hashed using SHA-256 - will result in a hash ending with {}.", BASE, DIFFICULTY);
    println!("Please wait...");

    /*
     * Here, we have 4 threads (as specified by the value of THREADS constant).
     * Thread 1 will start at number 1 and check 5, 9,  13 and so on.
     * Thread 2 will start at number 2 and check 6, 10, 14 and so on.
     * Thread 3 will start at number 3 and check 7, 11, 15 and so on.
     * Thread 4 will start at number 4 and check 8, 12, 16 and so on.
     *
     * This way, we have 4 parallel threads of execution and we're sure
     * that each number will be examined exactly once.
     */
    let (sender, receiver) = mpsc::channel();
    for i in 1..THREADS+1 {
        let sender_n = sender.clone();
        thread::spawn(move || {
            search_for_solution(i, sender_n);
        });
    }

    /*
     * In a loop, receive messages from the producers.
     * As long as there is no solution found (None is received),
     * spin the loop. When the message with a solution arrives
     * (Some(Solution(i, hash))), print out the result and break
     * out from the loop.
     *
     * Note that breaking out from the loop drops the receiver,
     * so that producers will receive an Err(_) on a next attempt to
     * send a message. This way, producers will "know" that the solution
     * was found by one of the worker threads and the receiver does no
     * longer "listen", so they can stop their work too.
     */
    loop {
        match receiver.recv() {
            Ok(None) => continue,
            Ok(Some(Solution(i, hash))) => {
                println!("Found the solution.");
                println!("The number is: {}.", i);
                println!("Result hash: {}.", hash);
                break;
            },
            _ => {},
        }
    }
}
<commit_msg>wording in the message<commit_after>#![feature(iterator_step_by)]

extern crate easy_hash;

use easy_hash::{Sha256, Hasher, HashResult};
use std::thread;
use std::sync::mpsc;

const BASE: usize = 42;
const THREADS: usize = 4;
static DIFFICULTY: &'static str = "000000";

struct Solution(usize, String);

fn search_for_solution(start_at: usize, sender: mpsc::Sender<Option<Solution>>) {
    for i in (start_at..).step_by(THREADS) {
        let hash: String = Sha256::hash(format!("{}", i * BASE).as_bytes()).hex();

        let result = if hash.ends_with(DIFFICULTY) {
            Some(Solution(i, hash))
        } else {
            None
        };

        match sender.send(result) {
            Ok(_)  => {},
            Err(_) => {
                println!("Receiver has stopped listening, dropping worker number {}", start_at);
                break;
            },
        }
    }
}

fn main() {
    println!("Attempting to find a number, which - while multiplied by {} and hashed using SHA-256 - will result in a hash ending with {}.", BASE, DIFFICULTY);
    println!("Please wait...");

    /*
     * Here, we have 4 threads (as specified by the value of THREADS constant).
     * Thread 1 will start at number 1 and check 5, 9,  13 and so on.
     * Thread 2 will start at number 2 and check 6, 10, 14 and so on.
     * Thread 3 will start at number 3 and check 7, 11, 15 and so on.
     * Thread 4 will start at number 4 and check 8, 12, 16 and so on.
     *
     * This way, we have 4 parallel threads of execution and we're sure
     * that each number will be examined exactly once.
     */
    let (sender, receiver) = mpsc::channel();
    for i in 1..THREADS+1 {
        let sender_n = sender.clone();
        thread::spawn(move || {
            search_for_solution(i, sender_n);
        });
    }

    /*
     * In a loop, receive messages from the producers.
     * As long as there is no solution found (None is received),
     * spin the loop. When the message with a solution arrives
     * (Some(Solution(i, hash))), print out the result and break
     * out from the loop.
     *
     * Note that breaking out from the loop drops the receiver,
     * so that producers will receive an Err(_) on a next attempt to
     * send a message. This way, producers will "know" that the solution
     * was found by one of the worker threads and the receiver does no
     * longer "listen", so they can stop their work too.
     */
    loop {
        match receiver.recv() {
            Ok(None) => continue,
            Ok(Some(Solution(i, hash))) => {
                println!("Found the solution.");
                println!("The number is: {}.", i);
                println!("Result hash: {}.", hash);
                break;
            },
            _ => {},
        }
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(test, feature(test))]

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

use std::slice;

extern crate unicode_segmentation;
use unicode_segmentation::UnicodeSegmentation;

fn print_usage(program: &str) {
    println!(
"Usage: {} SHIFT [PLAINTEXT] [options]

Options:
    -h, --help      print this help menu", program);
}

fn transform<O: Write>(mut pipe: O, plaintext: &str, mut shift: i8) -> io::Result<usize> {
    while shift >= 26
        { shift -= 26; }
    while shift < 0
        { shift += 26; }
    debug_assert!(shift >= 0 && shift <= 26);

    let offset: u8 = shift as u8;
    // this will have to wait on `feature(stmt_expr_attributes)` (rust-lang/rust/pull/29850)
    // #[allow(unused_assignments)]
    let mut c = b'\0';

    for grapheme in plaintext.graphemes(true) {
        let bytes = if grapheme.len() == 1 {
            unsafe {
                let cres = *grapheme.as_bytes().get_unchecked(0) as char;
                match cres {
                    'a' ... 'z' => {
                        c = cres as u8 + offset;
                        if c > b'z'
                            { c -= 26; }
                    }
                    'A' ... 'Z' => {
                        c = cres as u8 + offset;
                        if c > b'Z'
                            { c -= 26 }
                    }
                    _ => c = cres as u8
                }
                slice::from_raw_parts(&c, 1)
            }
        } else {
            grapheme.as_bytes()
        };
        try!(pipe.write(bytes).map(|_|()));
    }
    Ok(0)
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    if args[1..].iter().map(|s|&s[..]).any(|s| s == "-h" || s == "--help") || args.len() <= 1 {
        print_usage(&program);
        return;
    }

    let shift: i8 = match args[1].parse() {
        Ok(v) => v,
        Err(f) => {
            println!("cannot parse shift value: `{}` ({})", args[1], f.to_string());
            return;
        },
    };

    let status = match args.get(2) {
        Some(input) => {
            let mut pipe = std::io::stdout();
            transform(&mut pipe, input, shift)
            .and(pipe.write(b"\n"))
        }
        None => {
            let mut input = String::new();
            std::io::stdin().read_to_string(&mut input)
            .and(transform(std::io::stdout(), &input, shift))
        }
    };
    if let Err(f) = status {
        println!("broken pipe: {}", f.to_string());
    }
}

#[cfg(test)]
mod tests {
    extern crate test;

    use self::test::Bencher;
    use super::{std, transform};
    use std::fs::File;
    use std::io::Read;

    macro_rules! reftest {
        ($plain: expr, $cipher: expr, 13) => ({
            let mut cipher = Vec::new();
            let mut cipher1 = Vec::new();
            transform(&mut cipher, $plain, 13);
            assert_eq!($cipher.as_bytes(), &cipher[..]);

            transform(&mut cipher1, std::str::from_utf8(&cipher).unwrap(), 13);
            assert_eq!($plain.as_bytes(), &cipher1[..]);
        });
        ($plain: expr, $cipher: expr, $shift: expr) => ({
            let mut cipher = Vec::new();
            transform(&mut cipher, $plain, $shift);
            assert_eq!($cipher.as_bytes(), &cipher[..]);
        })
    }

    #[test]
    fn alphabet() {
        reftest!("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "XYZABCDEFGHIJKLMNOPQRSTUVW", 23);
        reftest!(
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
            "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm", 13
        );
    }

    #[test]
    fn storytime() {
        reftest!("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG", "QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD", -3);
        reftest!("defend the east wall of the castle", "efgfoe uif fbtu xbmm pg uif dbtumf", 1);
    }

    #[bench]
    fn lorem_ipsum_stdout(b: &mut Bencher) {
        let mut input = String::new();
        let mut f = File::open("auxiliary/lorem_ipsum").unwrap();
        f.read_to_string(&mut input);

        b.iter(|| transform(std::io::stdout(), &input, 13));
    }

    #[bench]
    fn lorem_ipsum_string(b: &mut Bencher) {
        let mut input = String::new();
        let mut f = File::open("auxiliary/lorem_ipsum").unwrap();
        f.read_to_string(&mut input);

        b.iter(|| transform(&mut Vec::new(), &input, 13));
    }
}
<commit_msg>Replace naive wrapping code<commit_after>#![cfg_attr(test, feature(test))]
#![feature(stmt_expr_attributes)]

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

use std::slice;

extern crate unicode_segmentation;
use unicode_segmentation::UnicodeSegmentation;

fn print_usage(program: &str) {
    println!(
"Usage: {} SHIFT [PLAINTEXT] [options]

Options:
    -h, --help      print this help menu", program);
}

fn transform<O: Write>(mut pipe: O, plaintext: &str, ishift: i8) -> io::Result<usize> {
    // Wrap shift value to 0...26 so we
    //  -only have to worry about the upper bound
    //  -only need to wrap around a single time without checking
    let shift = if ishift >= 0 {
        ishift % 26
    } else {
        26 + (ishift % 26)
    };

    #[allow(unused_assignments)]
    let mut c = b'\0';
    let offset: u8 = shift as u8;

    for grapheme in plaintext.graphemes(true) {
        let bytes = if grapheme.len() == 1 {
            unsafe {
                let cres = *grapheme.as_bytes().get_unchecked(0) as char;
                match cres {
                    'a' ... 'z' => {
                        c = cres as u8 + offset;
                        if c > b'z'
                            { c -= 26; }
                    }
                    'A' ... 'Z' => {
                        c = cres as u8 + offset;
                        if c > b'Z'
                            { c -= 26 }
                    }
                    _ => c = cres as u8
                }
                slice::from_raw_parts(&c, 1)
            }
        } else {
            grapheme.as_bytes()
        };
        try!(pipe.write(bytes).map(|_|()));
    }
    Ok(0)
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    if args[1..].iter().map(|s|&s[..]).any(|s| s == "-h" || s == "--help") || args.len() <= 1 {
        print_usage(&program);
        return;
    }

    let shift: i8 = match args[1].parse() {
        Ok(v) => v,
        Err(f) => {
            println!("cannot parse shift value: `{}` ({})", args[1], f.to_string());
            return;
        },
    };

    let status = match args.get(2) {
        Some(input) => {
            let mut pipe = std::io::stdout();
            transform(&mut pipe, input, shift)
            .and(pipe.write(b"\n"))
        }
        None => {
            let mut input = String::new();
            std::io::stdin().read_to_string(&mut input)
            .and(transform(std::io::stdout(), &input, shift))
        }
    };
    if let Err(f) = status {
        println!("broken pipe: {}", f.to_string());
    }
}

#[cfg(test)]
mod tests {
    extern crate test;

    use self::test::Bencher;
    use super::{std, transform};
    use std::fs::File;
    use std::io::Read;

    macro_rules! reftest {
        ($plain: expr, $cipher: expr, 13) => ({
            let mut cipher = Vec::new();
            let mut cipher1 = Vec::new();
            transform(&mut cipher, $plain, 13);
            assert_eq!($cipher.as_bytes(), &cipher[..]);

            transform(&mut cipher1, std::str::from_utf8(&cipher).unwrap(), 13);
            assert_eq!($plain.as_bytes(), &cipher1[..]);
        });
        ($plain: expr, $cipher: expr, $shift: expr) => ({
            let mut cipher = Vec::new();
            transform(&mut cipher, $plain, $shift);
            assert_eq!($cipher.as_bytes(), &cipher[..]);
        })
    }

    #[test]
    fn alphabet() {
        reftest!("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "XYZABCDEFGHIJKLMNOPQRSTUVW", 23);
        reftest!(
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
            "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm", 13
        );
    }

    #[test]
    fn storytime() {
        reftest!("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG", "QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD", -3);
        reftest!("defend the east wall of the castle", "efgfoe uif fbtu xbmm pg uif dbtumf", 1);
    }

    #[bench]
    fn lorem_ipsum_stdout(b: &mut Bencher) {
        let mut input = String::new();
        let mut f = File::open("auxiliary/lorem_ipsum").unwrap();
        f.read_to_string(&mut input);

        b.iter(|| transform(std::io::stdout(), &input, 13));
    }

    #[bench]
    fn lorem_ipsum_string(b: &mut Bencher) {
        let mut input = String::new();
        let mut f = File::open("auxiliary/lorem_ipsum").unwrap();
        f.read_to_string(&mut input);

        b.iter(|| transform(&mut Vec::new(), &input, 13));
    }
}
<|endoftext|>


<commit_before>use std::io::{Write, BufWriter};
use std::fs::File;

extern crate rand;
use rand::Rng;

extern crate image;
use image::{
  GenericImage,
  ImageBuffer
};

mod vector;
mod ray;
mod sphere;
mod hitable;
mod hitable_list;
mod camera;
mod material;

use vector::{Vec3, dot, cross, unit_vector};
use ray::Ray;
use sphere::{Sphere};
use hitable::{Hitable, HitRecord};
use hitable_list::HitableList;
use camera::Camera;

fn color<T: Hitable>(ray : Ray, world : &T) -> Vec3 {
  let mut rec = HitRecord::new(0.0, Vec3::origin(), Vec3::origin());
  if world.hit(ray, 0.0, std::f64::MAX, &mut rec) {
    let target = rec.p + rec.normal + random_in_unit_sphere();
    return color(Ray::new(rec.p, target - rec.p), world) * 0.5
  }
  else {
    let unit_direction = unit_vector(ray.direction());
    let t = (unit_direction.y() + 1.0) * 0.5;
    return Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t;
  }
}

fn random_in_unit_sphere() -> Vec3 {
  let mut p : Vec3;
  let mut rng = rand::thread_rng();
  loop {
    p = Vec3::new(rng.gen::<f64>(),rng.gen::<f64>(),rng.gen::<f64>()) * 2.0 - Vec3::all(1.0);
    if dot(p, p) < 1.0 { break; }
  }
  p
}

fn random<T: Hitable>(i : i32, j : i32, nx : i32, ny : i32, cam : Camera, world : &T, rng : &mut rand::ThreadRng) -> Vec3 {
  let sample_size = 100;
  let mut samples = 0;
  let mut col = Vec3::origin();
  loop {
    let u = (i as f64 + rng.gen::<f64>()) / nx as f64;
    let v = (j as f64 + rng.gen::<f64>()) / ny as f64;
    let r = cam.get_ray(u, v);

    col += color(r, world);
    samples += 1;
    if samples >= sample_size { break; }
  }

  col /= sample_size as f64;
  return col;
}

fn stratified<T: Hitable>(i : i32, j : i32, nx : i32, ny : i32, cam : Camera, world : &T, rng : &mut rand::ThreadRng) -> Vec3 {
  let mut col = Vec3::origin();
  let grid_size = 5;
  let step = 1.0 / grid_size as f64;
  let low_i = i as f64 - 0.5;
  let low_j = j as f64 - 0.5;

  for ii in 0..grid_size {
    for jj in 0..grid_size {
      let u = ((low_i + ii as f64 * step) + rng.gen_range(0.0, step)) / nx as f64;
      let v = ((low_j + jj as f64 * step) + rng.gen_range(0.0, step)) / ny as f64;
      let r = cam.get_ray(u, v);

      col += color(r, world);
    }
  }

  col /= (grid_size * grid_size) as f64;
  return col;
}

fn main() {
  let nx = 200;
  let ny = 100;
  let mut img = ImageBuffer::new(nx as u32, ny as u32);
  let mut f = File::create("target/image.png").expect("Unable to create file");

  let upper_left_corner = Vec3::new(-2.0, 1.0, -1.0);
  let horizontal = Vec3::new(4.0, 0.0, 0.0);
  let vertical = Vec3::new(0.0, 2.0, 0.0);
  let origin = Vec3::new(0.0, 0.0, 0.0);

  let cam = Camera::new(upper_left_corner, vertical, horizontal, origin);

  let mut list : Vec<Box<Hitable>> = Vec::new();
  list.push(Box::new(Sphere::new(Vec3::new(0.0,0.0,-1.0), 0.5)));
  list.push(Box::new(Sphere::new(Vec3::new(0.0,-100.5,-1.0), 100.0)));
  let world = HitableList::new(list);

  let mut rng = rand::thread_rng();

  for (x, y, pixel) in img.enumerate_pixels_mut() {
    let mut col = stratified(x as i32, y as i32, nx, ny, cam, &world, &mut rng);
    col = col.sqrt();
    *pixel = image::Rgb([(col.i * 255.0) as u8, (col.j * 255.0) as u8, (col.k * 255.0) as u8]);
  }

  let _ = image::ImageRgb8(img).save(&mut f, image::PNG);
}<commit_msg>Fix compiler warnings and remove unused imports<commit_after>use std::fs::File;

extern crate rand;
use rand::Rng;

extern crate image;
use image::{
  ImageBuffer
};

mod vector;
mod ray;
mod sphere;
mod hitable;
mod hitable_list;
mod camera;
mod material;

use vector::{Vec3, dot, unit_vector};
use ray::Ray;
use sphere::{Sphere};
use hitable::{Hitable, HitRecord};
use hitable_list::HitableList;
use camera::Camera;

fn color<T: Hitable>(ray : Ray, world : &T) -> Vec3 {
  let mut rec = HitRecord::new(0.0, Vec3::origin(), Vec3::origin());
  if world.hit(ray, 0.0, std::f64::MAX, &mut rec) {
    let target = rec.p + rec.normal + random_in_unit_sphere();
    return color(Ray::new(rec.p, target - rec.p), world) * 0.5
  }
  else {
    let unit_direction = unit_vector(ray.direction());
    let t = (unit_direction.y() + 1.0) * 0.5;
    return Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t;
  }
}

fn random_in_unit_sphere() -> Vec3 {
  let mut p : Vec3;
  let mut rng = rand::thread_rng();
  loop {
    p = Vec3::new(rng.gen::<f64>(),rng.gen::<f64>(),rng.gen::<f64>()) * 2.0 - Vec3::all(1.0);
    if dot(p, p) < 1.0 { break; }
  }
  p
}

#[allow(dead_code)]
fn random<T: Hitable>(i : i32, j : i32, nx : i32, ny : i32, cam : Camera, world : &T, rng : &mut rand::ThreadRng) -> Vec3 {
  let sample_size = 100;
  let mut samples = 0;
  let mut col = Vec3::origin();
  loop {
    let u = (i as f64 + rng.gen::<f64>()) / nx as f64;
    let v = (j as f64 + rng.gen::<f64>()) / ny as f64;
    let r = cam.get_ray(u, v);

    col += color(r, world);
    samples += 1;
    if samples >= sample_size { break; }
  }

  col /= sample_size as f64;
  return col;
}

fn stratified<T: Hitable>(i : i32, j : i32, nx : i32, ny : i32, cam : Camera, world : &T, rng : &mut rand::ThreadRng) -> Vec3 {
  let mut col = Vec3::origin();
  let grid_size = 5;
  let step = 1.0 / grid_size as f64;
  let low_i = i as f64 - 0.5;
  let low_j = j as f64 - 0.5;

  for ii in 0..grid_size {
    for jj in 0..grid_size {
      let u = ((low_i + ii as f64 * step) + rng.gen_range(0.0, step)) / nx as f64;
      let v = ((low_j + jj as f64 * step) + rng.gen_range(0.0, step)) / ny as f64;
      let r = cam.get_ray(u, v);

      col += color(r, world);
    }
  }

  col /= (grid_size * grid_size) as f64;
  return col;
}

fn main() {
  let nx = 200;
  let ny = 100;
  let mut img = ImageBuffer::new(nx as u32, ny as u32);
  let mut f = File::create("target/image.png").expect("Unable to create file");

  let upper_left_corner = Vec3::new(-2.0, 1.0, -1.0);
  let horizontal = Vec3::new(4.0, 0.0, 0.0);
  let vertical = Vec3::new(0.0, 2.0, 0.0);
  let origin = Vec3::new(0.0, 0.0, 0.0);

  let cam = Camera::new(upper_left_corner, vertical, horizontal, origin);

  let mut list : Vec<Box<Hitable>> = Vec::new();
  list.push(Box::new(Sphere::new(Vec3::new(0.0,0.0,-1.0), 0.5)));
  list.push(Box::new(Sphere::new(Vec3::new(0.0,-100.5,-1.0), 100.0)));
  let world = HitableList::new(list);

  let mut rng = rand::thread_rng();

  for (x, y, pixel) in img.enumerate_pixels_mut() {
    let mut col = stratified(x as i32, y as i32, nx, ny, cam, &world, &mut rng);
    col = col.sqrt();
    *pixel = image::Rgb([(col.i * 255.0) as u8, (col.j * 255.0) as u8, (col.k * 255.0) as u8]);
  }

  let _ = image::ImageRgb8(img).save(&mut f, image::PNG);
}<|endoftext|>


<commit_before>#![allow(unstable)]
#![feature(plugin)]
extern crate regex;
#[plugin] #[no_link] extern crate regex_macros;
extern crate "rustc-serialize" as rustc_serialize;
extern crate docopt;
#[plugin] extern crate docopt_macros;
#[macro_use] extern crate log;
extern crate term;
extern crate delivery;

use std::os;
use std::error;
use delivery::utils::say::{say, sayln};
use delivery::errors::{DeliveryError, Kind};
use delivery::config::Config;
use delivery::git;

docopt!(Args derive Show, "
Usage: delivery review [--for=<pipeline>]
       delivery clone <project> [--user=<user>] [--server=<server>] [--ent=<ent>] [--org=<org>] [--git-url=<url>]
       delivery checkout <change> [--for=<pipeline>] [--patchset=<number>]
       delivery diff <change> [--for=<pipeline>] [--patchset=<number>] [--local]
       delivery init [--user=<user>] [--server=<server>] [--ent=<ent>] [--org=<org>] [--project=<project>]
       delivery setup [--user=<user>] [--server=<server>] [--ent=<ent>] [--org=<org>] [--config-path=<dir>]
       delivery --help

Options:
  -h, --help               Show this message.
  -f, --for=<pipeline>     A pipeline to target
  -P, --patchset=<number>  A patchset number [default: latest]
  -u, --user=<user>        A delivery username
  -s, --server=<server>    A delivery server
  -e, --ent=<ent>          A delivery enterprise
  -o, --org=<org>          A delivery organization
  -p, --project=<project>  The project name
  -c, --config-path=<dir>  The directory to write a config to
  -l, --local              Diff against the local branch HEAD
  -g, --git-url=<url>      A raw git URL
  <change>                 The change to checkout
");

macro_rules! validate {
    ($config:ident, $value:ident) => (
        try!($config.clone().$value());
    )
}

#[cfg(not(test))]
fn main() {
    let args: Args = Args::docopt().decode().unwrap_or_else(|e| e.exit());
    // debug!("{}", args);
    let cmd_result = match args {
        Args {
            cmd_review: true,
            flag_for: ref for_pipeline,
            ..
        } => review(&for_pipeline[]),
        Args {
            cmd_setup: true,
            flag_user: ref user,
            flag_server: ref server,
            flag_ent: ref ent,
            flag_org: ref org,
            flag_config_path: ref path,
            ..
        } => setup(user.as_slice(), server.as_slice(), ent.as_slice(), org.as_slice(), path.as_slice()),
        Args {
            cmd_init: true,
            flag_user: ref user,
            flag_server: ref server,
            flag_ent: ref ent,
            flag_org: ref org,
            flag_project: ref proj,
            ..
        } => init(user.as_slice(), server.as_slice(), ent.as_slice(), org.as_slice(), proj.as_slice()),
        Args {
            cmd_checkout: true,
            arg_change: ref change,
            flag_patchset: ref patchset,
            flag_for: ref pipeline,
            ..
        } => checkout(change.as_slice(), patchset.as_slice(), pipeline.as_slice()),
        Args {
            cmd_diff: true,
            arg_change: ref change,
            flag_patchset: ref patchset,
            flag_for: ref pipeline,
            flag_local: ref local,
            ..
        } => diff(change.as_slice(), patchset.as_slice(), pipeline.as_slice(), local),
        Args {
            cmd_clone: true,
            arg_project: ref project,
            flag_user: ref user,
            flag_server: ref server,
            flag_ent: ref ent,
            flag_org: ref org,
            flag_git_url: ref git_url,
            ..
        } => clone(project.as_slice(), user.as_slice(), server.as_slice(), ent.as_slice(), org.as_slice(), git_url.as_slice()),
        _ => no_matching_command(),
    };
    match cmd_result {
        Ok(_) => {},
        Err(e) => exit_with(e, 1)
    }
}

fn cwd() -> Path {
    os::getcwd().unwrap()
}

fn no_matching_command() -> Result<(), DeliveryError> {
    Err(DeliveryError { kind: Kind::NoMatchingCommand, detail: None })
}

fn exit_with<T: error::Error>(e: T, i: isize) {
    sayln("red", e.description());
    match e.detail() {
        Some(deets) => sayln("red", deets.as_slice()),
        None => {}
    }
    os::set_exit_status(i)
}

fn setup(user: &str, server: &str, ent: &str, org: &str, path: &str) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let config_path = if path.is_empty() {
        cwd()
    } else {
        Path::new(path)
    };
    let mut config = try!(Config::load_config(&config_path));
    config = config.set_server(server)
        .set_user(user)
        .set_enterprise(ent)
        .set_organization(org);
    try!(config.write_file(&config_path));
    Ok(())
}

fn init(user: &str, server: &str, ent: &str, org: &str, proj: &str) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let mut config = try!(Config::load_config(&cwd()));
    // Since we wind up taking the filename as a reference, we need to
    // have its scope be the entire method. Sadly, it means we call it
    // whether we need to or not. We could probably abstract this into
    // a function and get the lifetimes right, but.. meh :)
    let cwd = try!(os::getcwd());
    let final_proj = if proj.is_empty() {
        let cwd_name = cwd.filename().unwrap();
        std::str::from_utf8(cwd_name.clone()).unwrap()
    } else {
        proj
    };
    config = config.set_user(user)
        .set_server(server)
        .set_enterprise(ent)
        .set_organization(org)
        .set_project(final_proj);
    let u = validate!(config, user);
    let s = validate!(config, server);
    let e = validate!(config, enterprise);
    let o = validate!(config, organization);
    let p = validate!(config, project);
    try!(git::config_repo(
            u.as_slice(),
            s.as_slice(),
            e.as_slice(),
            o.as_slice(),
            p.as_slice()));
    sayln("white", "Configuration added!");
    Ok(())
}

fn review(for_pipeline: &str) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let mut config = try!(Config::load_config(&cwd()));
    config = config.set_pipeline(for_pipeline);
    let target = validate!(config, pipeline);
    say("white", "Review for change  ");
    let head = try!(git::get_head());
    if for_pipeline == head.as_slice() {
        return Err(DeliveryError{ kind: Kind::CannotReviewSameBranch, detail: None })
    }
    say("yellow", head.as_slice());
    say("white", " targeted for pipeline ");
    sayln("magenta", target.as_slice());
    try!(git::git_push(head.as_slice(), target.as_slice()));
    Ok(())
}

fn checkout(change: &str, patchset: &str, pipeline: &str) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let mut config = try!(Config::load_config(&cwd()));
    config = config.set_pipeline(pipeline);
    let target = validate!(config, pipeline);
    say("white", "Checking out ");
    say("yellow", change);
    say("white", " targeted for pipeline ");
    say("magenta", target.as_slice());

    if patchset == "latest" {
        sayln("white", " tracking latest changes");
    } else {
        say("white", " at patchset ");
        sayln("yellow", patchset);
    }
    try!(git::checkout_review(change, patchset, target.as_slice()));
    Ok(())
}

fn diff(change: &str, patchset: &str, pipeline: &str, local: &bool) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let mut config = try!(Config::load_config(&cwd()));
    config = config.set_pipeline(pipeline);
    let target = validate!(config, pipeline);
    say("white", "Showing diff for ");
    say("yellow", change);
    say("white", " targeted for pipeline ");
    say("magenta", target.as_slice());

    if patchset == "latest" {
        sayln("white", " latest patchset");
    } else {
        say("white", " at patchset ");
        sayln("yellow", patchset);
    }
    try!(git::diff(change, patchset, target.as_slice(), local));
    Ok(())
}

fn clone(project: &str, user: &str, server: &str, ent: &str, org: &str, git_url: &str) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let mut config = try!(Config::load_config(&cwd()));
    config = config.set_user(user)
        .set_server(server)
        .set_enterprise(ent)
        .set_organization(org);
    let u = validate!(config, user);
    let s = validate!(config, server);
    let e = validate!(config, enterprise);
    let o = validate!(config, organization);
    say("white", "Cloning ");
    let clone_url = if git_url.is_empty() {
        say("yellow", format!("{}/{}/{}", e, o, project).as_slice());
        git::delivery_ssh_url(u.as_slice(), s.as_slice(), e.as_slice(), o.as_slice(), project)
    } else {
        say("yellow", git_url);
        String::from_str(git_url)
    };
    say("white", " to ");
    sayln("magenta", format!("{}", project).as_slice());
    try!(git::clone(project, clone_url.as_slice()));
    let project_root = cwd().join(project);
    try!(os::change_dir(&project_root));
    try!(git::config_repo(u.as_slice(),
                          s.as_slice(),
                          e.as_slice(),
                          o.as_slice(),
                          project));
    Ok(())
}

<commit_msg>Fixing a bug that let you review non feature branches<commit_after>#![allow(unstable)]
#![feature(plugin)]
extern crate regex;
#[plugin] #[no_link] extern crate regex_macros;
extern crate "rustc-serialize" as rustc_serialize;
extern crate docopt;
#[plugin] extern crate docopt_macros;
#[macro_use] extern crate log;
extern crate term;
extern crate delivery;

use std::os;
use std::error;
use delivery::utils::say::{say, sayln};
use delivery::errors::{DeliveryError, Kind};
use delivery::config::Config;
use delivery::git;

docopt!(Args derive Show, "
Usage: delivery review [--for=<pipeline>]
       delivery clone <project> [--user=<user>] [--server=<server>] [--ent=<ent>] [--org=<org>] [--git-url=<url>]
       delivery checkout <change> [--for=<pipeline>] [--patchset=<number>]
       delivery diff <change> [--for=<pipeline>] [--patchset=<number>] [--local]
       delivery init [--user=<user>] [--server=<server>] [--ent=<ent>] [--org=<org>] [--project=<project>]
       delivery setup [--user=<user>] [--server=<server>] [--ent=<ent>] [--org=<org>] [--config-path=<dir>]
       delivery --help

Options:
  -h, --help               Show this message.
  -f, --for=<pipeline>     A pipeline to target
  -P, --patchset=<number>  A patchset number [default: latest]
  -u, --user=<user>        A delivery username
  -s, --server=<server>    A delivery server
  -e, --ent=<ent>          A delivery enterprise
  -o, --org=<org>          A delivery organization
  -p, --project=<project>  The project name
  -c, --config-path=<dir>  The directory to write a config to
  -l, --local              Diff against the local branch HEAD
  -g, --git-url=<url>      A raw git URL
  <change>                 The change to checkout
");

macro_rules! validate {
    ($config:ident, $value:ident) => (
        try!($config.clone().$value());
    )
}

#[cfg(not(test))]
fn main() {
    let args: Args = Args::docopt().decode().unwrap_or_else(|e| e.exit());
    // debug!("{}", args);
    let cmd_result = match args {
        Args {
            cmd_review: true,
            flag_for: ref for_pipeline,
            ..
        } => review(&for_pipeline[]),
        Args {
            cmd_setup: true,
            flag_user: ref user,
            flag_server: ref server,
            flag_ent: ref ent,
            flag_org: ref org,
            flag_config_path: ref path,
            ..
        } => setup(user.as_slice(), server.as_slice(), ent.as_slice(), org.as_slice(), path.as_slice()),
        Args {
            cmd_init: true,
            flag_user: ref user,
            flag_server: ref server,
            flag_ent: ref ent,
            flag_org: ref org,
            flag_project: ref proj,
            ..
        } => init(user.as_slice(), server.as_slice(), ent.as_slice(), org.as_slice(), proj.as_slice()),
        Args {
            cmd_checkout: true,
            arg_change: ref change,
            flag_patchset: ref patchset,
            flag_for: ref pipeline,
            ..
        } => checkout(change.as_slice(), patchset.as_slice(), pipeline.as_slice()),
        Args {
            cmd_diff: true,
            arg_change: ref change,
            flag_patchset: ref patchset,
            flag_for: ref pipeline,
            flag_local: ref local,
            ..
        } => diff(change.as_slice(), patchset.as_slice(), pipeline.as_slice(), local),
        Args {
            cmd_clone: true,
            arg_project: ref project,
            flag_user: ref user,
            flag_server: ref server,
            flag_ent: ref ent,
            flag_org: ref org,
            flag_git_url: ref git_url,
            ..
        } => clone(project.as_slice(), user.as_slice(), server.as_slice(), ent.as_slice(), org.as_slice(), git_url.as_slice()),
        _ => no_matching_command(),
    };
    match cmd_result {
        Ok(_) => {},
        Err(e) => exit_with(e, 1)
    }
}

fn cwd() -> Path {
    os::getcwd().unwrap()
}

fn no_matching_command() -> Result<(), DeliveryError> {
    Err(DeliveryError { kind: Kind::NoMatchingCommand, detail: None })
}

fn exit_with<T: error::Error>(e: T, i: isize) {
    sayln("red", e.description());
    match e.detail() {
        Some(deets) => sayln("red", deets.as_slice()),
        None => {}
    }
    os::set_exit_status(i)
}

fn setup(user: &str, server: &str, ent: &str, org: &str, path: &str) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let config_path = if path.is_empty() {
        cwd()
    } else {
        Path::new(path)
    };
    let mut config = try!(Config::load_config(&config_path));
    config = config.set_server(server)
        .set_user(user)
        .set_enterprise(ent)
        .set_organization(org);
    try!(config.write_file(&config_path));
    Ok(())
}

fn init(user: &str, server: &str, ent: &str, org: &str, proj: &str) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let mut config = try!(Config::load_config(&cwd()));
    // Since we wind up taking the filename as a reference, we need to
    // have its scope be the entire method. Sadly, it means we call it
    // whether we need to or not. We could probably abstract this into
    // a function and get the lifetimes right, but.. meh :)
    let cwd = try!(os::getcwd());
    let final_proj = if proj.is_empty() {
        let cwd_name = cwd.filename().unwrap();
        std::str::from_utf8(cwd_name.clone()).unwrap()
    } else {
        proj
    };
    config = config.set_user(user)
        .set_server(server)
        .set_enterprise(ent)
        .set_organization(org)
        .set_project(final_proj);
    let u = validate!(config, user);
    let s = validate!(config, server);
    let e = validate!(config, enterprise);
    let o = validate!(config, organization);
    let p = validate!(config, project);
    try!(git::config_repo(
            u.as_slice(),
            s.as_slice(),
            e.as_slice(),
            o.as_slice(),
            p.as_slice()));
    sayln("white", "Configuration added!");
    Ok(())
}

fn review(for_pipeline: &str) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let mut config = try!(Config::load_config(&cwd()));
    config = config.set_pipeline(for_pipeline);
    let target = validate!(config, pipeline);
    say("white", "Review for change  ");
    let head = try!(git::get_head());
    if target.as_slice() == head.as_slice() {
        return Err(DeliveryError{ kind: Kind::CannotReviewSameBranch, detail: None })
    }
    say("yellow", head.as_slice());
    say("white", " targeted for pipeline ");
    sayln("magenta", target.as_slice());
    try!(git::git_push(head.as_slice(), target.as_slice()));
    Ok(())
}

fn checkout(change: &str, patchset: &str, pipeline: &str) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let mut config = try!(Config::load_config(&cwd()));
    config = config.set_pipeline(pipeline);
    let target = validate!(config, pipeline);
    say("white", "Checking out ");
    say("yellow", change);
    say("white", " targeted for pipeline ");
    say("magenta", target.as_slice());

    if patchset == "latest" {
        sayln("white", " tracking latest changes");
    } else {
        say("white", " at patchset ");
        sayln("yellow", patchset);
    }
    try!(git::checkout_review(change, patchset, target.as_slice()));
    Ok(())
}

fn diff(change: &str, patchset: &str, pipeline: &str, local: &bool) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let mut config = try!(Config::load_config(&cwd()));
    config = config.set_pipeline(pipeline);
    let target = validate!(config, pipeline);
    say("white", "Showing diff for ");
    say("yellow", change);
    say("white", " targeted for pipeline ");
    say("magenta", target.as_slice());

    if patchset == "latest" {
        sayln("white", " latest patchset");
    } else {
        say("white", " at patchset ");
        sayln("yellow", patchset);
    }
    try!(git::diff(change, patchset, target.as_slice(), local));
    Ok(())
}

fn clone(project: &str, user: &str, server: &str, ent: &str, org: &str, git_url: &str) -> Result<(), DeliveryError> {
    sayln("green", "Chef Delivery");
    let mut config = try!(Config::load_config(&cwd()));
    config = config.set_user(user)
        .set_server(server)
        .set_enterprise(ent)
        .set_organization(org);
    let u = validate!(config, user);
    let s = validate!(config, server);
    let e = validate!(config, enterprise);
    let o = validate!(config, organization);
    say("white", "Cloning ");
    let clone_url = if git_url.is_empty() {
        say("yellow", format!("{}/{}/{}", e, o, project).as_slice());
        git::delivery_ssh_url(u.as_slice(), s.as_slice(), e.as_slice(), o.as_slice(), project)
    } else {
        say("yellow", git_url);
        String::from_str(git_url)
    };
    say("white", " to ");
    sayln("magenta", format!("{}", project).as_slice());
    try!(git::clone(project, clone_url.as_slice()));
    let project_root = cwd().join(project);
    try!(os::change_dir(&project_root));
    try!(git::config_repo(u.as_slice(),
                          s.as_slice(),
                          e.as_slice(),
                          o.as_slice(),
                          project));
    Ok(())
}

<|endoftext|>