<commit_before>use std::cmp;
use rand::{
    Rng,
    seq::SliceRandom,
    thread_rng,
};
use crate::{
    game::GameState,
    ship::{
        Ship,
        ShipDirection,
    },
    space::Space,
};

pub struct Player {
    pub is_cpu: bool,
    spaces: Vec<Space>,
    ships: Vec<Ship>,
    grid_size: [u8; 2],
    grid_cursor: [u8; 2],
    pub temp_ship_pos: Vec<[u8; 2]>,
    pub temp_ship_dir: ShipDirection,
}

impl Player {
    pub fn new(grid_size: [u8; 2], is_cpu: bool) -> Player {
        let spaces_x = grid_size[0] as usize;
        let spaces_y = grid_size[1] as usize;
        let mut spaces = Vec::with_capacity(spaces_x * spaces_y);

        for col in 0..grid_size[0] {
            for row in 0..grid_size[1] {
                spaces.push(Space::new([col, row]));
            }
        }

        Player {
            is_cpu: is_cpu,
            spaces: spaces,
            ships: vec![],
            grid_size: grid_size,
            grid_cursor: [0, 0],
            temp_ship_pos: vec![[0, 0], [1, 0]],
            temp_ship_dir: ShipDirection::West,
        }
    }

    /// Selects a space and checks the status of ships if there's a hit.
    pub fn select_space(&mut self, pos: &[u8; 2]) -> GameState {
        let mut game_state = GameState::Active;
        let ship_hit = self.ships.iter().position(|s| s.pos().contains(pos));

        let space_index = self.space_index(pos);
        self.spaces[space_index].set_checked(ship_hit.is_some());

        if let Some(ship) = ship_hit {
            let ship_sunk = self.ships[ship].pos().iter()
                .all(|p| self.space(p).is_hit());

            if ship_sunk {
                self.ships[ship].sink();
            }

            let all_sunk = self.ships.iter().all(|s| !s.is_active());
            if all_sunk {
                game_state = GameState::Over;
            }
        }

        game_state
    }

    /// Determines the next space a CPU player will select.
    pub fn cpu_select_space(&self) -> [u8; 2] {
        let mut rng = thread_rng();
        let mut select = Vec::new();
        let mut directions = [
            ShipDirection::North,
            ShipDirection::East,
            ShipDirection::South,
            ShipDirection::West
        ];

        directions.shuffle(&mut rng);

        let mut hit_spaces = self.spaces.iter()
            .filter(|s| s.is_hit())
            .filter(|s| self.ship(s.pos()).unwrap().is_active())
            .collect::<Vec<&Space>>();

        hit_spaces.shuffle(&mut rng);

        // Check for a line of hit spaces.
        for space in &hit_spaces {
            for direction in &directions {
                let unchecked = self.find_unchecked_space(
                    space.pos(),
                    *direction,
                    true
                );

                if let Some(pos) = unchecked {
                    if !select.contains(&pos) {
                        select.push(pos);
                    }
                }
            }
        }

        // If a hit space was found, but no hit spaces next to it, find any
        // unchecked spaces next to it.
        if hit_spaces.len() > 0 && select.is_empty() {
            for direction in &directions {
                let unchecked = self.find_unchecked_space(
                    hit_spaces[0].pos(),
                    *direction,
                    false
                );

                if let Some(pos) = unchecked {
                    select.push(pos);
                }
            }
        }

        // If no spaces have been selected, just select any available space.
        if select.is_empty() {
            let pos: [u8; 2] = loop {
                let space = self.rng_pos();

                if self.space(&space).is_unchecked() {
                    break space;
                }
            };

            select.push(pos);
        }

        if select.len() > 1 {
            select.shuffle(&mut rng);
        }

        select[0]
    }

    fn rng_pos(&self) -> [u8; 2] {
        let mut rng = thread_rng();

        [
            rng.gen_range(0, self.grid_size[0]),
            rng.gen_range(0, self.grid_size[1]),
        ]
    }

    pub fn move_temp_ship(&mut self, direction: ShipDirection) {
        let old_head = self.temp_ship_pos[0];

        if let Some(new_head) = self.movement(&old_head, direction) {
            if let Some(ship) = self.get_ship_position(
                new_head,
                self.temp_ship_dir,
                self.temp_ship_pos.len() as u8
            ) {
                self.temp_ship_pos = ship;
            }
        }
    }

    pub fn place_temp_ship(&mut self) {
        let ship = self.temp_ship_pos.clone();

        if self.valid_ship_position(&ship) {
            self.ships.push(Ship::new(ship));

            self.temp_ship_dir = ShipDirection::West;
            self.temp_ship_pos = self.get_ship_position(
                [0, 0],
                self.temp_ship_dir,
                self.temp_ship_pos.len() as u8 + 1
            ).unwrap();
        }
    }

    /// Rotates a ship during the ship placement game state.
    pub fn rotate_temp_ship(&mut self) {
        let ship_len = self.temp_ship_pos.len() as u8;
        let dir = match self.temp_ship_dir {
            ShipDirection::North => ShipDirection::East,
            ShipDirection::East => ShipDirection::South,
            ShipDirection::South => ShipDirection::West,
            ShipDirection::West => ShipDirection::North,
        };

        // If the current starting position would cause the rotation to position
        // the ship partially out of bounds, adjust the starting position such
        // that the ship will be entirely within bounds.
        let old_start_pos = self.temp_ship_pos[0];
        let start_pos = match dir {
            ShipDirection::North => [
                self.temp_ship_pos[0][0],
                cmp::min(old_start_pos[1], self.grid_size[1] - ship_len),
            ],
            ShipDirection::East => [
                cmp::max(old_start_pos[0], ship_len - 1),
                self.temp_ship_pos[0][1],
            ],
            ShipDirection::South => [
                self.temp_ship_pos[0][0],
                cmp::max(old_start_pos[1], ship_len - 1),
            ],
            ShipDirection::West => [
                cmp::min(old_start_pos[0], self.grid_size[0] - ship_len),
                self.temp_ship_pos[0][1],
            ],
        };

        if let Some(ship) = self.get_ship_position(start_pos, dir, ship_len) {
            self.temp_ship_pos = ship;
            self.temp_ship_dir = dir;
        }
    }

    pub fn cpu_place_ships(&mut self) {
        for length in 2..6 {
            let ship_pos = self.cpu_place_ship(length);
            self.ships.push(Ship::new(ship_pos));
        }
    }

    /// RNGs ship locations for CPU players.
    fn cpu_place_ship(&self, length: u8) -> Vec<[u8; 2]> {
        let mut rng = thread_rng();

        // RNG a position and direction, then make sure it's valid.
        loop {
            let pos = self.rng_pos();
            let direction = match rng.gen_range(0, 4) {
                0 => ShipDirection::North,
                1 => ShipDirection::East,
                2 => ShipDirection::South,
                3 => ShipDirection::West,
                _ => unreachable!()
            };

            if let Some(s) = self.get_ship_position(pos, direction, length) {
                let valid = self.valid_ship_position(&s);

                if valid {
                    break s;
                }
            }
        }
    }

    /// Returns a ship position, given its head position, direction and length.
    ///
    /// `direction` refers to the direction the ship is facing, not the
    /// direction in which positions are generated.
    /// Returns `None` if the resulting ship position would not be contained
    /// within the grid.
    pub fn get_ship_position(
        &self,
        head: [u8; 2],
        direction: ShipDirection,
        length: u8
    ) -> Option<Vec<[u8; 2]>> {
        let valid = match direction {
            ShipDirection::North => head[1] + length <= self.grid_size[1],
            ShipDirection::East => head[0] >= length - 1,
            ShipDirection::South => head[1] >= length - 1,
            ShipDirection::West => head[0] + length <= self.grid_size[0],
        };

        let ship_opt = if valid {
            let mut ship = vec![head];

            for pos in 1..length {
                let pos_u8 = pos as u8;
                let space = match direction {
                    ShipDirection::North => [head[0], head[1] + pos_u8],
                    ShipDirection::East => [head[0] - pos_u8, head[1]],
                    ShipDirection::South => [head[0], head[1] - pos_u8],
                    ShipDirection::West => [head[0] + pos_u8, head[1]],
                };
                ship.push(space);
            }
            Some(ship)
        } else {
            None
        };

        ship_opt
    }

    /// Checks that the given ship position is valid.
    ///
    /// If the player is CPU-controlled, a ship in a space next to another ship
    /// will be considered invalid.
    pub fn valid_ship_position(&self, new_ship: &[[u8; 2]]) -> bool {
        new_ship.iter()
            .all(|s| self.valid_space(s) && !self.ship_is_in_space(s)
                 && !(self.ship_is_next_to(s) && self.is_cpu))
    }

    /// Gets a reference to the ships.
    pub fn ships(&self) -> &[Ship] {
        &self.ships
    }

    /// Gets a reference to a ship if it is in the given position.
    fn ship(&self, pos: &[u8; 2]) -> Option<&Ship> {
        self.ships.iter().find(|s| s.pos().contains(pos))
    }

    /// Returns whether a ship occupies the specified grid coordinates.
    pub fn ship_is_in_space(&self, pos: &[u8; 2]) -> bool {
        self.ships.iter().any(|s| s.pos().contains(pos))
    }

    /// Returns whether there is a ship next to the specified grid coordinates.
    fn ship_is_next_to(&self, pos: &[u8; 2]) -> bool {
        let mut result = false;
        let x = pos[0];
        let y = pos[1];

        // Left
        if x > 0 {
            result = self.ship_is_in_space(&[x - 1, y]);
        }

        // Right
        if x < self.grid_size[0] - 1 && !result {
            result = self.ship_is_in_space(&[x + 1, y]);
        }

        // Above
        if y > 0 && !result {
            result = self.ship_is_in_space(&[x, y - 1]);
        }

        // Below
        if y < self.grid_size[1] - 1 && !result {
            result = self.ship_is_in_space(&[x, y + 1]);
        }

        result
    }

    /// Gets a reference to the spaces.
    pub fn spaces(&self) -> &[Space] {
        &self.spaces
    }

    /// Returns whether the given position is valid.
    fn valid_space(&self, pos: &[u8; 2]) -> bool {
        pos[0] < self.grid_size[0] && pos[1] < self.grid_size[1]
    }

    /// Gets a reference to the space with the given position.
    pub fn space(&self, pos: &[u8; 2]) -> &Space {
        self.spaces.get(self.space_index(pos)).unwrap()
    }

    /// Calculates the index of the given position in the spaces vector.
    fn space_index(&self, pos: &[u8; 2]) -> usize {
        self.grid_size[0] as usize * pos[0] as usize + pos[1] as usize
    }

    /// Returns the coordinates of the player's grid cursor.
    pub fn get_grid_cursor(&self) -> [u8; 2] {
        self.grid_cursor.clone()
    }

    /// Returns the coordinates of a movement from `pos` in a `direction`.
    /// Returns `None` if the movement is not possible.
    pub fn movement(&self, pos: &[u8; 2], direction: ShipDirection) -> Option<[u8; 2]> {
        let valid = match direction {
            ShipDirection::North => pos[1] > 0,
            ShipDirection::East => pos[0] < self.grid_size[0] - 1,
            ShipDirection::South => pos[1] < self.grid_size[1] - 1,
            ShipDirection::West => pos[0] > 0,
        };

        if valid {
            let movement = match direction {
                ShipDirection::North => [pos[0], pos[1] - 1],
                ShipDirection::East => [pos[0] + 1, pos[1]],
                ShipDirection::South => [pos[0], pos[1] + 1],
                ShipDirection::West => [pos[0] - 1, pos[1]],
            };
            Some(movement)
        } else {
            None
        }
    }

    /// Finds the first non-hit, unchecked space in a `direction` from `pos`.
    /// Can also make sure the space is at the end of a `line` of hit spaces.
    /// Returns `None` if the first non-hit space has been checked or if a grid
    /// boundary is reached.
    fn find_unchecked_space(
        &self,
        pos: &[u8; 2],
        direction: ShipDirection,
        check_for_line: bool
    ) -> Option<[u8; 2]> {
        let mut check_pos = self.movement(pos, direction);

        while let Some(next_pos) = check_pos {
            let next_space = self.space(&next_pos);

            match next_space.is_hit() {
                true => check_pos = self.movement(&next_pos, direction),
                false => {
                    if !next_space.is_unchecked() {
                        check_pos = None;
                    }
                    break;
                }
            };
        }

        if check_for_line && check_pos.is_some() {
            let unchecked = check_pos.unwrap();
            let opposite_dir = direction.opposite();
            let prev_pos = self.movement(&unchecked, opposite_dir).unwrap();

            if &prev_pos == pos {
                check_pos = None;
            }
        }

        check_pos
    }

    /// Moves the player's grid cursor in the given `direction` if possible.
    pub fn move_grid_cursor(&mut self, direction: ShipDirection) {
        if let Some(new_cursor) = self.movement(&self.grid_cursor, direction) {
            self.set_grid_cursor(&new_cursor);
        }
    }

    /// Sets the player's grid cursor coordinates.
    pub fn set_grid_cursor(&mut self, new_cursor: &[u8; 2]) {
        self.grid_cursor = *new_cursor;
    }
}

<commit_msg>That didn't need two filters<commit_after>use std::cmp;
use rand::{
    Rng,
    seq::SliceRandom,
    thread_rng,
};
use crate::{
    game::GameState,
    ship::{
        Ship,
        ShipDirection,
    },
    space::Space,
};

pub struct Player {
    pub is_cpu: bool,
    spaces: Vec<Space>,
    ships: Vec<Ship>,
    grid_size: [u8; 2],
    grid_cursor: [u8; 2],
    pub temp_ship_pos: Vec<[u8; 2]>,
    pub temp_ship_dir: ShipDirection,
}

impl Player {
    pub fn new(grid_size: [u8; 2], is_cpu: bool) -> Player {
        let spaces_x = grid_size[0] as usize;
        let spaces_y = grid_size[1] as usize;
        let mut spaces = Vec::with_capacity(spaces_x * spaces_y);

        for col in 0..grid_size[0] {
            for row in 0..grid_size[1] {
                spaces.push(Space::new([col, row]));
            }
        }

        Player {
            is_cpu: is_cpu,
            spaces: spaces,
            ships: vec![],
            grid_size: grid_size,
            grid_cursor: [0, 0],
            temp_ship_pos: vec![[0, 0], [1, 0]],
            temp_ship_dir: ShipDirection::West,
        }
    }

    /// Selects a space and checks the status of ships if there's a hit.
    pub fn select_space(&mut self, pos: &[u8; 2]) -> GameState {
        let mut game_state = GameState::Active;
        let ship_hit = self.ships.iter().position(|s| s.pos().contains(pos));

        let space_index = self.space_index(pos);
        self.spaces[space_index].set_checked(ship_hit.is_some());

        if let Some(ship) = ship_hit {
            let ship_sunk = self.ships[ship].pos().iter()
                .all(|p| self.space(p).is_hit());

            if ship_sunk {
                self.ships[ship].sink();
            }

            let all_sunk = self.ships.iter().all(|s| !s.is_active());
            if all_sunk {
                game_state = GameState::Over;
            }
        }

        game_state
    }

    /// Determines the next space a CPU player will select.
    pub fn cpu_select_space(&self) -> [u8; 2] {
        let mut rng = thread_rng();
        let mut select = Vec::new();
        let mut directions = [
            ShipDirection::North,
            ShipDirection::East,
            ShipDirection::South,
            ShipDirection::West
        ];

        directions.shuffle(&mut rng);

        let mut hit_spaces = self.spaces.iter()
            .filter(|s| s.is_hit() && self.ship(s.pos()).unwrap().is_active())
            .collect::<Vec<&Space>>();

        hit_spaces.shuffle(&mut rng);

        // Check for a line of hit spaces.
        for space in &hit_spaces {
            for direction in &directions {
                let unchecked = self.find_unchecked_space(
                    space.pos(),
                    *direction,
                    true
                );

                if let Some(pos) = unchecked {
                    if !select.contains(&pos) {
                        select.push(pos);
                    }
                }
            }
        }

        // If a hit space was found, but no hit spaces next to it, find any
        // unchecked spaces next to it.
        if hit_spaces.len() > 0 && select.is_empty() {
            for direction in &directions {
                let unchecked = self.find_unchecked_space(
                    hit_spaces[0].pos(),
                    *direction,
                    false
                );

                if let Some(pos) = unchecked {
                    select.push(pos);
                }
            }
        }

        // If no spaces have been selected, just select any available space.
        if select.is_empty() {
            let pos: [u8; 2] = loop {
                let space = self.rng_pos();

                if self.space(&space).is_unchecked() {
                    break space;
                }
            };

            select.push(pos);
        }

        if select.len() > 1 {
            select.shuffle(&mut rng);
        }

        select[0]
    }

    fn rng_pos(&self) -> [u8; 2] {
        let mut rng = thread_rng();

        [
            rng.gen_range(0, self.grid_size[0]),
            rng.gen_range(0, self.grid_size[1]),
        ]
    }

    pub fn move_temp_ship(&mut self, direction: ShipDirection) {
        let old_head = self.temp_ship_pos[0];

        if let Some(new_head) = self.movement(&old_head, direction) {
            if let Some(ship) = self.get_ship_position(
                new_head,
                self.temp_ship_dir,
                self.temp_ship_pos.len() as u8
            ) {
                self.temp_ship_pos = ship;
            }
        }
    }

    pub fn place_temp_ship(&mut self) {
        let ship = self.temp_ship_pos.clone();

        if self.valid_ship_position(&ship) {
            self.ships.push(Ship::new(ship));

            self.temp_ship_dir = ShipDirection::West;
            self.temp_ship_pos = self.get_ship_position(
                [0, 0],
                self.temp_ship_dir,
                self.temp_ship_pos.len() as u8 + 1
            ).unwrap();
        }
    }

    /// Rotates a ship during the ship placement game state.
    pub fn rotate_temp_ship(&mut self) {
        let ship_len = self.temp_ship_pos.len() as u8;
        let dir = match self.temp_ship_dir {
            ShipDirection::North => ShipDirection::East,
            ShipDirection::East => ShipDirection::South,
            ShipDirection::South => ShipDirection::West,
            ShipDirection::West => ShipDirection::North,
        };

        // If the current starting position would cause the rotation to position
        // the ship partially out of bounds, adjust the starting position such
        // that the ship will be entirely within bounds.
        let old_start_pos = self.temp_ship_pos[0];
        let start_pos = match dir {
            ShipDirection::North => [
                self.temp_ship_pos[0][0],
                cmp::min(old_start_pos[1], self.grid_size[1] - ship_len),
            ],
            ShipDirection::East => [
                cmp::max(old_start_pos[0], ship_len - 1),
                self.temp_ship_pos[0][1],
            ],
            ShipDirection::South => [
                self.temp_ship_pos[0][0],
                cmp::max(old_start_pos[1], ship_len - 1),
            ],
            ShipDirection::West => [
                cmp::min(old_start_pos[0], self.grid_size[0] - ship_len),
                self.temp_ship_pos[0][1],
            ],
        };

        if let Some(ship) = self.get_ship_position(start_pos, dir, ship_len) {
            self.temp_ship_pos = ship;
            self.temp_ship_dir = dir;
        }
    }

    pub fn cpu_place_ships(&mut self) {
        for length in 2..6 {
            let ship_pos = self.cpu_place_ship(length);
            self.ships.push(Ship::new(ship_pos));
        }
    }

    /// RNGs ship locations for CPU players.
    fn cpu_place_ship(&self, length: u8) -> Vec<[u8; 2]> {
        let mut rng = thread_rng();

        // RNG a position and direction, then make sure it's valid.
        loop {
            let pos = self.rng_pos();
            let direction = match rng.gen_range(0, 4) {
                0 => ShipDirection::North,
                1 => ShipDirection::East,
                2 => ShipDirection::South,
                3 => ShipDirection::West,
                _ => unreachable!()
            };

            if let Some(s) = self.get_ship_position(pos, direction, length) {
                let valid = self.valid_ship_position(&s);

                if valid {
                    break s;
                }
            }
        }
    }

    /// Returns a ship position, given its head position, direction and length.
    ///
    /// `direction` refers to the direction the ship is facing, not the
    /// direction in which positions are generated.
    /// Returns `None` if the resulting ship position would not be contained
    /// within the grid.
    pub fn get_ship_position(
        &self,
        head: [u8; 2],
        direction: ShipDirection,
        length: u8
    ) -> Option<Vec<[u8; 2]>> {
        let valid = match direction {
            ShipDirection::North => head[1] + length <= self.grid_size[1],
            ShipDirection::East => head[0] >= length - 1,
            ShipDirection::South => head[1] >= length - 1,
            ShipDirection::West => head[0] + length <= self.grid_size[0],
        };

        let ship_opt = if valid {
            let mut ship = vec![head];

            for pos in 1..length {
                let pos_u8 = pos as u8;
                let space = match direction {
                    ShipDirection::North => [head[0], head[1] + pos_u8],
                    ShipDirection::East => [head[0] - pos_u8, head[1]],
                    ShipDirection::South => [head[0], head[1] - pos_u8],
                    ShipDirection::West => [head[0] + pos_u8, head[1]],
                };
                ship.push(space);
            }
            Some(ship)
        } else {
            None
        };

        ship_opt
    }

    /// Checks that the given ship position is valid.
    ///
    /// If the player is CPU-controlled, a ship in a space next to another ship
    /// will be considered invalid.
    pub fn valid_ship_position(&self, new_ship: &[[u8; 2]]) -> bool {
        new_ship.iter()
            .all(|s| self.valid_space(s) && !self.ship_is_in_space(s)
                 && !(self.ship_is_next_to(s) && self.is_cpu))
    }

    /// Gets a reference to the ships.
    pub fn ships(&self) -> &[Ship] {
        &self.ships
    }

    /// Gets a reference to a ship if it is in the given position.
    fn ship(&self, pos: &[u8; 2]) -> Option<&Ship> {
        self.ships.iter().find(|s| s.pos().contains(pos))
    }

    /// Returns whether a ship occupies the specified grid coordinates.
    pub fn ship_is_in_space(&self, pos: &[u8; 2]) -> bool {
        self.ships.iter().any(|s| s.pos().contains(pos))
    }

    /// Returns whether there is a ship next to the specified grid coordinates.
    fn ship_is_next_to(&self, pos: &[u8; 2]) -> bool {
        let mut result = false;
        let x = pos[0];
        let y = pos[1];

        // Left
        if x > 0 {
            result = self.ship_is_in_space(&[x - 1, y]);
        }

        // Right
        if x < self.grid_size[0] - 1 && !result {
            result = self.ship_is_in_space(&[x + 1, y]);
        }

        // Above
        if y > 0 && !result {
            result = self.ship_is_in_space(&[x, y - 1]);
        }

        // Below
        if y < self.grid_size[1] - 1 && !result {
            result = self.ship_is_in_space(&[x, y + 1]);
        }

        result
    }

    /// Gets a reference to the spaces.
    pub fn spaces(&self) -> &[Space] {
        &self.spaces
    }

    /// Returns whether the given position is valid.
    fn valid_space(&self, pos: &[u8; 2]) -> bool {
        pos[0] < self.grid_size[0] && pos[1] < self.grid_size[1]
    }

    /// Gets a reference to the space with the given position.
    pub fn space(&self, pos: &[u8; 2]) -> &Space {
        self.spaces.get(self.space_index(pos)).unwrap()
    }

    /// Calculates the index of the given position in the spaces vector.
    fn space_index(&self, pos: &[u8; 2]) -> usize {
        self.grid_size[0] as usize * pos[0] as usize + pos[1] as usize
    }

    /// Returns the coordinates of the player's grid cursor.
    pub fn get_grid_cursor(&self) -> [u8; 2] {
        self.grid_cursor.clone()
    }

    /// Returns the coordinates of a movement from `pos` in a `direction`.
    /// Returns `None` if the movement is not possible.
    pub fn movement(&self, pos: &[u8; 2], direction: ShipDirection) -> Option<[u8; 2]> {
        let valid = match direction {
            ShipDirection::North => pos[1] > 0,
            ShipDirection::East => pos[0] < self.grid_size[0] - 1,
            ShipDirection::South => pos[1] < self.grid_size[1] - 1,
            ShipDirection::West => pos[0] > 0,
        };

        if valid {
            let movement = match direction {
                ShipDirection::North => [pos[0], pos[1] - 1],
                ShipDirection::East => [pos[0] + 1, pos[1]],
                ShipDirection::South => [pos[0], pos[1] + 1],
                ShipDirection::West => [pos[0] - 1, pos[1]],
            };
            Some(movement)
        } else {
            None
        }
    }

    /// Finds the first non-hit, unchecked space in a `direction` from `pos`.
    /// Can also make sure the space is at the end of a `line` of hit spaces.
    /// Returns `None` if the first non-hit space has been checked or if a grid
    /// boundary is reached.
    fn find_unchecked_space(
        &self,
        pos: &[u8; 2],
        direction: ShipDirection,
        check_for_line: bool
    ) -> Option<[u8; 2]> {
        let mut check_pos = self.movement(pos, direction);

        while let Some(next_pos) = check_pos {
            let next_space = self.space(&next_pos);

            match next_space.is_hit() {
                true => check_pos = self.movement(&next_pos, direction),
                false => {
                    if !next_space.is_unchecked() {
                        check_pos = None;
                    }
                    break;
                }
            };
        }

        if check_for_line && check_pos.is_some() {
            let unchecked = check_pos.unwrap();
            let opposite_dir = direction.opposite();
            let prev_pos = self.movement(&unchecked, opposite_dir).unwrap();

            if &prev_pos == pos {
                check_pos = None;
            }
        }

        check_pos
    }

    /// Moves the player's grid cursor in the given `direction` if possible.
    pub fn move_grid_cursor(&mut self, direction: ShipDirection) {
        if let Some(new_cursor) = self.movement(&self.grid_cursor, direction) {
            self.set_grid_cursor(&new_cursor);
        }
    }

    /// Sets the player's grid cursor coordinates.
    pub fn set_grid_cursor(&mut self, new_cursor: &[u8; 2]) {
        self.grid_cursor = *new_cursor;
    }
}

<|endoftext|>


<commit_before>use std::env;
use std::fs::{File, read_dir};
use std::io::Write;
use std::path::PathBuf;
use std::result::Result;
use serde::{Deserialize, Serialize};
use serde_json::ser::Serializer;
use serde_protobuf::descriptor::{Descriptors, MessageDescriptor};
use serde_protobuf::de::Deserializer;
use serde_protobuf::error::{Error, ErrorKind};
use serde_value::Value;
use protobuf::{CodedInputStream, parse_from_reader};
use protobuf::error::ProtobufError;

pub enum PqrsError {
    EofError(String),
    SerdeError(String),
    ProtobufError(String),
}

pub fn process_single(data: &[u8], msg_type: &str, out: &mut Write) -> Result<(), PqrsError> {
    match msg_type {
        "guess" => return guess_message(data, out),
        _ => return named_message(data, msg_type, out),
    }
}

fn named_message(data: &[u8], msg_type: &str, out: &mut Write) -> Result<(), PqrsError> {
    let mut loc_msg_type = String::new();
    let ch = msg_type.chars().nth(0).unwrap();
    if ch != '.' {
        loc_msg_type.push('.');
    }
    loc_msg_type.push_str(msg_type);

    let (descriptors, _) = get_descriptors(false);

    let stream = CodedInputStream::from_bytes(data);
    let mut deserializer = Deserializer::for_named_message(&descriptors, &loc_msg_type, stream).unwrap();
    let mut serializer = Serializer::new(out);
    let value = match Value::deserialize(&mut deserializer) {
        Ok(x) => x,
        Err(Error(ErrorKind::Protobuf(ProtobufError::WireError(msg)), _)) => {
            if msg == "unexpected EOF" {
                return Err(PqrsError::EofError(msg));
            }
            return Err(PqrsError::ProtobufError(msg));
        },
        Err(e) => return Err(PqrsError::SerdeError(String::from(e.description()))),
    };
    value.serialize(&mut serializer).unwrap();
    Ok(())
}

fn guess_message(data: &[u8], out: &mut Write) -> Result<(), PqrsError> {
    let (descriptors, message_descriptors) = get_descriptors(true);

    let mut serializer = Serializer::new(out);
    for md in message_descriptors {
        let stream = CodedInputStream::from_bytes(data);
        let mut deserializer = Deserializer::new(&descriptors, &md, stream);
        let value = match Value::deserialize(&mut deserializer) {
            Ok(x) => x,
            Err(Error(ErrorKind::Protobuf(ProtobufError::WireError(msg)), _)) => {
                if msg == "unexpected EOF" {
                    return Err(PqrsError::EofError(msg));
                }
                return Err(PqrsError::ProtobufError(msg));
            },
            Err(e) => return Err(PqrsError::SerdeError(String::from(e.description()))),
        };
        value.serialize(&mut serializer).unwrap();
    }
    Ok(())
}

fn get_descriptors(with_message_descriptors: bool) -> (Descriptors, Vec<MessageDescriptor>) {
    let mut descriptors = Descriptors::new();
    let mut message_descriptors = Vec::new();

    for fdset_path in discover_fdsets() {
        let mut fdset_file = File::open(fdset_path.as_path()).unwrap();
        let fdset_proto = parse_from_reader(&mut fdset_file).unwrap();
        descriptors.add_file_set_proto(&fdset_proto);
        if with_message_descriptors {
            for file_proto in fdset_proto.get_file().iter() {
                for message_proto in file_proto.get_message_type().iter() {
                    message_descriptors.push(MessageDescriptor::from_proto(&fdset_path.to_string_lossy().into_owned().as_str(), message_proto));
                }
            }
        }
    }
    descriptors.resolve_refs();
    (descriptors, message_descriptors)
}

fn discover_fdsets() -> Vec<PathBuf> {
    let mut fdset_files = Vec::new();

    let mut home = env::home_dir().expect("Could not find $HOME");
    home.push(".pq");
    let paths = read_dir(home.as_path()).unwrap();

    for p in paths {
        let path = match p {
            Ok(p) => p.path(),
            Err(_) => continue,
        };
        match path.extension() {
            Some(x) => {
                if x != "fdset" {
                    continue;
                }
            },
            None => continue,
        }
        fdset_files.push(path);
    }
    fdset_files
}
<commit_msg>DRY deserialize function<commit_after>use std::env;
use std::fs::{File, read_dir};
use std::io::Write;
use std::path::PathBuf;
use std::result::Result;
use serde::{Deserialize, Serialize};
use serde_json::ser::Serializer;
use serde_protobuf::descriptor::{Descriptors, MessageDescriptor};
use serde_protobuf::de::Deserializer;
use serde_protobuf::error::{Error, ErrorKind};
use serde_value::Value;
use protobuf::{CodedInputStream, parse_from_reader};
use protobuf::error::ProtobufError;

pub enum PqrsError {
    EofError(String),
    SerdeError(String),
    ProtobufError(String),
}

pub fn process_single(data: &[u8], msg_type: &str, out: &mut Write) -> Result<(), PqrsError> {
    match msg_type {
        "guess" => return guess_message(data, out),
        _ => return named_message(data, msg_type, out),
    }
}

fn named_message(data: &[u8], msg_type: &str, out: &mut Write) -> Result<(), PqrsError> {
    let mut loc_msg_type = String::new();
    let ch = msg_type.chars().nth(0).unwrap();
    if ch != '.' {
        loc_msg_type.push('.');
    }
    loc_msg_type.push_str(msg_type);

    let (descriptors, _) = get_descriptors(false);

    let stream = CodedInputStream::from_bytes(data);
    let mut deserializer = Deserializer::for_named_message(&descriptors, &loc_msg_type, stream).unwrap();
    let mut serializer = Serializer::new(out);
    match deser(&mut deserializer) {
        Ok(value) => value.serialize(&mut serializer).unwrap(),
        Err(e) => return Err(e),
    }
    Ok(())
}

fn guess_message(data: &[u8], out: &mut Write) -> Result<(), PqrsError> {
    let (descriptors, message_descriptors) = get_descriptors(true);

    let mut serializer = Serializer::new(out);
    for md in message_descriptors {
        let stream = CodedInputStream::from_bytes(data);
        let mut deserializer = Deserializer::new(&descriptors, &md, stream);
        match deser(&mut deserializer) {
            Ok(value) => value.serialize(&mut serializer).unwrap(),
            Err(e) => return Err(e),
        }
    }
    Ok(())
}

fn deser(deserializer: &mut Deserializer) -> Result<Value, PqrsError> {
    match Value::deserialize(deserializer) {
        Ok(x) => return Ok(x),
        Err(Error(ErrorKind::Protobuf(ProtobufError::WireError(msg)), _)) => {
            if msg == "unexpected EOF" {
                return Err(PqrsError::EofError(msg));
            }
            return Err(PqrsError::ProtobufError(msg));
        },
        Err(e) => return Err(PqrsError::SerdeError(String::from(e.description()))),
    };
}

fn get_descriptors(with_message_descriptors: bool) -> (Descriptors, Vec<MessageDescriptor>) {
    let mut descriptors = Descriptors::new();
    let mut message_descriptors = Vec::new();

    for fdset_path in discover_fdsets() {
        let mut fdset_file = File::open(fdset_path.as_path()).unwrap();
        let fdset_proto = parse_from_reader(&mut fdset_file).unwrap();
        descriptors.add_file_set_proto(&fdset_proto);
        if with_message_descriptors {
            for file_proto in fdset_proto.get_file().iter() {
                for message_proto in file_proto.get_message_type().iter() {
                    message_descriptors.push(MessageDescriptor::from_proto(&fdset_path.to_string_lossy().into_owned().as_str(), message_proto));
                }
            }
        }
    }
    descriptors.resolve_refs();
    (descriptors, message_descriptors)
}

fn discover_fdsets() -> Vec<PathBuf> {
    let mut fdset_files = Vec::new();

    let mut home = env::home_dir().expect("Could not find $HOME");
    home.push(".pq");
    let paths = read_dir(home.as_path()).unwrap();

    for p in paths {
        let path = match p {
            Ok(p) => p.path(),
            Err(_) => continue,
        };
        match path.extension() {
            Some(x) => {
                if x != "fdset" {
                    continue;
                }
            },
            None => continue,
        }
        fdset_files.push(path);
    }
    fdset_files
}
<|endoftext|>


<commit_before>use {Action, ButtonTuple, InputTranslator, MouseTranslationData};
use input::Button;
use piston_window::Size;
use std::collections::HashMap;
use std::convert::Into;
use std::default::Default;

#[allow(missing_docs)]
pub struct InputRebind<A: Action> {
    keymap: HashMap<A, ButtonTuple>,
    mouse_data: MouseTranslationData
}

impl<A: Action> InputRebind<A> {
    #[allow(missing_docs)]
    pub fn new(size: Size) -> Self {
        InputRebind {
            keymap: HashMap::new(),
            mouse_data: MouseTranslationData::new(size)
        }
    }

    #[allow(missing_docs)]
    pub fn insert_action(&mut self, action: A) -> Option<ButtonTuple> {
        self.keymap.insert(action, ButtonTuple::new())
    }

    #[allow(missing_docs)]
    pub fn insert_action_with_buttons(&mut self, action: A, buttons: ButtonTuple) -> Option<ButtonTuple> {
        self.keymap.insert(action, buttons)
    }

    #[allow(missing_docs)]
    pub fn get_bindings(&self, action: &A) -> Option<&ButtonTuple> {
        self.keymap.get(action)
    }

    #[allow(missing_docs)]
    pub fn get_bindings_mut(&mut self, action: &mut A) -> Option<&mut ButtonTuple> {
        self.keymap.get_mut(action)
    }

    #[allow(missing_docs)]
    pub fn get_x_scroll_inverted(&self) -> &bool {
        &self.mouse_data.x_axis_scroll_inverted
    }

    #[allow(missing_docs)]
    pub fn get_x_scroll_inverted_mut(&mut self) -> &mut bool {
        &mut self.mouse_data.x_axis_scroll_inverted
    }

    #[allow(missing_docs)]
    pub fn get_y_scroll_inverted(&self) -> &bool {
        &self.mouse_data.y_axis_scroll_inverted
    }

    #[allow(missing_docs)]
    pub fn get_y_scroll_inverted_mut(&mut self) -> &mut bool {
        &mut self.mouse_data.y_axis_scroll_inverted
    }

    #[allow(missing_docs)]
    pub fn get_x_motion_inverted(&self) -> &bool {
        &self.mouse_data.x_axis_motion_inverted
    }

    #[allow(missing_docs)]
    pub fn get_x_motion_inverted_mut(&mut self) -> &mut bool {
        &mut self.mouse_data.x_axis_motion_inverted
    }

    #[allow(missing_docs)]
    pub fn get_y_motion_inverted(&self) -> &bool {
        &self.mouse_data.y_axis_motion_inverted
    }

    #[allow(missing_docs)]
    pub fn get_y_motion_inverted_mut(&mut self) -> &mut bool {
        &mut self.mouse_data.y_axis_motion_inverted
    }

    #[allow(missing_docs)]
    pub fn get_viewport_size(&self) -> &Size {
        &self.mouse_data.viewport_size
    }

    #[allow(missing_docs)]
    pub fn get_viewport_size_mut(&mut self) -> &mut Size {
        &mut self.mouse_data.viewport_size
    }
}

impl<A: Action> Default for InputRebind<A> {
    #[allow(missing_docs)]
    fn default() -> Self {
        InputRebind::new((800, 600).into())
    }
}

impl<A: Action> Into<InputTranslator<A>> for InputRebind<A> {
    #[allow(missing_docs)]
    fn into(self) -> InputTranslator<A> {
        let mut input_translator = InputTranslator::new(self.mouse_data.viewport_size);
        input_translator.mouse_translator.data = self.mouse_data;
        input_translator.keymap = self.keymap.values()
                                             .flat_map(|&bt| bt.into_iter())
                                             .filter_map(|x| x)
                                             .zip(self.keymap.keys().map(|x| x.clone()))
                                             .collect();

        input_translator
    }
}

impl<A: Action> Into<InputRebind<A>> for InputTranslator<A> {
    #[allow(missing_docs)]
    fn into(self) -> InputRebind<A> {
        let mut input_rebind = InputRebind::new(self.mouse_translator.data.viewport_size);
        input_rebind.mouse_data = self.mouse_translator.data;
        //input_rebind.keymap = self.keymap.btn_map.
        //input_rebind.keymap = self.keymap.btn_map.drain().map(|(k, v)| (v, k)).collect();
        input_rebind
    }
}

impl ButtonTuple {
    fn into_iter(self) -> ButtonTupleIterator {
        ButtonTupleIterator {
            button_tuple: self,
            i: 0
        }
    }
}

struct ButtonTupleIterator {
    button_tuple: ButtonTuple,
    i: usize
}

impl Iterator for ButtonTupleIterator {
    type Item = Option<Button>;

    fn next(&mut self) -> Option<Self::Item> {
        let i = self.i;
        self.i += 1;
        match i {
            0 => Some(self.button_tuple.0),
            1 => Some(self.button_tuple.1),
            2 => Some(self.button_tuple.2),
            _ => None
        }
    }
}
<commit_msg>Use deref rather than clone<commit_after>use {Action, ButtonTuple, InputTranslator, MouseTranslationData};
use input::Button;
use piston_window::Size;
use std::collections::HashMap;
use std::convert::Into;
use std::default::Default;

#[allow(missing_docs)]
pub struct InputRebind<A: Action> {
    keymap: HashMap<A, ButtonTuple>,
    mouse_data: MouseTranslationData
}

impl<A: Action> InputRebind<A> {
    #[allow(missing_docs)]
    pub fn new(size: Size) -> Self {
        InputRebind {
            keymap: HashMap::new(),
            mouse_data: MouseTranslationData::new(size)
        }
    }

    #[allow(missing_docs)]
    pub fn insert_action(&mut self, action: A) -> Option<ButtonTuple> {
        self.keymap.insert(action, ButtonTuple::new())
    }

    #[allow(missing_docs)]
    pub fn insert_action_with_buttons(&mut self, action: A, buttons: ButtonTuple) -> Option<ButtonTuple> {
        self.keymap.insert(action, buttons)
    }

    #[allow(missing_docs)]
    pub fn get_bindings(&self, action: &A) -> Option<&ButtonTuple> {
        self.keymap.get(action)
    }

    #[allow(missing_docs)]
    pub fn get_bindings_mut(&mut self, action: &mut A) -> Option<&mut ButtonTuple> {
        self.keymap.get_mut(action)
    }

    #[allow(missing_docs)]
    pub fn get_x_scroll_inverted(&self) -> &bool {
        &self.mouse_data.x_axis_scroll_inverted
    }

    #[allow(missing_docs)]
    pub fn get_x_scroll_inverted_mut(&mut self) -> &mut bool {
        &mut self.mouse_data.x_axis_scroll_inverted
    }

    #[allow(missing_docs)]
    pub fn get_y_scroll_inverted(&self) -> &bool {
        &self.mouse_data.y_axis_scroll_inverted
    }

    #[allow(missing_docs)]
    pub fn get_y_scroll_inverted_mut(&mut self) -> &mut bool {
        &mut self.mouse_data.y_axis_scroll_inverted
    }

    #[allow(missing_docs)]
    pub fn get_x_motion_inverted(&self) -> &bool {
        &self.mouse_data.x_axis_motion_inverted
    }

    #[allow(missing_docs)]
    pub fn get_x_motion_inverted_mut(&mut self) -> &mut bool {
        &mut self.mouse_data.x_axis_motion_inverted
    }

    #[allow(missing_docs)]
    pub fn get_y_motion_inverted(&self) -> &bool {
        &self.mouse_data.y_axis_motion_inverted
    }

    #[allow(missing_docs)]
    pub fn get_y_motion_inverted_mut(&mut self) -> &mut bool {
        &mut self.mouse_data.y_axis_motion_inverted
    }

    #[allow(missing_docs)]
    pub fn get_viewport_size(&self) -> &Size {
        &self.mouse_data.viewport_size
    }

    #[allow(missing_docs)]
    pub fn get_viewport_size_mut(&mut self) -> &mut Size {
        &mut self.mouse_data.viewport_size
    }
}

impl<A: Action> Default for InputRebind<A> {
    #[allow(missing_docs)]
    fn default() -> Self {
        InputRebind::new((800, 600).into())
    }
}

impl<A: Action> Into<InputTranslator<A>> for InputRebind<A> {
    #[allow(missing_docs)]
    fn into(self) -> InputTranslator<A> {
        let mut input_translator = InputTranslator::new(self.mouse_data.viewport_size);
        input_translator.mouse_translator.data = self.mouse_data;
        input_translator.keymap = self.keymap.values()
                                             .flat_map(|&bt| bt.into_iter())
                                             .filter_map(|x| x)
                                             .zip(self.keymap.keys().map(|x| *x))
                                             .collect();

        input_translator
    }
}

impl<A: Action> Into<InputRebind<A>> for InputTranslator<A> {
    #[allow(missing_docs)]
    fn into(self) -> InputRebind<A> {
        let mut input_rebind = InputRebind::new(self.mouse_translator.data.viewport_size);
        input_rebind.mouse_data = self.mouse_translator.data;
        //input_rebind.keymap = self.keymap.btn_map.
        //input_rebind.keymap = self.keymap.btn_map.drain().map(|(k, v)| (v, k)).collect();
        input_rebind
    }
}

impl ButtonTuple {
    fn into_iter(self) -> ButtonTupleIterator {
        ButtonTupleIterator {
            button_tuple: self,
            i: 0
        }
    }
}

struct ButtonTupleIterator {
    button_tuple: ButtonTuple,
    i: usize
}

impl Iterator for ButtonTupleIterator {
    type Item = Option<Button>;

    fn next(&mut self) -> Option<Self::Item> {
        let i = self.i;
        self.i += 1;
        match i {
            0 => Some(self.button_tuple.0),
            1 => Some(self.button_tuple.1),
            2 => Some(self.button_tuple.2),
            _ => None
        }
    }
}
<|endoftext|>


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// fn set_and_determine_last_generation(target_dir: &Path) -> i64 {

// }
<commit_msg>fix bug in liquid context<commit_after>use std::vec::Vec;
use std::fs;
use std::fs::File;
use std::fs::DirBuilder;
use std::io::prelude::*;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fmt::{self, Debug};
use walkdir::{DirEntry, WalkDir};
use liquid;
use config::Config;
use pulldown_cmark::{html, Options, Parser};
use template;
use image;
use errors::*;
use rayon::prelude::*;
use filter::{contains_markdown_file, contains_markdown_subdir, is_directory, is_hidden, is_image,
             is_markdown};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// fn set_and_determine_last_generation(target_dir: &Path) -> i64 {

// }
<|endoftext|>


<commit_before>use regex::{self, Captures, Regex};
use std::error;
use std::fmt;
use term::{self, color, Terminal};

#[derive(Debug)]
pub enum Error {
    Regex(regex::Error)
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Regex(ref err) => err.description()
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::Regex(ref err) => Some(err)
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Regex(ref err) => write!(f, "Regex error: {}", err)
        }
    }
}

impl From<regex::Error> for Error {
    fn from(err: regex::Error) -> Error {
        Error::Regex(err)
    }
}

/// Returns an iterator for finding all matches of `pattern` in `subject`.
pub fn find_matches<'t>(pattern: &str, subject: &'t str) -> Result<Vec<Captures<'t>>, Error> {
    let regex = try!(Regex::new(&pattern));
    Ok(regex.captures_iter(subject).collect())
}

/// Finds all matches for the given pattern on the given subject and prints out
/// the subject with matches highlighted.
pub fn print_subject_highlighted<'t>(subject: &str, matches: &Vec<Captures<'t>>) {
    // Loop over each match in the subject and pretty-print the match as well as
    // any trailing, non-matching text preceding the match.
    let mut last_index = 0;

    for captures in matches.iter() {
        let positions = captures.pos(0).unwrap();

        print!("{}", &subject[last_index .. positions.0]);
        print_match(captures);

        last_index = positions.1;
    }

    // Print trailing unmatched text if there is any.
    if last_index < subject.len() {
        print!("{}", &subject[last_index ..]);
    }
    println!("");
}

/// Prints the matches in a list format.
pub fn print_match_list<'t>(matches: &Vec<Captures<'t>>) {
    let mut match_id = 1;

    for captures in matches.iter() {
        print!("{:<4} ", format!("{}.", match_id));
        print_match(captures);
        println!("");
        match_id += 1;
    }
}

/// Prints out a match using color formatting.
fn print_match(captures: &Captures) {
    let mut terminal = term::stdout().unwrap();
    let color_cycle = [color::BLUE, color::GREEN, color::MAGENTA, color::YELLOW];
    let mut color_index = 1;

    // Get the string of the entire match and the relative offset in the subject.
    let string = captures.at(0).unwrap();
    let offsets = captures.pos(0).unwrap();

    // To highlight sub-matches, divide the match string into a series of
    // resizeable regions that store their range and their color. As we loop
    // over each capture, find the parent region the current capture group fits
    // into, and split the region in two, with a new region for the current
    // capture between it. Worst-case time is O(3n^2), where n is the number of
    // subgroups in the regular expression.
    let mut regions: Vec<(usize, usize, u16)> = Vec::new();
    regions.push((offsets.0, offsets.1, color::BLUE));

    for i in 1..captures.len() {
        let pos = match captures.pos(i) {
            None => continue,
            Some(pos) => pos
        };

        for j in 0..regions.len() {
            if pos.0 >= regions[j].0 && pos.1 <= regions[j].1 {
                // Define two new regions that overlap the old one.
                let middle = (pos.0, pos.1, color_cycle[color_index]);
                let right = (pos.1, regions[j].1, regions[j].2);
                // Shrink the old region to be the leftmost one.
                regions[j].1 = pos.0;

                // Insert the new regions.
                regions.insert(j + 1, middle);
                regions.insert(j + 2, right);

                // Choose next color next time.
                color_index = (color_index + 1) % 4;
                break;
            }
        }
    }

    // Now print out the prepared regions; this part is pretty easy, since the
    // region parser already did all the work.
    for i in 0..regions.len() {
        terminal.bg(regions[i].2).unwrap();
        print!("{}", &string[regions[i].0 - offsets.0 .. regions[i].1 - offsets.0]);
    }

    // Reset coloring to normal.
    terminal.reset().unwrap();
}
<commit_msg>Prettier list format with range info<commit_after>use regex::{self, Captures, Regex};
use std::error;
use std::fmt;
use term::{self, color, Terminal};

#[derive(Debug)]
pub enum Error {
    Regex(regex::Error)
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Regex(ref err) => err.description()
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::Regex(ref err) => Some(err)
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Regex(ref err) => write!(f, "Regex error: {}", err)
        }
    }
}

impl From<regex::Error> for Error {
    fn from(err: regex::Error) -> Error {
        Error::Regex(err)
    }
}

/// Returns an iterator for finding all matches of `pattern` in `subject`.
pub fn find_matches<'t>(pattern: &str, subject: &'t str) -> Result<Vec<Captures<'t>>, Error> {
    let regex = try!(Regex::new(&pattern));
    Ok(regex.captures_iter(subject).collect())
}

/// Finds all matches for the given pattern on the given subject and prints out
/// the subject with matches highlighted.
pub fn print_subject_highlighted<'t>(subject: &str, matches: &Vec<Captures<'t>>) {
    // Loop over each match in the subject and pretty-print the match as well as
    // any trailing, non-matching text preceding the match.
    let mut last_index = 0;

    for captures in matches.iter() {
        let positions = captures.pos(0).unwrap();

        print!("{}", &subject[last_index .. positions.0]);
        print_match(captures);

        last_index = positions.1;
    }

    // Print trailing unmatched text if there is any.
    if last_index < subject.len() {
        print!("{}", &subject[last_index ..]);
    }
    println!("");
}

/// Prints the matches in a list format.
pub fn print_match_list<'t>(matches: &Vec<Captures<'t>>) {
    let mut match_id = 1;

    for captures in matches.iter() {
        let positions = captures.pos(0).unwrap();

        print!("{:<4} {:<14} ", format!("{}.", match_id), format!("[{}-{}]", positions.0, positions.1));
        print_match(captures);
        println!("");
        match_id += 1;
    }
}

/// Prints out a match using color formatting.
fn print_match(captures: &Captures) {
    let mut terminal = term::stdout().unwrap();
    let color_cycle = [color::BLUE, color::GREEN, color::MAGENTA, color::YELLOW];
    let mut color_index = 1;

    // Get the string of the entire match and the relative offset in the subject.
    let string = captures.at(0).unwrap();
    let offsets = captures.pos(0).unwrap();

    // To highlight sub-matches, divide the match string into a series of
    // resizeable regions that store their range and their color. As we loop
    // over each capture, find the parent region the current capture group fits
    // into, and split the region in two, with a new region for the current
    // capture between it. Worst-case time is O(3n^2), where n is the number of
    // subgroups in the regular expression.
    let mut regions: Vec<(usize, usize, u16)> = Vec::new();
    regions.push((offsets.0, offsets.1, color::BLUE));

    for i in 1..captures.len() {
        let pos = match captures.pos(i) {
            None => continue,
            Some(pos) => pos
        };

        for j in 0..regions.len() {
            if pos.0 >= regions[j].0 && pos.1 <= regions[j].1 {
                // Define two new regions that overlap the old one.
                let middle = (pos.0, pos.1, color_cycle[color_index]);
                let right = (pos.1, regions[j].1, regions[j].2);
                // Shrink the old region to be the leftmost one.
                regions[j].1 = pos.0;

                // Insert the new regions.
                regions.insert(j + 1, middle);
                regions.insert(j + 2, right);

                // Choose next color next time.
                color_index = (color_index + 1) % 4;
                break;
            }
        }
    }

    // Now print out the prepared regions; this part is pretty easy, since the
    // region parser already did all the work.
    for i in 0..regions.len() {
        terminal.bg(regions[i].2).unwrap();
        print!("{}", &string[regions[i].0 - offsets.0 .. regions[i].1 - offsets.0]);
    }

    // Reset coloring to normal.
    terminal.reset().unwrap();
}
<|endoftext|>


<commit_before>use regex::{self, Captures, Regex};
use std::error;
use std::fmt;
use term::{self, color, Terminal};

#[derive(Debug)]
pub enum Error {
    Regex(regex::Error)
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Regex(ref err) => err.description()
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::Regex(ref err) => Some(err)
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Regex(ref err) => write!(f, "Regex error: {}", err)
        }
    }
}

impl From<regex::Error> for Error {
    fn from(err: regex::Error) -> Error {
        Error::Regex(err)
    }
}

/// Finds all matches for the given pattern on the given subject and returns
/// the matches and their captures.
pub fn find_matches<'t>(pattern: &str, subject: &'t str) -> Result<Vec<Captures<'t>>, Error> {
    let regex = try!(Regex::new(&pattern));
    Ok(regex.captures_iter(subject).collect())
}

/// Prints out the given subject string, with the regions in the given matches
/// highlighted.
pub fn print_subject_highlighted<'t>(subject: &str, matches: &Vec<Captures<'t>>) {
    // Loop over each match in the subject and pretty-print the match as well as
    // any trailing, non-matching text preceding the match.
    let mut last_index = 0;

    for captures in matches.iter() {
        let positions = captures.pos(0).unwrap();

        print!("{}", &subject[last_index .. positions.0]);
        print_match(&subject, captures);

        last_index = positions.1;
    }

    // Print trailing unmatched text if there is any.
    if last_index < subject.len() {
        print!("{}", &subject[last_index ..]);
    }
    println!("");
}

/// Prints the given matches in a formatted list.
pub fn print_match_list<'t>(subject: &str, matches: &Vec<Captures<'t>>) {
    let mut match_id = 1;

    for captures in matches.iter() {
        let positions = captures.pos(0).unwrap();

        print!("{:<4} {:<14} ", format!("{}.", match_id), format!("[{}-{}]", positions.0, positions.1));
        print_match(&subject, captures);
        println!("");
        match_id += 1;
    }
}

/// Prints out a match using color formatting.
///
/// The match is highlighted according to the captures that are matched. Each
/// capture is highlighted in a different color to distinguish it from other
/// captures. Because captures can be nested, this function uses a stack to keep
/// track of capture scope and display nested captures correctly.
///
/// The worst-case run time is O(2n log(n)), but the average run time should be
/// more like Θ(n + log(n)). The more nested the captures are, the worse the run
/// time is.
fn print_match(subject: &str, captures: &Captures) {
    let mut terminal = term::stdout().unwrap();
    let color_cycle = [color::BLUE, color::GREEN, color::MAGENTA, color::YELLOW];
    let mut color_index = 0;

    // To highlight the captures in a context-sensitive manner, set up a stack
    // to keep track of entering and exiting capture scopes. When examining a
    // capture, check if it fits inside the parent capture. If it doesn't, close
    // the scopes until we find a scope the capture does fit in.
    let mut stack: Vec<(usize, usize, u16)> = Vec::new();

    // Since we will print the captures as we go along, set up a cursor that
    // points to how much of the string we have printed out so far so we don't
    // accidentally print out the same regions of the strings more than once.
    let mut string_cursor = captures.pos(0).unwrap().0;

    // Loop over each capture and find the scope it belongs to. Optional capture
    // groups that are not matched are ignored.
    for i in 0..captures.len() {
        let pos = match captures.pos(i) {
            None => continue,
            Some(pos) => pos
        };

        // Unwind the stack until we find the correct parent.
        while !stack.is_empty() && pos.1 > stack.last().unwrap().1 {
            let scope = stack.pop().unwrap();

            terminal.bg(scope.2).unwrap();
            print!("{}", &subject[string_cursor .. scope.1]);
            string_cursor = scope.1;
        }

        // If the stack isn't empty, print out the head end of the parent scope
        // just before the current capture.
        if !stack.is_empty() {
            terminal.bg(stack.last().unwrap().2).unwrap();
            print!("{}", &subject[string_cursor .. pos.0]);
            string_cursor = pos.0;
        }

        // Push the current capture onto the stack with a color selected from
        // the color cycle.
        stack.push((pos.0, pos.1, color_cycle[color_index]));
        color_index = (color_index + 1) % color_cycle.len();
    }

    // Finally, print out the tail of the match. The stack will still have
    // captures in it for the very last capture and its ancestors, so unwind the
    // stack one last time and print out each capture's tail.
    while !stack.is_empty() {
        let scope = stack.pop().unwrap();
        terminal.bg(scope.2).unwrap();
        print!("{}", &subject[string_cursor .. scope.1]);
        string_cursor = scope.1;
    }

    // Reset coloring to normal.
    terminal.reset().unwrap();
}
<commit_msg>Formatting<commit_after>use regex::{self, Captures, Regex};
use std::error;
use std::fmt;
use term::{self, color, Terminal};

#[derive(Debug)]
pub enum Error {
    Regex(regex::Error)
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Regex(ref err) => err.description()
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::Regex(ref err) => Some(err)
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Regex(ref err) => write!(f, "Regex error: {}", err)
        }
    }
}

impl From<regex::Error> for Error {
    fn from(err: regex::Error) -> Error {
        Error::Regex(err)
    }
}

/// Finds all matches for the given pattern on the given subject and returns
/// the matches and their captures.
pub fn find_matches<'t>(pattern: &str, subject: &'t str) -> Result<Vec<Captures<'t>>, Error> {
    let regex = try!(Regex::new(&pattern));
    Ok(regex.captures_iter(subject).collect())
}

/// Prints out the given subject string, with the regions in the given matches
/// highlighted.
pub fn print_subject_highlighted<'t>(subject: &str, matches: &Vec<Captures<'t>>) {
    // Loop over each match in the subject and pretty-print the match as well as
    // any trailing, non-matching text preceding the match.
    let mut last_index = 0;

    for captures in matches.iter() {
        let positions = captures.pos(0).unwrap();

        print!("{}", &subject[last_index .. positions.0]);
        print_match(&subject, captures);

        last_index = positions.1;
    }

    // Print trailing unmatched text if there is any.
    if last_index < subject.len() {
        print!("{}", &subject[last_index ..]);
    }
    println!("");
}

/// Prints the given matches in a formatted list.
pub fn print_match_list<'t>(subject: &str, matches: &Vec<Captures<'t>>) {
    let mut match_id = 1;

    for captures in matches.iter() {
        let positions = captures.pos(0).unwrap();

        print!("{:<4} {:<14} ", format!("{}.", match_id), format!("[{}-{}]", positions.0, positions.1));
        print_match(&subject, captures);
        println!("");
        match_id += 1;
    }
}

/// Prints out a match using color formatting.
///
/// The match is highlighted according to the captures that are matched. Each
/// capture is highlighted in a different color to distinguish it from other
/// captures. Because captures can be nested, this function uses a stack to keep
/// track of capture scope and display nested captures correctly.
///
/// The worst-case run time is O(2n log(n)), but the average run time should be
/// more like Θ(n + log(n)). The more nested the captures are, the worse the run
/// time is.
fn print_match(subject: &str, captures: &Captures) {
    let mut terminal = term::stdout().unwrap();
    let color_cycle = [color::BLUE, color::GREEN, color::MAGENTA, color::YELLOW];
    let mut color_index = 0;

    // To highlight the captures in a context-sensitive manner, set up a stack
    // to keep track of entering and exiting capture scopes. When examining a
    // capture, check if it fits inside the parent capture. If it doesn't, close
    // the scopes until we find a scope the capture does fit in.
    let mut stack: Vec<(usize, usize, u16)> = Vec::new();

    // Since we will print the captures as we go along, set up a cursor that
    // points to how much of the string we have printed out so far so we don't
    // accidentally print out the same regions of the strings more than once.
    let mut string_cursor = captures.pos(0).unwrap().0;

    // Loop over each capture and find the scope it belongs to. Optional capture
    // groups that are not matched are ignored.
    for i in 0..captures.len() {
        let pos = match captures.pos(i) {
            None => continue,
            Some(pos) => pos
        };

        // Unwind the stack until we find the correct parent.
        while !stack.is_empty() && pos.1 > stack.last().unwrap().1 {
            let scope = stack.pop().unwrap();
            terminal.bg(scope.2).unwrap();
            print!("{}", &subject[string_cursor .. scope.1]);
            string_cursor = scope.1;
        }

        // If the stack isn't empty, print out the head end of the parent scope
        // just before the current capture.
        if !stack.is_empty() {
            terminal.bg(stack.last().unwrap().2).unwrap();
            print!("{}", &subject[string_cursor .. pos.0]);
            string_cursor = pos.0;
        }

        // Push the current capture onto the stack with a color selected from
        // the color cycle.
        stack.push((pos.0, pos.1, color_cycle[color_index]));
        color_index = (color_index + 1) % color_cycle.len();
    }

    // Finally, print out the tail of the match. The stack will still have
    // captures in it for the very last capture and its ancestors, so unwind the
    // stack one last time and print out each capture's tail.
    while !stack.is_empty() {
        let scope = stack.pop().unwrap();
        terminal.bg(scope.2).unwrap();
        print!("{}", &subject[string_cursor .. scope.1]);
        string_cursor = scope.1;
    }

    // Reset coloring to normal.
    terminal.reset().unwrap();
}
<|endoftext|>