<commit_before>extern crate rand;

pub const ROWS: u8 = 9;
pub const COLS: u8 = 12;
pub const TILES: u8 = 108;
pub const PLAYERS: u8 = 4;

#[derive(RustcDecodable, RustcEncodable, Clone)]
pub struct Game {
    players: Vec<Player>,
    board: Board,
    turn: PlayerId,
    merge_decision: Option<PlayerId>
}

#[derive(RustcDecodable, RustcEncodable, Clone)]
pub struct Player {
    id: PlayerId,
    money: i32,
    shares: PlayerShares,
    tiles: Vec<Tile>
}

pub type PlayerId = u8;

#[derive(RustcDecodable, RustcEncodable, Clone)]
pub struct PlayerShares {
    luxor: u8,
    tower: u8,
    american: u8,
    festival: u8,
    worldwide: u8,
    continental: u8,
    imperial: u8
}

#[derive(RustcDecodable, RustcEncodable, Clone, Debug)]
pub struct Tile { row: u8, col: u8 }

#[derive(RustcDecodable, RustcEncodable, Clone)]
pub struct Board {
    slots: Vec<Slot>
}

#[derive(RustcDecodable, RustcEncodable, Clone, PartialEq)]
pub struct Slot {
    row: u8,
    col: u8,
    has_tile: bool,
    hotel: Option<Hotel>
}

#[derive(RustcDecodable, RustcEncodable, Clone, PartialEq)]
pub enum Hotel { Luxor, Tower, American, Festival, Worldwide, Continental, Imperial }

#[derive(Debug)]
pub enum Action {
    PlaceTile { player: PlayerId, tile: Tile },
    HandleMergeStocks { hold: u8, sell: u8, trade: u8 },
    BuyStocks,
    DrawTile,
    EndGame
}

pub fn new_game() -> Game {
    let all_tiles = (0..TILES).map(|i| Tile { row: i / COLS, col: i % ROWS }).collect();
    let (starting_tiles, remaining_tiles) = choose_tiles(all_tiles, PLAYERS);
    let players = new_players(remaining_tiles);
    let slots = initial_slots(starting_tiles);
    Game {
        board: Board { slots: slots },
        players: players,
        turn: 1,
        merge_decision: None
    }
}

fn choose_tiles(tiles: Vec<Tile>, count: u8) -> (Vec<Tile>, Vec<Tile>) {
    let mut remaining_tiles = tiles;
    let mut random_tiles = Vec::new();
    for _ in 0..count {
        let random_index = rand::random::<usize>() % remaining_tiles.len();
        random_tiles.push(remaining_tiles.remove(random_index));
    }
    (random_tiles, remaining_tiles)
}

fn new_players(tiles: Vec<Tile>) -> Vec<Player>{
    let init_players: Vec<Player> = Vec::new();
    let (players, _) = (0..PLAYERS)
        .fold( (init_players, tiles), | (mut v, remaining), i | {
            let (player_tiles, new_remaining) = choose_tiles(remaining, 6);
            let player = new_player(i, player_tiles);
            v.push(player);
            (v, new_remaining)
        });
    players
}

pub fn new_player(id: PlayerId, tiles: Vec<Tile>) -> Player {
    Player { id: id, money: 6000, shares: empty_shares(), tiles: tiles }
}

fn empty_shares() -> PlayerShares {
    PlayerShares { luxor: 0, tower: 0, american: 0, festival: 0, worldwide: 0, continental: 0, imperial: 0 }
}

pub fn initial_slots(starting_tiles: Vec<Tile>) -> Vec<Slot> {
    (0..ROWS).flat_map(|row| -> Vec<Slot> {
        (0..COLS).map(|col| {
            Slot {
                row: row,
                col: col,
                hotel: None,
                has_tile: has_tile_on_slot(&starting_tiles, row, col)
            }
        }).collect()
    }).collect()
}

fn has_tile_on_slot(tiles: & Vec<Tile>, row: u8, col: u8) -> bool {
    tiles.iter().any(|t| t.row == row && t.col == col)
}

pub fn play_turn(game: &Game, action: Action) -> Game {
    match action {
        Action::PlaceTile { player: player, tile: tile } => {
            place_tile(game, player, &tile)
        }
        _ => panic!(format!("I don't know how to play a turn with action {:?}", action))
    }
}

fn place_tile(game: &Game, player: u8, tile: &Tile) -> Game {
    Game {
        board: place_tile_on_board(&game.board, &tile),
        players: game.players.clone(),
        turn: game.turn,
        merge_decision: game.merge_decision
    }
}

fn place_tile_on_board(board: &Board, tile: &Tile) -> Board {
    println!("Tile {:?}", tile);
    let slots = board.slots
        .iter()
        .map(|s| {
            if s.row == tile.row && s.col == tile.col {
                println!("Match! ({},{})", s.row, s.col);
                Slot { row: s.row, col: s.col, has_tile: true, hotel: s.hotel.clone() }
            } else {
                println!("No match! ({},{})", s.row, s.col);
                s.clone()
            }
        })
        .collect();
    Board { slots: slots }
}

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

    type BoardTiles = [[i32; COLS as usize]; ROWS as usize];
    type PlayerTiles = [[(i32,i32); 6]; PLAYERS as usize];

    #[test]
    fn players_start_with_six_tiles() {
        let game = new_game();
        for player in game.players {
            assert_eq!(player.tiles.len(), 6);
        }
    }

    #[test]
    fn players_start_with_6000_in_cash() {
        let game = new_game();
        for player in game.players {
            assert_eq!(player.money, 6000);
        }
    }

    #[test]
    fn players_start_with_zero_shares() {
        let game = new_game();
        for player in game.players {
            assert_eq!(player.shares.luxor, 0);
            assert_eq!(player.shares.tower, 0);
            assert_eq!(player.shares.american, 0);
            assert_eq!(player.shares.festival, 0);
            assert_eq!(player.shares.worldwide, 0);
            assert_eq!(player.shares.continental, 0);
            assert_eq!(player.shares.imperial, 0);
        }
    }

    #[test]
    fn game_starts_with_four_placed_tiles() {
        let game = new_game();
        let board_tiles = game.board.slots.iter().filter(|s| s.has_tile).count();
        assert_eq!(board_tiles, 4)
    }

    #[test]
    fn placing_a_tile_adds_tile_to_board() {
        let start_tiles = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
        let end_tiles =   [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
        let player_tiles = [[ (0,0), (0,1), (0,2), (0,3), (0,4), (0,5) ],
                            [ (1,0), (1,1), (1,2), (1,3), (1,4), (1,5) ],
                            [ (2,2), (2,1), (2,2), (2,3), (2,4), (2,5) ],
                            [ (3,3), (3,1), (3,2), (3,3), (3,4), (3,5) ]];
        let game = new_game_with_tiles(start_tiles, player_tiles);
        let tile_to_place = Tile { row: 5, col: 11 };
        let action = Action::PlaceTile { player: 1, tile: tile_to_place };
        let game_after = play_turn(&game, action);
        assert_boards_equal(&tiles_to_board(&end_tiles), &game_after.board);
    }

    fn new_game_with_tiles(start_tiles: BoardTiles, player_tiles: PlayerTiles) -> Game {
        let (starting_tiles, remaining) = board_tiles_to_tiles(&start_tiles);
        let players = player_tiles
            .iter()
            .enumerate()
            .map(|(i, tiles)| {
                let _tiles = tiles
                    .iter()
                    .map(|&(r,c)| Tile { row: r as u8, col: c as u8 } )
                    .collect();
                new_player(i as u8, _tiles)
            })
            .collect();
        let slots = initial_slots(starting_tiles);
        Game {
            board: Board { slots: slots },
            players: players,
            turn: 1,
            merge_decision: None
        }
    }

    fn board_tiles_to_tiles(tiles: &BoardTiles) -> (Vec<Tile>, Vec<Tile>) {
        let mut chosen = Vec::new();
        let mut others = Vec::new();
        for row in 0..tiles.len() {
            for col in 0..tiles[0].len() {
                let tile = Tile { row: row as u8, col: col as u8 };
                if tiles[row][col] == 1 {
                    chosen.push(tile)
                } else {
                    others.push(tile)
                }
            }
        }
        (chosen, others)
    }

    fn assert_boards_equal(expected: &Board, actual: &Board) {
        let are_equal = expected.slots
            .iter()
            .zip(actual.slots.iter())
            .all(|(left, right)| *left == *right );
        let error_msg = format!("\n\nBoard did not have expected tiles.\n\nExpected tiles:\n{}\n\nActual tiles:\n{}\n\n", print_board(&expected), print_board(&actual));
        assert!(are_equal, error_msg);
    }

    fn tiles_to_board(tiles: &BoardTiles) -> Board {
        let board_tiles = tiles
            .iter()
            .enumerate()
            .flat_map(|(row, row_tiles)| {
                let slots: Vec<Slot> = row_tiles
                    .iter()
                    .enumerate()
                    .map(|(col, val): (usize, &i32)| {
                        let has_tile = *val == 1;
                        Slot { row: row as u8, col: col as u8, has_tile: has_tile, hotel: None }
                    })
                    .collect();
                slots
            }).collect();
        Board { slots: board_tiles }
    }

    fn row_to_char<'a>(row: u8) -> &'a str {
        let mapping = ["A", "B", "C", "D", "E", "F", "G", "H", "I"];
        mapping[row as usize]
    }

    fn print_tile(slot: &Slot) -> String{
        format!("{}{}", row_to_char(slot.row), slot.col + 1)
    }

    fn print_board(board: &Board) -> String {
        let mut string = String::new();
        string.push_str("   1  2  3  4  5  6  7  8  9  10 11 12\n");
        for row in board.slots.chunks(COLS as usize) {
            let row_char = row_to_char(row[0].row);
            string.push_str(&format!("{}  ", row_char));
            for slot in row {
                string.push_str(&format!("{:<2} ", if slot.has_tile { '\u{25FC}' } else { '\u{25FB}' }));
            }
            string.push_str(&format!("{}\n", row_char));
        }
        string.push_str("   1  2  3  4  5  6  7  8  9  10 11 12");
        string
    }
}
<commit_msg>Remove print statements<commit_after>extern crate rand;

pub const ROWS: u8 = 9;
pub const COLS: u8 = 12;
pub const TILES: u8 = 108;
pub const PLAYERS: u8 = 4;

#[derive(RustcDecodable, RustcEncodable, Clone)]
pub struct Game {
    players: Vec<Player>,
    board: Board,
    turn: PlayerId,
    merge_decision: Option<PlayerId>
}

#[derive(RustcDecodable, RustcEncodable, Clone)]
pub struct Player {
    id: PlayerId,
    money: i32,
    shares: PlayerShares,
    tiles: Vec<Tile>
}

pub type PlayerId = u8;

#[derive(RustcDecodable, RustcEncodable, Clone)]
pub struct PlayerShares {
    luxor: u8,
    tower: u8,
    american: u8,
    festival: u8,
    worldwide: u8,
    continental: u8,
    imperial: u8
}

#[derive(RustcDecodable, RustcEncodable, Clone, Debug)]
pub struct Tile { row: u8, col: u8 }

#[derive(RustcDecodable, RustcEncodable, Clone)]
pub struct Board {
    slots: Vec<Slot>
}

#[derive(RustcDecodable, RustcEncodable, Clone, PartialEq)]
pub struct Slot {
    row: u8,
    col: u8,
    has_tile: bool,
    hotel: Option<Hotel>
}

#[derive(RustcDecodable, RustcEncodable, Clone, PartialEq)]
pub enum Hotel { Luxor, Tower, American, Festival, Worldwide, Continental, Imperial }

#[derive(Debug)]
pub enum Action {
    PlaceTile { player: PlayerId, tile: Tile },
    HandleMergeStocks { hold: u8, sell: u8, trade: u8 },
    BuyStocks,
    DrawTile,
    EndGame
}

pub fn new_game() -> Game {
    let all_tiles = (0..TILES).map(|i| Tile { row: i / COLS, col: i % ROWS }).collect();
    let (starting_tiles, remaining_tiles) = choose_tiles(all_tiles, PLAYERS);
    let players = new_players(remaining_tiles);
    let slots = initial_slots(starting_tiles);
    Game {
        board: Board { slots: slots },
        players: players,
        turn: 1,
        merge_decision: None
    }
}

fn choose_tiles(tiles: Vec<Tile>, count: u8) -> (Vec<Tile>, Vec<Tile>) {
    let mut remaining_tiles = tiles;
    let mut random_tiles = Vec::new();
    for _ in 0..count {
        let random_index = rand::random::<usize>() % remaining_tiles.len();
        random_tiles.push(remaining_tiles.remove(random_index));
    }
    (random_tiles, remaining_tiles)
}

fn new_players(tiles: Vec<Tile>) -> Vec<Player>{
    let init_players: Vec<Player> = Vec::new();
    let (players, _) = (0..PLAYERS)
        .fold( (init_players, tiles), | (mut v, remaining), i | {
            let (player_tiles, new_remaining) = choose_tiles(remaining, 6);
            let player = new_player(i, player_tiles);
            v.push(player);
            (v, new_remaining)
        });
    players
}

pub fn new_player(id: PlayerId, tiles: Vec<Tile>) -> Player {
    Player { id: id, money: 6000, shares: empty_shares(), tiles: tiles }
}

fn empty_shares() -> PlayerShares {
    PlayerShares { luxor: 0, tower: 0, american: 0, festival: 0, worldwide: 0, continental: 0, imperial: 0 }
}

pub fn initial_slots(starting_tiles: Vec<Tile>) -> Vec<Slot> {
    (0..ROWS).flat_map(|row| -> Vec<Slot> {
        (0..COLS).map(|col| {
            Slot {
                row: row,
                col: col,
                hotel: None,
                has_tile: has_tile_on_slot(&starting_tiles, row, col)
            }
        }).collect()
    }).collect()
}

fn has_tile_on_slot(tiles: & Vec<Tile>, row: u8, col: u8) -> bool {
    tiles.iter().any(|t| t.row == row && t.col == col)
}

pub fn play_turn(game: &Game, action: Action) -> Game {
    match action {
        Action::PlaceTile { player: player, tile: tile } => {
            place_tile(game, player, &tile)
        }
        _ => panic!(format!("I don't know how to play a turn with action {:?}", action))
    }
}

fn place_tile(game: &Game, player: u8, tile: &Tile) -> Game {
    Game {
        board: place_tile_on_board(&game.board, &tile),
        players: game.players.clone(),
        turn: game.turn,
        merge_decision: game.merge_decision
    }
}

fn place_tile_on_board(board: &Board, tile: &Tile) -> Board {
    let slots = board.slots
        .iter()
        .map(|s| {
            if s.row == tile.row && s.col == tile.col {
                Slot { row: s.row, col: s.col, has_tile: true, hotel: s.hotel.clone() }
            } else {
                s.clone()
            }
        })
        .collect();
    Board { slots: slots }
}

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

    type BoardTiles = [[i32; COLS as usize]; ROWS as usize];
    type PlayerTiles = [[(i32,i32); 6]; PLAYERS as usize];

    #[test]
    fn players_start_with_six_tiles() {
        let game = new_game();
        for player in game.players {
            assert_eq!(player.tiles.len(), 6);
        }
    }

    #[test]
    fn players_start_with_6000_in_cash() {
        let game = new_game();
        for player in game.players {
            assert_eq!(player.money, 6000);
        }
    }

    #[test]
    fn players_start_with_zero_shares() {
        let game = new_game();
        for player in game.players {
            assert_eq!(player.shares.luxor, 0);
            assert_eq!(player.shares.tower, 0);
            assert_eq!(player.shares.american, 0);
            assert_eq!(player.shares.festival, 0);
            assert_eq!(player.shares.worldwide, 0);
            assert_eq!(player.shares.continental, 0);
            assert_eq!(player.shares.imperial, 0);
        }
    }

    #[test]
    fn game_starts_with_four_placed_tiles() {
        let game = new_game();
        let board_tiles = game.board.slots.iter().filter(|s| s.has_tile).count();
        assert_eq!(board_tiles, 4)
    }

    #[test]
    fn placing_a_tile_adds_tile_to_board() {
        let start_tiles = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
        let end_tiles =   [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
        let player_tiles = [[ (0,0), (0,1), (0,2), (0,3), (0,4), (0,5) ],
                            [ (1,0), (1,1), (1,2), (1,3), (1,4), (1,5) ],
                            [ (2,2), (2,1), (2,2), (2,3), (2,4), (2,5) ],
                            [ (3,3), (3,1), (3,2), (3,3), (3,4), (3,5) ]];
        let game = new_game_with_tiles(start_tiles, player_tiles);
        let tile_to_place = Tile { row: 5, col: 11 };
        let action = Action::PlaceTile { player: 1, tile: tile_to_place };
        let game_after = play_turn(&game, action);
        assert_boards_equal(&tiles_to_board(&end_tiles), &game_after.board);
    }

    fn new_game_with_tiles(start_tiles: BoardTiles, player_tiles: PlayerTiles) -> Game {
        let (starting_tiles, remaining) = board_tiles_to_tiles(&start_tiles);
        let players = player_tiles
            .iter()
            .enumerate()
            .map(|(i, tiles)| {
                let _tiles = tiles
                    .iter()
                    .map(|&(r,c)| Tile { row: r as u8, col: c as u8 } )
                    .collect();
                new_player(i as u8, _tiles)
            })
            .collect();
        let slots = initial_slots(starting_tiles);
        Game {
            board: Board { slots: slots },
            players: players,
            turn: 1,
            merge_decision: None
        }
    }

    fn board_tiles_to_tiles(tiles: &BoardTiles) -> (Vec<Tile>, Vec<Tile>) {
        let mut chosen = Vec::new();
        let mut others = Vec::new();
        for row in 0..tiles.len() {
            for col in 0..tiles[0].len() {
                let tile = Tile { row: row as u8, col: col as u8 };
                if tiles[row][col] == 1 {
                    chosen.push(tile)
                } else {
                    others.push(tile)
                }
            }
        }
        (chosen, others)
    }

    fn assert_boards_equal(expected: &Board, actual: &Board) {
        let are_equal = expected.slots
            .iter()
            .zip(actual.slots.iter())
            .all(|(left, right)| *left == *right );
        let error_msg = format!("\n\nBoard did not have expected tiles.\n\nExpected tiles:\n{}\n\nActual tiles:\n{}\n\n", print_board(&expected), print_board(&actual));
        assert!(are_equal, error_msg);
    }

    fn tiles_to_board(tiles: &BoardTiles) -> Board {
        let board_tiles = tiles
            .iter()
            .enumerate()
            .flat_map(|(row, row_tiles)| {
                let slots: Vec<Slot> = row_tiles
                    .iter()
                    .enumerate()
                    .map(|(col, val): (usize, &i32)| {
                        let has_tile = *val == 1;
                        Slot { row: row as u8, col: col as u8, has_tile: has_tile, hotel: None }
                    })
                    .collect();
                slots
            }).collect();
        Board { slots: board_tiles }
    }

    fn row_to_char<'a>(row: u8) -> &'a str {
        let mapping = ["A", "B", "C", "D", "E", "F", "G", "H", "I"];
        mapping[row as usize]
    }

    fn print_tile(slot: &Slot) -> String{
        format!("{}{}", row_to_char(slot.row), slot.col + 1)
    }

    fn print_board(board: &Board) -> String {
        let mut string = String::new();
        string.push_str("   1  2  3  4  5  6  7  8  9  10 11 12\n");
        for row in board.slots.chunks(COLS as usize) {
            let row_char = row_to_char(row[0].row);
            string.push_str(&format!("{}  ", row_char));
            for slot in row {
                string.push_str(&format!("{:<2} ", if slot.has_tile { '\u{25FC}' } else { '\u{25FB}' }));
            }
            string.push_str(&format!("{}\n", row_char));
        }
        string.push_str("   1  2  3  4  5  6  7  8  9  10 11 12");
        string
    }
}
<|endoftext|>


<commit_before>use cssparser::Parser;

use parsers::Parse;
use parsers::ParseError;

#[derive(Debug, Clone, PartialEq)]
pub enum IRI {
    None,
    Resource(String),
}

impl Default for IRI {
    fn default() -> IRI {
        IRI::None
    }
}

impl IRI {
    /// Returns the contents of an `IRI::Resource`, or `None`
    pub fn get(&self) -> Option<&str> {
        match *self {
            IRI::None => None,
            IRI::Resource(ref s) => Some(s.as_ref()),
        }
    }
}

impl Parse for IRI {
    type Data = ();
    type Err = ParseError;

    fn parse(parser: &mut Parser<'_, '_>, _: Self::Data) -> Result<IRI, ParseError> {
        if parser.try(|i| i.expect_ident_matching("none")).is_ok() {
            Ok(IRI::None)
        } else {
            let url = parser
                .expect_url()
                .map_err(|_| ParseError::new("expected url"))?;

            parser
                .expect_exhausted()
                .map_err(|_| ParseError::new("expected url"))?;

            Ok(IRI::Resource(url.as_ref().to_owned()))
        }
    }
}

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

    #[test]
    fn parses_none() {
        assert_eq!(IRI::parse_str("none", ()), Ok(IRI::None));
    }

    #[test]
    fn parses_url() {
        assert_eq!(
            IRI::parse_str("url(foo)", ()),
            Ok(IRI::Resource("foo".to_string()))
        );

        // be permissive if the closing ) is missing
        assert_eq!(
            IRI::parse_str("url(", ()),
            Ok(IRI::Resource("".to_string()))
        );
        assert_eq!(
            IRI::parse_str("url(foo", ()),
            Ok(IRI::Resource("foo".to_string()))
        );

        assert!(IRI::parse_str("", ()).is_err());
        assert!(IRI::parse_str("foo", ()).is_err());
        assert!(IRI::parse_str("url(foo)bar", ()).is_err());
    }

    #[test]
    fn get() {
        assert_eq!(IRI::None.get(), None);
        assert_eq!(IRI::Resource(String::from("foo")).get(), Some("foo"));
    }
}
<commit_msg>Document the purpose of the IRI type<commit_after>use cssparser::Parser;

use parsers::Parse;
use parsers::ParseError;

/// Used where style properties take a funciri or "none"
///
/// This is not to be used for values which don't come from properties.
/// For example, the `xlink:href` attribute in the `<image>` element
/// does not take a funciri value (which looks like `url(...)`), but rather
/// it takes a plain URL.  Use the `Href` type in that case.
#[derive(Debug, Clone, PartialEq)]
pub enum IRI {
    None,
    Resource(String),
}

impl Default for IRI {
    fn default() -> IRI {
        IRI::None
    }
}

impl IRI {
    /// Returns the contents of an `IRI::Resource`, or `None`
    pub fn get(&self) -> Option<&str> {
        match *self {
            IRI::None => None,
            IRI::Resource(ref s) => Some(s.as_ref()),
        }
    }
}

impl Parse for IRI {
    type Data = ();
    type Err = ParseError;

    fn parse(parser: &mut Parser<'_, '_>, _: Self::Data) -> Result<IRI, ParseError> {
        if parser.try(|i| i.expect_ident_matching("none")).is_ok() {
            Ok(IRI::None)
        } else {
            let url = parser
                .expect_url()
                .map_err(|_| ParseError::new("expected url"))?;

            parser
                .expect_exhausted()
                .map_err(|_| ParseError::new("expected url"))?;

            Ok(IRI::Resource(url.as_ref().to_owned()))
        }
    }
}

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

    #[test]
    fn parses_none() {
        assert_eq!(IRI::parse_str("none", ()), Ok(IRI::None));
    }

    #[test]
    fn parses_url() {
        assert_eq!(
            IRI::parse_str("url(foo)", ()),
            Ok(IRI::Resource("foo".to_string()))
        );

        // be permissive if the closing ) is missing
        assert_eq!(
            IRI::parse_str("url(", ()),
            Ok(IRI::Resource("".to_string()))
        );
        assert_eq!(
            IRI::parse_str("url(foo", ()),
            Ok(IRI::Resource("foo".to_string()))
        );

        assert!(IRI::parse_str("", ()).is_err());
        assert!(IRI::parse_str("foo", ()).is_err());
        assert!(IRI::parse_str("url(foo)bar", ()).is_err());
    }

    #[test]
    fn get() {
        assert_eq!(IRI::None.get(), None);
        assert_eq!(IRI::Resource(String::from("foo")).get(), Some("foo"));
    }
}
<|endoftext|>


<commit_before>extern crate num_cpus;
extern crate itertools;
extern crate rand;
extern crate crossbeam;

use self::rand::{thread_rng, Rng};
use self::itertools::Itertools;
use self::crossbeam::{scope, ScopedJoinHandle};

use std::ops::Range;
use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::sync::{Mutex, RwLock, MutexGuard};
use std::sync::mpsc::{Sender, Receiver, channel};
use std::thread::spawn;

use task::{TaskGenerator, Task};
use candidate::{WorkingCandidate, Candidate};
use solution::Solution;
use scaling::{ScalingFunction, proportionate};
use result::{Result as AbcResult, Error as AbcError};

/// Manages the parameters of the ABC algorithm.
pub struct HiveBuilder<S: Solution> {
    workers: usize,
    observers: usize,
    retries: usize,
    builder: Mutex<S::Builder>,
    threads: usize,
    scale: Box<ScalingFunction>,
}

impl<S: Solution> HiveBuilder<S> {
    /// Creates a new hive.
    ///
    /// * `builder` - Factory-like state that can be used while generating solutions.
    /// * `workers` - Number of working solution candidates to maintain at a time.
    pub fn new(builder: S::Builder, workers: usize) -> HiveBuilder<S> {
        if workers == 0 {
            panic!("HiveBuilder must have at least one worker.");
        }

        HiveBuilder {
            workers: workers,
            observers: workers,
            retries: workers,

            builder: Mutex::new(builder),
            threads: num_cpus::get(),
            scale: proportionate(),
        }
    }

    /// Sets the number of "bees" that will pick a candidate to work on at random.
    ///
    /// This defaults to the number of workers.
    pub fn set_observers(mut self, observers: usize) -> HiveBuilder<S> {
        self.observers = observers;
        self
    }

    /// Sets the number of times a candidate can go unimproved before being reinitialized.
    ///
    /// This defaults to the number of workers.
    pub fn set_retries(mut self, retries: usize) -> HiveBuilder<S> {
        self.retries = retries;
        self
    }

    /// Sets the number of worker threads to use while running.
    pub fn set_threads(mut self, threads: usize) -> HiveBuilder<S> {
        self.threads = threads;
        self
    }

    /// Sets the scaling function for observers to use.
    pub fn set_scaling(mut self, scale: Box<ScalingFunction>) -> HiveBuilder<S> {
        self.scale = scale;
        self
    }

    /// Activates the `HiveBuilder` to create a runnable object.
    pub fn build(self) -> AbcResult<Hive<S>> {
        Hive::new(self)
    }
}

/// Runs the ABC algorithm, maintaining any necessary state.
pub struct Hive<S: Solution> {
    hive: HiveBuilder<S>,

    working: Vec<RwLock<WorkingCandidate<S>>>,
    best: Mutex<Candidate<S>>,

    tasks: Mutex<Option<TaskGenerator>>,
    streaming: Option<Mutex<Sender<Candidate<S>>>>,
}

impl<S: Solution> Hive<S> {
    fn new(hive: HiveBuilder<S>) -> AbcResult<Hive<S>> {
        let tokens: Mutex<Range<usize>> = Mutex::new(0..hive.workers);
        let candidates = Mutex::new(Vec::with_capacity(hive.workers));
        let mut handles = Vec::with_capacity(hive.threads);

        try!(crossbeam::scope(|scope| {
            for _ in 0..hive.threads {
                handles.push(scope.spawn(|| {
                    while let Some(_) = tokens.lock().unwrap().next() {
                        let mut builder = match hive.builder.lock() {
                            Ok(b) => b,
                            Err(err) => return Err(AbcError::from(err)),
                        };
                        let solution = S::make(&mut builder);
                        drop(builder);
                        let candidate = Candidate::new(solution);
                        try!(candidates.lock()).push(candidate);
                    }
                    Ok(())
                }));
            }

            handles.drain(..)
                   .fold(Ok(()), |result, handle| result.and(handle.join()))
        }));

        let mut candidates = try!(candidates.into_inner());

        let best = {
            let best_candidate = candidates.iter()
                                           .fold1(|best, next| {
                                               if next.fitness > best.fitness {
                                                   next
                                               } else {
                                                   best
                                               }
                                           })
                                           .unwrap();
            Mutex::new(best_candidate.clone())
        };

        let working = candidates.drain(..)
                                .map(|c| RwLock::new(WorkingCandidate::new(c, hive.retries)))
                                .collect::<Vec<_>>();

        Ok(Hive {
            working: working,
            best: best,
            hive: hive,
            tasks: Mutex::new(None),
            streaming: None,
        })
    }

    fn current_working(&self) -> AbcResult<Vec<Candidate<S>>> {
        let mut current_working = Vec::with_capacity(self.working.len());
        for candidate_mutex in &self.working {
            let read_guard = try!(candidate_mutex.read());
            current_working.push(read_guard.candidate.clone())
        }
        Ok(current_working)
    }

    /// Returns a guard for the current best solution found by the hive.
    ///
    /// If the hive is running, you should drop the guard returned by this
    /// function as soon as convenient, since the logic of the hive can block
    /// on the availability of the associated mutex. If you plan on performing
    /// expensive computations, you should `drop` the guard as soon as
    /// possible, or acquire and clone it within a small block.
    pub fn get(&self) -> AbcResult<MutexGuard<Candidate<S>>> {
        self.best.lock().map_err(AbcError::from)
    }

    fn consider_improvement(&self, candidate: &Candidate<S>) -> AbcResult<()> {
        let mut best_guard = try!(self.best.lock());
        if candidate.fitness > best_guard.fitness {
            *best_guard = candidate.clone();
            if let Some(mutex) = self.streaming.as_ref() {
                // We're streaming, so we need to post the improved candidate.
                let sender_guard = try!(mutex.lock());
                // If this errors, the receiver was dropped, so we're done.
                if let Err(_) = sender_guard.send(candidate.clone()) {
                    try!(self.stop());
                }
            }
        }
        Ok(())
    }

    fn work_on(&self, current_working: &[Candidate<S>], n: usize) -> AbcResult<()> {
        let variant = Candidate::new(S::explore(current_working, n));
        let mut write_guard = try!(self.working[n].write());
        if variant.fitness > write_guard.candidate.fitness {
            *write_guard = WorkingCandidate::new(variant, self.hive.retries);
            try!(self.consider_improvement(&write_guard.candidate));
        } else {
            write_guard.deplete();
            // Scouting has been folded into the working process
            if write_guard.expired() {
                let mut builder = try!(self.hive.builder.lock());
                let candidate = Candidate::new(S::make(&mut builder));
                drop(builder);
                *write_guard = WorkingCandidate::new(candidate, self.hive.retries);
                try!(self.consider_improvement(&write_guard.candidate));
            }
        }
        Ok(())
    }

    fn choose(&self, current_working: &[Candidate<S>]) -> usize {
        let fitnesses = (self.hive.scale)(current_working.iter()
                                                         .map(|candidate| candidate.fitness)
                                                         .collect::<Vec<f64>>());

        let running_totals = fitnesses.iter()
                                      .scan(0f64, |total, fitness| {
                                          *total += *fitness;
                                          Some(*total)
                                      })
                                      .collect::<Vec<f64>>();

        // Multiplying the choice point is equivalent to, and more efficient than, normalizing
        // all of the scaled fitnesses and having a choice point in [0,1)
        let total_fitness = running_totals.last().unwrap();
        let choice_point = thread_rng().next_f64() * total_fitness;

        for (i, total) in running_totals.iter().enumerate() {
            if *total > choice_point {
                return i;
            }
        }
        unreachable!();
    }

    fn execute(&self, task: &Task) -> AbcResult<()> {
        let current_working = try!(self.current_working());
        let index = match *task {
            Task::Worker(n) => n,
            Task::Observer(_) => self.choose(&current_working),
        };
        self.work_on(&current_working, index)
    }

    fn run(&self, tasks: TaskGenerator) -> AbcResult<()> {
        let mut guard = try!(self.tasks.lock());
        *guard = Some(tasks);
        drop(guard);

        let mut handles: Vec<ScopedJoinHandle<AbcResult<()>>> = Vec::new();

        scope(|scope| {
            for _ in 0..self.hive.threads {
                handles.push(scope.spawn(|| {
                    loop {
                        let mut guard = try!(self.tasks.lock());
                        let task = guard.as_mut().and_then(|gen| gen.next());
                        drop(guard);

                        match task {
                            Some(t) => try!(self.execute(&t)),
                            None => return Ok(()),
                        };
                    }
                }));
            }

            // Returns `Ok(())` only if all threads join cleanly, and the task
            // cycle is successfully cleared away.
            //
            // We avoid `try!` because we want all of the following logic to
            // execute unconditionally.
            handles.drain(..)
                   .fold(Ok(()), |result, handle| result.and(handle.join()))
                   .and(self.tasks
                            .lock()
                            .map(|mut tasks_guard| *tasks_guard = None)
                            .map_err(AbcError::from))
        })
    }

    /// Runs for a fixed number of rounds, then return the best solution found.
    ///
    /// If one of the worker threads panics while working, this will return
    /// `Err(abc::Error)`. Otherwise, it will return `Ok` with a `Candidate`.
    pub fn run_for_rounds(&self, rounds: usize) -> AbcResult<Candidate<S>> {
        let tasks = TaskGenerator::new(self.hive.workers, self.hive.observers).max_rounds(rounds);
        try!(self.run(tasks));
        self.get().map(|guard| guard.clone())
    }

    /// Runs indefinitely in the background, providing a stream of results.
    ///
    /// This method consumes the hive, which will run until the `HiveBuilder` object
    /// is dropped. It returns an `mpsc::Receiver`, which receives a
    /// `Candidate` each time the hive improves on its best solution.
    pub fn stream(mut self) -> Receiver<Candidate<S>> {
        let (sender, receiver) = channel();
        spawn(move || {
            let tasks = TaskGenerator::new(self.hive.workers, self.hive.observers);
            self.streaming = Some(Mutex::new(sender));
            self.run(tasks)
        });
        receiver
    }

    /// Stops a running hive.
    ///
    /// If a worker thread has panicked, this returns `Err(abc::Error)`.
    pub fn stop(&self) -> AbcResult<()> {
        let mut tasks_guard = try!(self.tasks.lock());
        Ok(tasks_guard.as_mut().map_or((), |t| t.stop()))
    }

    /// Returns the current round of a running hive.
    ///
    /// If a worker thread has panicked and poisoned the task generator lock,
    /// `get_round` will return `Err(abc::Error)`.
    ///
    /// If the hive has not been run, `get_round` will return `Ok(None)`.
    pub fn get_round(&self) -> AbcResult<Option<usize>> {
        let tasks_guard = try!(self.tasks.lock());
        Ok(tasks_guard.as_ref().map(|tasks| tasks.round))
    }
}

impl<S: Solution + Debug> Debug for Hive<S> {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        for mutex in (&self.working).iter() {
            let working = mutex.read().unwrap();
            try!(write!(f, "..{:?}..\n", working.candidate));
        }
        let best_candidate = self.get().unwrap();
        write!(f, ">>{:?}<<", *best_candidate)
    }
}

impl<S: Solution> Drop for Hive<S> {
    fn drop(&mut self) {
        self.stop().unwrap_or(())
    }
}
<commit_msg>Refactor new solutions<commit_after>extern crate num_cpus;
extern crate itertools;
extern crate rand;
extern crate crossbeam;

use self::rand::{thread_rng, Rng};
use self::itertools::Itertools;
use self::crossbeam::{scope, ScopedJoinHandle};

use std::ops::Range;
use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::sync::{Mutex, RwLock, MutexGuard};
use std::sync::mpsc::{Sender, Receiver, channel};
use std::thread::spawn;

use task::{TaskGenerator, Task};
use candidate::{WorkingCandidate, Candidate};
use solution::Solution;
use scaling::{ScalingFunction, proportionate};
use result::{Result as AbcResult, Error as AbcError};

/// Manages the parameters of the ABC algorithm.
pub struct HiveBuilder<S: Solution> {
    workers: usize,
    observers: usize,
    retries: usize,
    builder: Mutex<S::Builder>,
    threads: usize,
    scale: Box<ScalingFunction>,
}

impl<S: Solution> HiveBuilder<S> {
    /// Creates a new hive.
    ///
    /// * `builder` - Factory-like state that can be used while generating solutions.
    /// * `workers` - Number of working solution candidates to maintain at a time.
    pub fn new(builder: S::Builder, workers: usize) -> HiveBuilder<S> {
        if workers == 0 {
            panic!("HiveBuilder must have at least one worker.");
        }

        HiveBuilder {
            workers: workers,
            observers: workers,
            retries: workers,

            builder: Mutex::new(builder),
            threads: num_cpus::get(),
            scale: proportionate(),
        }
    }

    /// Sets the number of "bees" that will pick a candidate to work on at random.
    ///
    /// This defaults to the number of workers.
    pub fn set_observers(mut self, observers: usize) -> HiveBuilder<S> {
        self.observers = observers;
        self
    }

    /// Sets the number of times a candidate can go unimproved before being reinitialized.
    ///
    /// This defaults to the number of workers.
    pub fn set_retries(mut self, retries: usize) -> HiveBuilder<S> {
        self.retries = retries;
        self
    }

    /// Sets the number of worker threads to use while running.
    pub fn set_threads(mut self, threads: usize) -> HiveBuilder<S> {
        self.threads = threads;
        self
    }

    /// Sets the scaling function for observers to use.
    pub fn set_scaling(mut self, scale: Box<ScalingFunction>) -> HiveBuilder<S> {
        self.scale = scale;
        self
    }

    /// Activates the `HiveBuilder` to create a runnable object.
    pub fn build(self) -> AbcResult<Hive<S>> {
        Hive::new(self)
    }
}

/// Runs the ABC algorithm, maintaining any necessary state.
pub struct Hive<S: Solution> {
    hive: HiveBuilder<S>,

    working: Vec<RwLock<WorkingCandidate<S>>>,
    best: Mutex<Candidate<S>>,

    tasks: Mutex<Option<TaskGenerator>>,
    streaming: Option<Mutex<Sender<Candidate<S>>>>,
}

impl<S: Solution> Hive<S> {
    fn new(hive: HiveBuilder<S>) -> AbcResult<Hive<S>> {
        let tokens: Mutex<Range<usize>> = Mutex::new(0..hive.workers);
        let candidates = Mutex::new(Vec::with_capacity(hive.workers));
        let mut handles = Vec::<ScopedJoinHandle<AbcResult<()>>>::with_capacity(hive.threads);

        try!(crossbeam::scope(|scope| {
            for _ in 0..hive.threads {
                handles.push(scope.spawn(|| {
                    while let Some(_) = tokens.lock().unwrap().next() {
                        let mut builder = try!(hive.builder.lock());
                        let solution = S::make(&mut builder);
                        drop(builder);
                        let candidate = Candidate::new(solution);
                        try!(candidates.lock()).push(candidate);
                    }
                    Ok(())
                }));
            }

            handles.drain(..)
                   .fold(Ok(()), |result, handle| result.and(handle.join()))
        }));

        let mut candidates = try!(candidates.into_inner());

        let best = {
            let best_candidate = candidates.iter()
                                           .fold1(|best, next| {
                                               if next.fitness > best.fitness {
                                                   next
                                               } else {
                                                   best
                                               }
                                           })
                                           .unwrap();
            Mutex::new(best_candidate.clone())
        };

        let working = candidates.drain(..)
                                .map(|c| RwLock::new(WorkingCandidate::new(c, hive.retries)))
                                .collect::<Vec<_>>();

        Ok(Hive {
            working: working,
            best: best,
            hive: hive,
            tasks: Mutex::new(None),
            streaming: None,
        })
    }

    fn current_working(&self) -> AbcResult<Vec<Candidate<S>>> {
        let mut current_working = Vec::with_capacity(self.working.len());
        for candidate_mutex in &self.working {
            let read_guard = try!(candidate_mutex.read());
            current_working.push(read_guard.candidate.clone())
        }
        Ok(current_working)
    }

    /// Returns a guard for the current best solution found by the hive.
    ///
    /// If the hive is running, you should drop the guard returned by this
    /// function as soon as convenient, since the logic of the hive can block
    /// on the availability of the associated mutex. If you plan on performing
    /// expensive computations, you should `drop` the guard as soon as
    /// possible, or acquire and clone it within a small block.
    pub fn get(&self) -> AbcResult<MutexGuard<Candidate<S>>> {
        self.best.lock().map_err(AbcError::from)
    }

    fn consider_improvement(&self, candidate: &Candidate<S>) -> AbcResult<()> {
        let mut best_guard = try!(self.best.lock());
        if candidate.fitness > best_guard.fitness {
            *best_guard = candidate.clone();
            if let Some(mutex) = self.streaming.as_ref() {
                // We're streaming, so we need to post the improved candidate.
                let sender_guard = try!(mutex.lock());
                // If this errors, the receiver was dropped, so we're done.
                if let Err(_) = sender_guard.send(candidate.clone()) {
                    try!(self.stop());
                }
            }
        }
        Ok(())
    }

    fn work_on(&self, current_working: &[Candidate<S>], n: usize) -> AbcResult<()> {
        let variant = Candidate::new(S::explore(current_working, n));
        let mut write_guard = try!(self.working[n].write());
        if variant.fitness > write_guard.candidate.fitness {
            *write_guard = WorkingCandidate::new(variant, self.hive.retries);
            try!(self.consider_improvement(&write_guard.candidate));
        } else {
            write_guard.deplete();
            // Scouting has been folded into the working process
            if write_guard.expired() {
                let mut builder = try!(self.hive.builder.lock());
                let solution = S::make(&mut builder);
                drop(builder);
                let candidate = Candidate::new(solution);
                *write_guard = WorkingCandidate::new(candidate, self.hive.retries);
                try!(self.consider_improvement(&write_guard.candidate));
            }
        }
        Ok(())
    }

    fn choose(&self, current_working: &[Candidate<S>]) -> usize {
        let fitnesses = (self.hive.scale)(current_working.iter()
                                                         .map(|candidate| candidate.fitness)
                                                         .collect::<Vec<f64>>());

        let running_totals = fitnesses.iter()
                                      .scan(0f64, |total, fitness| {
                                          *total += *fitness;
                                          Some(*total)
                                      })
                                      .collect::<Vec<f64>>();

        // Multiplying the choice point is equivalent to, and more efficient than, normalizing
        // all of the scaled fitnesses and having a choice point in [0,1)
        let total_fitness = running_totals.last().unwrap();
        let choice_point = thread_rng().next_f64() * total_fitness;

        for (i, total) in running_totals.iter().enumerate() {
            if *total > choice_point {
                return i;
            }
        }
        unreachable!();
    }

    fn execute(&self, task: &Task) -> AbcResult<()> {
        let current_working = try!(self.current_working());
        let index = match *task {
            Task::Worker(n) => n,
            Task::Observer(_) => self.choose(&current_working),
        };
        self.work_on(&current_working, index)
    }

    fn run(&self, tasks: TaskGenerator) -> AbcResult<()> {
        let mut guard = try!(self.tasks.lock());
        *guard = Some(tasks);
        drop(guard);

        let mut handles: Vec<ScopedJoinHandle<AbcResult<()>>> = Vec::new();

        scope(|scope| {
            for _ in 0..self.hive.threads {
                handles.push(scope.spawn(|| {
                    loop {
                        let mut guard = try!(self.tasks.lock());
                        let task = guard.as_mut().and_then(|gen| gen.next());
                        drop(guard);

                        match task {
                            Some(t) => try!(self.execute(&t)),
                            None => return Ok(()),
                        };
                    }
                }));
            }

            // Returns `Ok(())` only if all threads join cleanly, and the task
            // cycle is successfully cleared away.
            //
            // We avoid `try!` because we want all of the following logic to
            // execute unconditionally.
            handles.drain(..)
                   .fold(Ok(()), |result, handle| result.and(handle.join()))
                   .and(self.tasks
                            .lock()
                            .map(|mut tasks_guard| *tasks_guard = None)
                            .map_err(AbcError::from))
        })
    }

    /// Runs for a fixed number of rounds, then return the best solution found.
    ///
    /// If one of the worker threads panics while working, this will return
    /// `Err(abc::Error)`. Otherwise, it will return `Ok` with a `Candidate`.
    pub fn run_for_rounds(&self, rounds: usize) -> AbcResult<Candidate<S>> {
        let tasks = TaskGenerator::new(self.hive.workers, self.hive.observers).max_rounds(rounds);
        try!(self.run(tasks));
        self.get().map(|guard| guard.clone())
    }

    /// Runs indefinitely in the background, providing a stream of results.
    ///
    /// This method consumes the hive, which will run until the `HiveBuilder` object
    /// is dropped. It returns an `mpsc::Receiver`, which receives a
    /// `Candidate` each time the hive improves on its best solution.
    pub fn stream(mut self) -> Receiver<Candidate<S>> {
        let (sender, receiver) = channel();
        spawn(move || {
            let tasks = TaskGenerator::new(self.hive.workers, self.hive.observers);
            self.streaming = Some(Mutex::new(sender));
            self.run(tasks)
        });
        receiver
    }

    /// Stops a running hive.
    ///
    /// If a worker thread has panicked, this returns `Err(abc::Error)`.
    pub fn stop(&self) -> AbcResult<()> {
        let mut tasks_guard = try!(self.tasks.lock());
        Ok(tasks_guard.as_mut().map_or((), |t| t.stop()))
    }

    /// Returns the current round of a running hive.
    ///
    /// If a worker thread has panicked and poisoned the task generator lock,
    /// `get_round` will return `Err(abc::Error)`.
    ///
    /// If the hive has not been run, `get_round` will return `Ok(None)`.
    pub fn get_round(&self) -> AbcResult<Option<usize>> {
        let tasks_guard = try!(self.tasks.lock());
        Ok(tasks_guard.as_ref().map(|tasks| tasks.round))
    }
}

impl<S: Solution + Debug> Debug for Hive<S> {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        for mutex in (&self.working).iter() {
            let working = mutex.read().unwrap();
            try!(write!(f, "..{:?}..\n", working.candidate));
        }
        let best_candidate = self.get().unwrap();
        write!(f, ">>{:?}<<", *best_candidate)
    }
}

impl<S: Solution> Drop for Hive<S> {
    fn drop(&mut self) {
        self.stop().unwrap_or(())
    }
}
<|endoftext|>


<commit_before>extern crate num_cpus;
extern crate itertools;
extern crate rand;
extern crate crossbeam;

use self::rand::Rng;
use self::itertools::Itertools;
use self::crossbeam::{scope, ScopedJoinHandle};

use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::sync::{Mutex, RwLock, MutexGuard};

use task::{TaskGenerator, Task};
use candidate::{WorkingCandidate, Candidate};
use solution::Solution;
use scaling::{ScalingFunction, proportionate};
use result::{Result as AbcResult, Error as AbcError};

pub struct Hive<S: Solution> {
    workers: usize,
    observers: usize,
    retries: usize,

    working: Vec<RwLock<WorkingCandidate<S>>>,
    best: Mutex<Candidate<S>>,
    tasks: Mutex<Option<TaskGenerator>>,

    threads: usize,
    scale: Box<ScalingFunction>,
}

impl<S: Solution + Debug> Debug for Hive<S> {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        for mutex in (&self.working).iter() {
            let working = mutex.read().unwrap();
            try!(write!(f, "..{:?}..\n", working.candidate));
        }
        let best_candidate = self.get().unwrap();
        write!(f, ">>{:?}<<", *best_candidate)
    }
}

impl<S: Solution> Hive<S> {
    pub fn new(workers: usize, observers: usize, retries: usize) -> Hive<S> {
        if workers == 0 {
            panic!("Hive must have at least one worker.");
        }

        let mut candidates = (0..workers)
            .map(|_| Candidate::new(S::make()))
            .collect::<Vec<_>>();

        let best = {
            let best_candidate = candidates.iter()
                .fold1(|best, next| if next.fitness > best.fitness { next } else { best })
                .unwrap();
            Mutex::new(best_candidate.clone())
        };

        let working = candidates.drain(..)
            .map(|c| RwLock::new(WorkingCandidate::new(c, retries)))
            .collect::<Vec<_>>();

        Hive {
            workers: workers,
            observers: observers,
            retries: retries,

            working: working,
            best: best,
            tasks: Mutex::new(None),

            threads: num_cpus::get(),
            scale: proportionate(),
        }
    }

    pub fn set_threads(mut self, threads: usize) -> Hive<S> {
        self.threads = threads;
        self
    }

    pub fn set_scaling(mut self, scale: Box<ScalingFunction>) -> Hive<S> {
        self.scale = scale;
        self
    }

    fn current_working(&self) -> AbcResult<Vec<Candidate<S>>> {
        let mut current_working = Vec::with_capacity(self.working.len());
        for candidate_mutex in &self.working {
            let read_guard = try!(candidate_mutex.read());
            current_working.push(read_guard.candidate.clone())
        }
        Ok(current_working)
    }

    fn consider_improvement(&self, candidate: &Candidate<S>) -> AbcResult<()> {
        let mut best_guard = try!(self.best.lock());
        if candidate.fitness > best_guard.fitness {
            *best_guard = candidate.clone();
        }
        Ok(())
    }

    fn work_on(&self, current_working: &[Candidate<S>], n: usize) -> AbcResult<()> {
        let variant = Candidate::new(S::explore(current_working, n));

        let mut write_guard = try!(self.working[n].write());
        if variant.fitness > write_guard.candidate.fitness {
            *write_guard = WorkingCandidate::new(variant, self.retries);
            try!(self.consider_improvement(&write_guard.candidate));
        } else {
            write_guard.deplete();
            // Scouting has been folded into the working process
            if write_guard.expired() {
                let candidate = Candidate::new(S::make());
                *write_guard = WorkingCandidate::new(candidate, self.retries);
                try!(self.consider_improvement(&write_guard.candidate));
            }
        }
        Ok(())
    }

    fn choose(&self, current_working: &[Candidate<S>], rng: &mut Rng) -> usize {
        let fitnesses = (self.scale)(current_working.iter()
            .map(|candidate| candidate.fitness)
            .collect::<Vec<f64>>());

        let running_totals = fitnesses.iter()
            .scan(0f64, |total, fitness| {
                *total += *fitness;
                Some(*total)
            })
            .collect::<Vec<f64>>();

        let total_fitness = running_totals.last().unwrap();
        let choice_point = rng.next_f64() * total_fitness;

        for (i, total) in running_totals.iter().enumerate() {
            if *total > choice_point {
                return i;
            }
        }
        unreachable!();
    }

    fn execute(&self, task: &Task, rng: &mut Rng) -> AbcResult<()> {
        let current_working = try!(self.current_working());
        let index = match *task {
            Task::Worker(n) => n,
            Task::Observer(_) => self.choose(&current_working, rng),
        };
        self.work_on(&current_working, index)
    }

    fn run(&self, tasks: TaskGenerator) -> AbcResult<()> {
        let mut guard = try!(self.tasks.lock());
        *guard = Some(tasks);
        drop(guard);

        let mut handles: Vec<ScopedJoinHandle<AbcResult<()>>> = Vec::with_capacity(self.threads);

        scope(|scope| {
            for _ in 0..self.threads {
                handles.push(scope.spawn(|| {
                    let mut rng = rand::thread_rng();
                    loop {
                        let mut guard = try!(self.tasks.lock());
                        let task = guard.as_mut().and_then(|gen| gen.next());
                        drop(guard);

                        match task {
                            Some(t) => try!(self.execute(&t, &mut rng)),
                            None => return Ok(())
                        };
                    }
                }));
            }

            // Return Ok(()) only if all threads join cleanly.
            handles.drain(..).fold(Ok(()), |result, handle| result.and(handle.join()))
        })
    }

    pub fn run_for_rounds(&self, rounds: usize) -> AbcResult<Candidate<S>> {
        let tasks = TaskGenerator::new(self.workers, self.observers).max_rounds(rounds);
        try!(self.run(tasks));
        self.get().map(|guard| guard.clone())
    }

    /// Get a guard for the current best solution found by the hive.
    ///
    /// If the hive is running, you should drop the guard returned by this
    /// function as soon as convenient, since the logic of the hive can block
    /// on the availability of the associated mutex. If you plan on performing
    /// expensive computations, you should `drop` the guard as soon as
    /// possible, or acquire and clone it within a small block, like this:
    ///
    /// ```
    /// # extern crate abc; use abc::{Solution, Candidate, Hive};
    /// # #[derive(Clone)] struct X;
    /// # impl Solution for X {
    /// #     fn make() -> X { X }
    /// #     fn evaluate_fitness(&self) -> f64 { 0_f64 }
    /// #     fn explore(field: &[Candidate<X>], n: usize) -> X { X }
    /// # }
    /// # fn main() {
    /// let hive: Hive<X> = Hive::new(5, 5, 5);
    /// let current_best = {
    ///     let guard = hive.get().unwrap();
    ///     guard.clone()
    /// };
    /// # }
    /// ```
    pub fn get(&self) -> AbcResult<MutexGuard<Candidate<S>>> {
        self.best.lock().map_err(AbcError::from)
    }

    pub fn stop(&self) -> AbcResult<()> {
        let mut tasks_guard = try!(self.tasks.lock());
        Ok(tasks_guard.as_mut().map_or((), |t| t.stop()))
    }

    pub fn get_round(&self) -> AbcResult<Option<usize>> {
        let tasks_guard = try!(self.tasks.lock());
        Ok(tasks_guard.as_ref().map(|tasks| tasks.round))
    }
}

impl<S: Solution> Drop for Hive<S> {
    fn drop(&mut self) {
        self.stop().unwrap()
    }
}<commit_msg>Clarify Hive.get example<commit_after>extern crate num_cpus;
extern crate itertools;
extern crate rand;
extern crate crossbeam;

use self::rand::Rng;
use self::itertools::Itertools;
use self::crossbeam::{scope, ScopedJoinHandle};

use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::sync::{Mutex, RwLock, MutexGuard};

use task::{TaskGenerator, Task};
use candidate::{WorkingCandidate, Candidate};
use solution::Solution;
use scaling::{ScalingFunction, proportionate};
use result::{Result as AbcResult, Error as AbcError};

pub struct Hive<S: Solution> {
    workers: usize,
    observers: usize,
    retries: usize,

    working: Vec<RwLock<WorkingCandidate<S>>>,
    best: Mutex<Candidate<S>>,
    tasks: Mutex<Option<TaskGenerator>>,

    threads: usize,
    scale: Box<ScalingFunction>,
}

impl<S: Solution + Debug> Debug for Hive<S> {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        for mutex in (&self.working).iter() {
            let working = mutex.read().unwrap();
            try!(write!(f, "..{:?}..\n", working.candidate));
        }
        let best_candidate = self.get().unwrap();
        write!(f, ">>{:?}<<", *best_candidate)
    }
}

impl<S: Solution> Hive<S> {
    pub fn new(workers: usize, observers: usize, retries: usize) -> Hive<S> {
        if workers == 0 {
            panic!("Hive must have at least one worker.");
        }

        let mut candidates = (0..workers)
            .map(|_| Candidate::new(S::make()))
            .collect::<Vec<_>>();

        let best = {
            let best_candidate = candidates.iter()
                .fold1(|best, next| if next.fitness > best.fitness { next } else { best })
                .unwrap();
            Mutex::new(best_candidate.clone())
        };

        let working = candidates.drain(..)
            .map(|c| RwLock::new(WorkingCandidate::new(c, retries)))
            .collect::<Vec<_>>();

        Hive {
            workers: workers,
            observers: observers,
            retries: retries,

            working: working,
            best: best,
            tasks: Mutex::new(None),

            threads: num_cpus::get(),
            scale: proportionate(),
        }
    }

    pub fn set_threads(mut self, threads: usize) -> Hive<S> {
        self.threads = threads;
        self
    }

    pub fn set_scaling(mut self, scale: Box<ScalingFunction>) -> Hive<S> {
        self.scale = scale;
        self
    }

    fn current_working(&self) -> AbcResult<Vec<Candidate<S>>> {
        let mut current_working = Vec::with_capacity(self.working.len());
        for candidate_mutex in &self.working {
            let read_guard = try!(candidate_mutex.read());
            current_working.push(read_guard.candidate.clone())
        }
        Ok(current_working)
    }

    fn consider_improvement(&self, candidate: &Candidate<S>) -> AbcResult<()> {
        let mut best_guard = try!(self.best.lock());
        if candidate.fitness > best_guard.fitness {
            *best_guard = candidate.clone();
        }
        Ok(())
    }

    fn work_on(&self, current_working: &[Candidate<S>], n: usize) -> AbcResult<()> {
        let variant = Candidate::new(S::explore(current_working, n));

        let mut write_guard = try!(self.working[n].write());
        if variant.fitness > write_guard.candidate.fitness {
            *write_guard = WorkingCandidate::new(variant, self.retries);
            try!(self.consider_improvement(&write_guard.candidate));
        } else {
            write_guard.deplete();
            // Scouting has been folded into the working process
            if write_guard.expired() {
                let candidate = Candidate::new(S::make());
                *write_guard = WorkingCandidate::new(candidate, self.retries);
                try!(self.consider_improvement(&write_guard.candidate));
            }
        }
        Ok(())
    }

    fn choose(&self, current_working: &[Candidate<S>], rng: &mut Rng) -> usize {
        let fitnesses = (self.scale)(current_working.iter()
            .map(|candidate| candidate.fitness)
            .collect::<Vec<f64>>());

        let running_totals = fitnesses.iter()
            .scan(0f64, |total, fitness| {
                *total += *fitness;
                Some(*total)
            })
            .collect::<Vec<f64>>();

        let total_fitness = running_totals.last().unwrap();
        let choice_point = rng.next_f64() * total_fitness;

        for (i, total) in running_totals.iter().enumerate() {
            if *total > choice_point {
                return i;
            }
        }
        unreachable!();
    }

    fn execute(&self, task: &Task, rng: &mut Rng) -> AbcResult<()> {
        let current_working = try!(self.current_working());
        let index = match *task {
            Task::Worker(n) => n,
            Task::Observer(_) => self.choose(&current_working, rng),
        };
        self.work_on(&current_working, index)
    }

    fn run(&self, tasks: TaskGenerator) -> AbcResult<()> {
        let mut guard = try!(self.tasks.lock());
        *guard = Some(tasks);
        drop(guard);

        let mut handles: Vec<ScopedJoinHandle<AbcResult<()>>> = Vec::with_capacity(self.threads);

        scope(|scope| {
            for _ in 0..self.threads {
                handles.push(scope.spawn(|| {
                    let mut rng = rand::thread_rng();
                    loop {
                        let mut guard = try!(self.tasks.lock());
                        let task = guard.as_mut().and_then(|gen| gen.next());
                        drop(guard);

                        match task {
                            Some(t) => try!(self.execute(&t, &mut rng)),
                            None => return Ok(())
                        };
                    }
                }));
            }

            // Return Ok(()) only if all threads join cleanly.
            handles.drain(..).fold(Ok(()), |result, handle| result.and(handle.join()))
        })
    }

    pub fn run_for_rounds(&self, rounds: usize) -> AbcResult<Candidate<S>> {
        let tasks = TaskGenerator::new(self.workers, self.observers).max_rounds(rounds);
        try!(self.run(tasks));
        self.get().map(|guard| guard.clone())
    }

    /// Get a guard for the current best solution found by the hive.
    ///
    /// If the hive is running, you should drop the guard returned by this
    /// function as soon as convenient, since the logic of the hive can block
    /// on the availability of the associated mutex. If you plan on performing
    /// expensive computations, you should `drop` the guard as soon as
    /// possible, or acquire and clone it within a small block, like this:
    ///
    /// ```
    /// # extern crate abc; use abc::{Solution, Candidate, Hive};
    /// # #[derive(Clone)] struct X;
    /// # impl Solution for X {
    /// #     fn make() -> X { X }
    /// #     fn evaluate_fitness(&self) -> f64 { 0_f64 }
    /// #     fn explore(field: &[Candidate<X>], n: usize) -> X { X }
    /// # }
    /// # fn do_stuff_with(x: Candidate<X>) {}
    /// # fn main() {
    /// let hive: Hive<X> = Hive::new(5, 5, 5);
    /// let current_best = {
    ///     let guard = hive.get().unwrap();
    ///     guard.clone()
    /// };
    ///
    /// do_stuff_with(current_best);
    /// # }
    /// ```
    pub fn get(&self) -> AbcResult<MutexGuard<Candidate<S>>> {
        self.best.lock().map_err(AbcError::from)
    }

    pub fn stop(&self) -> AbcResult<()> {
        let mut tasks_guard = try!(self.tasks.lock());
        Ok(tasks_guard.as_mut().map_or((), |t| t.stop()))
    }

    pub fn get_round(&self) -> AbcResult<Option<usize>> {
        let tasks_guard = try!(self.tasks.lock());
        Ok(tasks_guard.as_ref().map(|tasks| tasks.round))
    }
}

impl<S: Solution> Drop for Hive<S> {
    fn drop(&mut self) {
        self.stop().unwrap()
    }
}<|endoftext|>


<commit_before>#[cfg(test)]
extern crate http_stub;
extern crate hyper;
extern crate log;
extern crate serde;
extern crate serde_json;

use hyper::Client;
use hyper::Error;
use hyper::client::response::Response;
use hyper::header::{Headers, ContentType};
use hyper::mime::{Mime, TopLevel, SubLevel};
use serde::Serialize;
use std::env::var;

const BASE_URL_KEY: &'static str = "K2_BASE_URL";
const BASE_URL: &'static str = "http://localhost:8080/k2/ctapi/";

#[derive(Serialize)]
struct Empty();

pub fn simple_post(path: String) -> Result<Response, Error> {
    post(path, &Empty {})
}

pub fn post<T>(path: String, payload: &T) -> Result<Response, Error>
    where T: Serialize
{
    let url = get_request_url(path);
    debug!("HTTP POST URL: {}", url);

    let body = serde_json::to_string(&payload).unwrap();
    debug!("HTTP POST body: {:?}", body);

    let mut headers = Headers::new();
    headers.set(ContentType(Mime(TopLevel::Application, SubLevel::Json, vec![])));

    let client = Client::new();
    let mut builder = client.post(&url);
    builder = builder.headers(headers);
    builder = builder.body(&body[..]);
    builder.send()
}

fn get_request_url(path: String) -> String {
    let mut url = var(BASE_URL_KEY).unwrap_or(BASE_URL.to_string());
    if !url.trim().ends_with("/") {
        url.push_str("/");
    }

    url.push_str(&path);

    url
}

#[cfg(test)]
mod tests {
    use self::http_stub as hs;
    use self::http_stub::HttpStub;
    use super::*;
    use std::env;
    use std::io::Read;

    #[test]
    fn simple_post_is_returning_error() {
        env::remove_var(BASE_URL_KEY);

        let res = simple_post(String::from("hello"));
        assert!(res.is_err());
    }

    #[test]
    fn simple_post_is_sending_empty_body() {
        let url = HttpStub::run(|mut stub| {
                                    stub.got_body("");
                                    stub.got_path("/hello");
                                    stub.got_method(hs::Method::Post);

                                    stub.send_status(hs::StatusCode::Ok);
                                    stub.send_body("world");
                                });

        env::set_var(BASE_URL_KEY, url);

        let mut res = simple_post(String::from("hello")).unwrap();
        assert_eq!(res.status, hyper::Ok);

        let mut body = String::new();
        res.read_to_string(&mut body).unwrap();
        assert_eq!(body, "world");
    }

    #[test]
    fn post_is_returning_error() {
        env::remove_var(BASE_URL_KEY);

        #[derive(Serialize)]
        struct Data {
            name: String,
        };

        let data = Data { name: String::from("hello") };

        let res = post(String::from("hello"), &data);
        assert!(res.is_err());
    }

    #[test]
    fn post_is_sending_payload_as_json_in_body() {
        let url = HttpStub::run(|mut stub| {
            stub.got_path("/hello");
            stub.got_method(hs::Method::Post);
            stub.got_header("content-type", "application/json");
            stub.got_body(r#"\{"name":"hello"\}"#);

            stub.send_status(hs::StatusCode::Ok);
            stub.send_body("world");
        });

        env::set_var(BASE_URL_KEY, url);

        #[derive(Serialize)]
        struct Data {
            name: String,
        };

        let data = Data { name: String::from("hello") };

        let mut res = post(String::from("hello"), &data).unwrap();
        assert_eq!(res.status, hyper::Ok);

        let mut body = String::new();
        res.read_to_string(&mut body).unwrap();
        assert_eq!(body, "world");
    }

    #[test]
    fn get_request_url_concats_default_value_of_base_url_key_and_given_string() {
        env::remove_var(BASE_URL_KEY);

        let url = get_request_url(String::from("a"));
        assert_eq!(url, BASE_URL.to_owned() + "a");
    }

    #[test]
    fn get_request_url_concats_value_of_base_url_key_and_given_string() {
        env::set_var(BASE_URL_KEY, "abc");

        let url = get_request_url(String::from("a"));
        assert_eq!(url, "abc/a");
    }
}
<commit_msg>irgnore tests using a http server because it takes to much time to start the server and there is no alternative yet<commit_after>#[cfg(test)]
extern crate http_stub;
extern crate hyper;
extern crate log;
extern crate serde;
extern crate serde_json;

use hyper::Client;
use hyper::Error;
use hyper::client::response::Response;
use hyper::header::{Headers, ContentType};
use hyper::mime::{Mime, TopLevel, SubLevel};
use serde::Serialize;
use std::env::var;

const BASE_URL_KEY: &'static str = "K2_BASE_URL";
const BASE_URL: &'static str = "http://localhost:8080/k2/ctapi/";

#[derive(Serialize)]
struct Empty();

pub fn simple_post(path: String) -> Result<Response, Error> {
    post(path, &Empty {})
}

pub fn post<T>(path: String, payload: &T) -> Result<Response, Error>
    where T: Serialize
{
    let url = get_request_url(path);
    debug!("HTTP POST URL: {}", url);

    let body = serde_json::to_string(&payload).unwrap();
    debug!("HTTP POST body: {:?}", body);

    let mut headers = Headers::new();
    headers.set(ContentType(Mime(TopLevel::Application, SubLevel::Json, vec![])));

    let client = Client::new();
    let mut builder = client.post(&url);
    builder = builder.headers(headers);
    builder = builder.body(&body[..]);
    builder.send()
}

fn get_request_url(path: String) -> String {
    let mut url = var(BASE_URL_KEY).unwrap_or(BASE_URL.to_string());
    if !url.trim().ends_with("/") {
        url.push_str("/");
    }

    url.push_str(&path);

    url
}

#[cfg(test)]
mod tests {
    use self::http_stub as hs;
    use self::http_stub::HttpStub;
    use super::*;
    use std::env;
    use std::io::Read;

    #[test]
    fn simple_post_is_returning_error() {
        env::remove_var(BASE_URL_KEY);

        let res = simple_post(String::from("hello"));
        assert!(res.is_err());
    }

    #[test]
    #[ignore]
    fn simple_post_is_sending_empty_body() {
        let url = HttpStub::run(|mut stub| {
                                    stub.got_body("");
                                    stub.got_path("/hello");
                                    stub.got_method(hs::Method::Post);

                                    stub.send_status(hs::StatusCode::Ok);
                                    stub.send_body("world");
                                });

        env::set_var(BASE_URL_KEY, url);

        let mut res = simple_post(String::from("hello")).unwrap();
        assert_eq!(res.status, hyper::Ok);

        let mut body = String::new();
        res.read_to_string(&mut body).unwrap();
        assert_eq!(body, "world");
    }

    #[test]
    fn post_is_returning_error() {
        env::remove_var(BASE_URL_KEY);

        #[derive(Serialize)]
        struct Data {
            name: String,
        };

        let data = Data { name: String::from("hello") };

        let res = post(String::from("hello"), &data);
        assert!(res.is_err());
    }

    #[test]
    #[ignore]
    fn post_is_sending_payload_as_json_in_body() {
        let url = HttpStub::run(|mut stub| {
            stub.got_path("/hello");
            stub.got_method(hs::Method::Post);
            stub.got_header("content-type", "application/json");
            stub.got_body(r#"\{"name":"hello"\}"#);

            stub.send_status(hs::StatusCode::Ok);
            stub.send_body("world");
        });

        env::set_var(BASE_URL_KEY, url);

        #[derive(Serialize)]
        struct Data {
            name: String,
        };

        let data = Data { name: String::from("hello") };

        let mut res = post(String::from("hello"), &data).unwrap();
        assert_eq!(res.status, hyper::Ok);

        let mut body = String::new();
        res.read_to_string(&mut body).unwrap();
        assert_eq!(body, "world");
    }

    #[test]
    fn get_request_url_concats_default_value_of_base_url_key_and_given_string() {
        env::remove_var(BASE_URL_KEY);

        let url = get_request_url(String::from("a"));
        assert_eq!(url, BASE_URL.to_owned() + "a");
    }

    #[test]
    fn get_request_url_concats_value_of_base_url_key_and_given_string() {
        env::set_var(BASE_URL_KEY, "abc");

        let url = get_request_url(String::from("a"));
        assert_eq!(url, "abc/a");
    }
}
<|endoftext|>


<commit_before>use std::{
    collections::HashMap,
    env,
    ffi::OsString,
    fs,
    path::{Path, PathBuf},
};

pub use eyre;
use eyre::{bail, Result};
pub use fs_utils;
pub use libcnb;
use libcnb::{
    data::{
        build_plan::{Provide, Require},
        buildpack::BuildpackId,
    },
    libcnb_runtime_build, libcnb_runtime_detect, BuildArgs, DetectArgs, Platform,
};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
pub use tokio;
pub use toml;
pub use tracing;

/// The stack id
///
/// Currently a constants (cause there's only one :) but in the
/// future there may be more e.g. `stencila.stacks.jammy`
const CNB_STACK_ID: &str = "stencila.stacks.focal";

/// Test whether the current CNB platform is Stencila
pub fn platform_dir_is_stencila(platform_dir: &Path) -> bool {
    platform_dir.join("env").join("STENCILA_VERSION").exists()
}

/// Test whether the current CNB platform is Stencila
pub fn platform_is_stencila(platform: &impl Platform) -> bool {
    platform.env().contains_key("STENCILA_VERSION")
}

/// String used by buildpacks to indicate that a tool version
/// was determined from the (highest) version found on the system
pub const SYSTEM_INSTALLED: &str = "<system>";

/// A local trait for buildpacks that extends `libcnb::Buildpack`
///
/// Why? To provide some additional introspection and the ability
/// to compile several buildpacks into a single binary and call
/// their `detect` and `build` methods.
pub trait BuildpackTrait: libcnb::Buildpack {
    /// Get the content of the `buildpack.toml` file
    fn toml() -> &'static str;

    /// Get the buildpack's spec (a.k.a. descriptor) from the `buildpack.toml`
    fn spec() -> Result<BuildpackToml> {
        let toml = Self::toml();
        let spec = toml::from_str::<BuildpackToml>(toml)?;
        Ok(spec)
    }

    /// Get the buildpack's `id` from the `buildpack.toml`
    fn id(&self) -> Result<BuildpackId> {
        let spec = Self::spec()?;
        Ok(spec.buildpack.id)
    }

    /// Ensure the buildpack's runtime directory exists and return its path
    ///
    /// Both the `detect` and `build` methods require that `buildpack.toml`
    /// be available on disk.
    fn ensure_dir(&self) -> Result<String> {
        let id = self.id()?;
        let dir = buildpacks_dir()?.join(id.to_string());
        fs::create_dir_all(&dir)?;

        // Write the `buildpack.toml` to the directory
        let toml_path = dir.join("buildpack.toml");
        fs::write(&toml_path, Self::toml())?;

        Ok(dir.display().to_string())
    }

    /// Test whether any of the files exists in the working directory
    ///
    /// A convenience method for use in `detect`.
    fn any_exist(paths: &[&str]) -> bool {
        for path in paths {
            if PathBuf::from(path).exists() {
                return true;
            }
        }
        false
    }

    /// Test whether a file contains a string
    ///
    /// A convenience method for use in `detect`.
    fn file_contains(file: &str, string: &str) -> bool {
        fs::read_to_string(file)
            .map(|content| content.contains(string))
            .unwrap_or(false)
    }

    /// Prepend a path to the `PATH` env var
    fn prepend_path(first: &Path) -> Result<OsString> {
        if let Some(path) = env::var_os("PATH") {
            let mut paths = vec![first.into()];
            paths.append(&mut env::split_paths(&path).collect::<Vec<_>>());
            Ok(env::join_paths(paths)?)
        } else {
            Ok(first.into())
        }
    }

    /// Parses a `.tool-versions` file (if any)
    ///
    /// A convenience method for use in `detect`. If there is no such file, then
    /// the returned map will be empty.
    ///
    /// Note that each line of `.tool-versions` can have multiple versions, but
    /// that this takes the first.
    fn tool_versions() -> HashMap<String, String> {
        match fs::read_to_string(".tool-versions") {
            Ok(content) => content
                .lines()
                .filter_map(|line| {
                    let mut parts = line.split_whitespace();
                    let name = match parts.next() {
                        Some(name) => name.to_string(),
                        None => return None,
                    };
                    let version = parts.next().unwrap_or("*").to_string();
                    Some((name, version))
                })
                .collect(),
            Err(..) => HashMap::new(),
        }
    }

    /// Generate `Require` (with metadata) and `Provide` objects
    ///
    /// A convenience method for use in `build` method.
    fn require_and_provide(
        name: impl AsRef<str>,
        source: impl AsRef<str>,
        desc: impl AsRef<str>,
    ) -> (Require, Provide) {
        let mut require = Require::new(name.as_ref());
        if !source.as_ref().is_empty() {
            require.metadata.insert(
                "source".to_string(),
                toml::Value::String(source.as_ref().to_string()),
            );
        }
        if !desc.as_ref().is_empty() {
            require.metadata.insert(
                "description".to_string(),
                toml::Value::String(desc.as_ref().to_string()),
            );
        }

        let provide = Provide::new(name.as_ref());

        (require, provide)
    }

    /// Split the `name` of a buildpack plan into two parts
    ///
    /// A convenience method for used in `build`.
    fn split_entry_name(name: &str) -> (String, Vec<String>) {
        let mut parts = name.split(' ');
        (
            parts.next().unwrap_or_default().to_string(),
            parts.map(String::from).collect::<Vec<String>>(),
        )
    }

    /// Run the buildpack's `detect` method
    fn detect_with(&self, platform_dir: &Path, build_plan: &Path) -> Result<i32>
    where
        Self: Sized,
    {
        env::set_var("CNB_STACK_ID", CNB_STACK_ID);

        let buildpack_dir = self.ensure_dir()?;
        env::set_var("CNB_BUILDPACK_DIR", buildpack_dir);

        match libcnb_runtime_detect(
            self,
            DetectArgs {
                platform_dir_path: PathBuf::from(platform_dir),
                build_plan_path: PathBuf::from(build_plan),
            },
        ) {
            Ok(code) => Ok(code),
            Err(error) => bail!(
                "While running `detect` for buildpack `{}`: {}",
                self.id()?,
                error
            ),
        }
    }

    /// Run the buildpack's `build` method
    fn build_with(
        &self,
        layers_dir: &Path,
        platform_dir: &Path,
        buildpack_plan: &Path,
    ) -> Result<i32>
    where
        Self: Sized,
    {
        env::set_var("CNB_STACK_ID", CNB_STACK_ID);

        let buildpack_dir = self.ensure_dir()?;
        env::set_var("CNB_BUILDPACK_DIR", buildpack_dir);

        match libcnb_runtime_build(
            self,
            BuildArgs {
                layers_dir_path: PathBuf::from(layers_dir),
                platform_dir_path: PathBuf::from(platform_dir),
                buildpack_plan_path: PathBuf::from(buildpack_plan),
            },
        ) {
            Ok(code) => Ok(code),
            Err(error) => bail!(
                "While running `build` for buildpack `{}`: {}",
                self.id()?,
                error
            ),
        }
    }
}

/// Get the path of the directory where buildpacks stored on the users machine
pub fn buildpacks_dir() -> Result<PathBuf> {
    let user_data_dir = dirs::data_dir()
        .unwrap_or_else(|| env::current_dir().expect("Should always be able to get current dir"));
    let dir = match env::consts::OS {
        "macos" | "windows" => user_data_dir.join("Stencila").join("Buildpacks"),
        _ => user_data_dir.join("stencila").join("buildpacks"),
    };
    Ok(dir)
}

/// Generate a locally unique tag for an image based on a directory path
///
/// Build a container image with a tag `<name>-<hash>` where `<name>` is the
/// name of the directory containing the `Dockerfile` and `<hash>` is the 12-character
/// truncated SHA256 hash of its path (to avoid clashes between directories with the same name).
pub fn tag_for_path(path: &Path) -> String {
    let name = path
        .file_name()
        .map(|name| name.to_string_lossy().to_string())
        .unwrap_or_else(|| "unnamed".to_string());

    let mut hash = hash_utils::str_sha256_hex(&path.display().to_string());
    hash.truncate(12);

    [&name, "-", &hash].concat()
}

// The `libcnb` crate provides similar structs to those below, often with stronger typing,
// but those do not implement `Serialize` or `Clone` and so for our purposes
// it was easier to reimplement them here. This decision should be revisited at some time.

/// A Buildpack Descriptor (`buildpack.toml`)
///
/// Used primarily to read in and display the spec for a buildpack for
/// use in commands such a `stencila buildpacks show <label>`.
///
/// See https://github.com/buildpacks/spec/blob/main/buildpack.md#buildpacktoml-toml
#[skip_serializing_none]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BuildpackToml {
    pub api: String,
    pub buildpack: Buildpack,
    pub stacks: Option<Vec<BuildpackStack>>,
    pub order: Option<Vec<BuildpackGroup>>,
    pub metadata: Option<serde_json::Value>,
}

#[skip_serializing_none]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Buildpack {
    pub id: BuildpackId,
    pub version: String,
    pub name: String,
    pub clear_env: Option<bool>,
    pub homepage: Option<String>,
    pub description: Option<String>,
    pub keywords: Option<Vec<String>>,
    #[serde(rename = "sbom-formats")]
    pub sbom_formats: Option<Vec<String>>,
    pub licenses: Option<Vec<BuildpackLicense>>,
}

#[skip_serializing_none]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BuildpackLicense {
    pub r#type: Option<String>,
    pub uri: Option<String>,
}

#[skip_serializing_none]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BuildpackStack {
    pub id: String,
    pub mixins: Option<Vec<String>>,
}

#[derive(Debug, Default, Clone, Deserialize, Serialize)]
#[serde(default)]
pub struct BuildpackGroup {
    pub id: String,
    pub version: String,
    pub optional: bool,
}

/// A Build Plan
///
/// Generated by the `detect` method of a buildpack (if it matches against a folder).
///
/// See https://github.com/buildpacks/spec/blob/main/buildpack.md#build-plan-toml
#[skip_serializing_none]
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct BuildPlan {
    pub provides: Option<Vec<BuildPlanProvides>>,
    pub requires: Option<Vec<BuildPlanRequires>>,
    pub or: Option<Vec<BuildPlanOr>>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct BuildPlanProvides {
    pub name: String,
}

#[skip_serializing_none]
#[derive(Debug, Deserialize, Serialize)]
pub struct BuildPlanRequires {
    pub name: String,
    pub metadata: Option<toml::Value>,
}

#[skip_serializing_none]
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct BuildPlanOr {
    pub provides: Option<BuildPlanProvides>,
    pub requires: Option<BuildPlanRequires>,
}

/// A Buildpack Plan
///
/// Passed to the `build` method of each buildpack involved in the build.
///
/// See https://github.com/buildpacks/spec/blob/main/buildpack.md#buildpack-plan-toml
#[derive(Debug, Default, Deserialize, Serialize)]
#[serde(default)]
pub struct BuildpackPlan {
    pub entries: Vec<BuildPlanRequires>,
}

/// Runtime entry point function
///
/// Just sets up logging and calls `libcnb::libcnb_runtime`
/// to do the actual work.
pub fn runtime<B: libcnb::Buildpack>(buildpack: &B) {
    tracing_subscriber::fmt().init();
    libcnb::libcnb_runtime(buildpack);
}

/// Generate a `main` function for a buildpack
#[macro_export]
macro_rules! buildpack_main {
    ($buildpack:ident) => {
        #[tokio::main]
        async fn main() {
            buildpack::runtime(&$buildpack);
        }
    };
}
<commit_msg>feat(Buildpack): Add `is_local_build` and `is_cnb_build` functions<commit_after>use std::{
    collections::HashMap,
    env,
    ffi::OsString,
    fs,
    path::{Path, PathBuf},
};

pub use eyre;
use eyre::{bail, Result};
pub use fs_utils;
pub use hash_utils;
pub use libcnb;
use libcnb::{
    build::BuildContext,
    data::{
        build_plan::{Provide, Require},
        buildpack::BuildpackId,
    },
    libcnb_runtime_build, libcnb_runtime_detect, BuildArgs, DetectArgs, Platform,
};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
pub use tokio;
pub use toml;
pub use tracing;

/// The stack id
///
/// Currently a constants (cause there's only one :) but in the
/// future there may be more e.g. `stencila.stacks.jammy`
const CNB_STACK_ID: &str = "stencila.stacks.focal";

/// Test whether the current CNB platform is Stencila
pub fn platform_dir_is_stencila(platform_dir: &Path) -> bool {
    platform_dir.join("env").join("STENCILA_VERSION").exists()
}

/// Test whether the current CNB platform is Stencila
pub fn platform_is_stencila(platform: &impl Platform) -> bool {
    platform.env().contains_key("STENCILA_VERSION")
}

/// Is this a local build (ie. using Stencila, not Pack as platform)
pub fn is_local_build<B: libcnb::Buildpack>(context: &BuildContext<B>) -> bool {
    platform_is_stencila(&context.platform)
}

/// Is this a CNB build (e.g. using Pack as the platform)
pub fn is_cnb_build<B: libcnb::Buildpack>(context: &BuildContext<B>) -> bool {
    !is_local_build(context)
}

/// String used by buildpacks to indicate that a tool version
/// was determined from the (highest) version found on the system
pub const SYSTEM_INSTALLED: &str = "<system>";

/// A local trait for buildpacks that extends `libcnb::Buildpack`
///
/// Why? To provide some additional introspection and the ability
/// to compile several buildpacks into a single binary and call
/// their `detect` and `build` methods.
pub trait BuildpackTrait: libcnb::Buildpack {
    /// Get the content of the `buildpack.toml` file
    fn toml() -> &'static str;

    /// Get the buildpack's spec (a.k.a. descriptor) from the `buildpack.toml`
    fn spec() -> Result<BuildpackToml> {
        let toml = Self::toml();
        let spec = toml::from_str::<BuildpackToml>(toml)?;
        Ok(spec)
    }

    /// Get the buildpack's `id` from the `buildpack.toml`
    fn id(&self) -> Result<BuildpackId> {
        let spec = Self::spec()?;
        Ok(spec.buildpack.id)
    }

    /// Ensure the buildpack's runtime directory exists and return its path
    ///
    /// Both the `detect` and `build` methods require that `buildpack.toml`
    /// be available on disk.
    fn ensure_dir(&self) -> Result<String> {
        let id = self.id()?;
        let dir = buildpacks_dir()?.join(id.to_string());
        fs::create_dir_all(&dir)?;

        // Write the `buildpack.toml` to the directory
        let toml_path = dir.join("buildpack.toml");
        fs::write(&toml_path, Self::toml())?;

        Ok(dir.display().to_string())
    }

    /// Test whether any of the files exists in the working directory
    ///
    /// A convenience method for use in `detect`.
    fn any_exist(paths: &[&str]) -> bool {
        for path in paths {
            if PathBuf::from(path).exists() {
                return true;
            }
        }
        false
    }

    /// Test whether a file contains a string
    ///
    /// A convenience method for use in `detect`.
    fn file_contains(file: &str, string: &str) -> bool {
        fs::read_to_string(file)
            .map(|content| content.contains(string))
            .unwrap_or(false)
    }

    /// Prepend a path to the `PATH` env var
    fn prepend_path(first: &Path) -> Result<OsString> {
        if let Some(path) = env::var_os("PATH") {
            let mut paths = vec![first.into()];
            paths.append(&mut env::split_paths(&path).collect::<Vec<_>>());
            Ok(env::join_paths(paths)?)
        } else {
            Ok(first.into())
        }
    }

    /// Parses a `.tool-versions` file (if any)
    ///
    /// A convenience method for use in `detect`. If there is no such file, then
    /// the returned map will be empty.
    ///
    /// Note that each line of `.tool-versions` can have multiple versions, but
    /// that this takes the first.
    fn tool_versions() -> HashMap<String, String> {
        match fs::read_to_string(".tool-versions") {
            Ok(content) => content
                .lines()
                .filter_map(|line| {
                    let mut parts = line.split_whitespace();
                    let name = match parts.next() {
                        Some(name) => name.to_string(),
                        None => return None,
                    };
                    let version = parts.next().unwrap_or("*").to_string();
                    Some((name, version))
                })
                .collect(),
            Err(..) => HashMap::new(),
        }
    }

    /// Generate `Require` (with metadata) and `Provide` objects
    ///
    /// A convenience method for use in `build` method.
    fn require_and_provide(
        name: impl AsRef<str>,
        source: impl AsRef<str>,
        desc: impl AsRef<str>,
    ) -> (Require, Provide) {
        let mut require = Require::new(name.as_ref());
        if !source.as_ref().is_empty() {
            require.metadata.insert(
                "source".to_string(),
                toml::Value::String(source.as_ref().to_string()),
            );
        }
        if !desc.as_ref().is_empty() {
            require.metadata.insert(
                "description".to_string(),
                toml::Value::String(desc.as_ref().to_string()),
            );
        }

        let provide = Provide::new(name.as_ref());

        (require, provide)
    }

    /// Split the `name` of a buildpack plan into two parts
    ///
    /// A convenience method for used in `build`.
    fn split_entry_name(name: &str) -> (String, Vec<String>) {
        let mut parts = name.split(' ');
        (
            parts.next().unwrap_or_default().to_string(),
            parts.map(String::from).collect::<Vec<String>>(),
        )
    }

    /// Run the buildpack's `detect` method
    fn detect_with(&self, platform_dir: &Path, build_plan: &Path) -> Result<i32>
    where
        Self: Sized,
    {
        env::set_var("CNB_STACK_ID", CNB_STACK_ID);

        let buildpack_dir = self.ensure_dir()?;
        env::set_var("CNB_BUILDPACK_DIR", buildpack_dir);

        match libcnb_runtime_detect(
            self,
            DetectArgs {
                platform_dir_path: PathBuf::from(platform_dir),
                build_plan_path: PathBuf::from(build_plan),
            },
        ) {
            Ok(code) => Ok(code),
            Err(error) => bail!(
                "While running `detect` for buildpack `{}`: {}",
                self.id()?,
                error
            ),
        }
    }

    /// Run the buildpack's `build` method
    fn build_with(
        &self,
        layers_dir: &Path,
        platform_dir: &Path,
        buildpack_plan: &Path,
    ) -> Result<i32>
    where
        Self: Sized,
    {
        env::set_var("CNB_STACK_ID", CNB_STACK_ID);

        let buildpack_dir = self.ensure_dir()?;
        env::set_var("CNB_BUILDPACK_DIR", buildpack_dir);

        match libcnb_runtime_build(
            self,
            BuildArgs {
                layers_dir_path: PathBuf::from(layers_dir),
                platform_dir_path: PathBuf::from(platform_dir),
                buildpack_plan_path: PathBuf::from(buildpack_plan),
            },
        ) {
            Ok(code) => Ok(code),
            Err(error) => bail!(
                "While running `build` for buildpack `{}`: {}",
                self.id()?,
                error
            ),
        }
    }
}

/// Get the path of the directory where buildpacks stored on the users machine
pub fn buildpacks_dir() -> Result<PathBuf> {
    let user_data_dir = dirs::data_dir()
        .unwrap_or_else(|| env::current_dir().expect("Should always be able to get current dir"));
    let dir = match env::consts::OS {
        "macos" | "windows" => user_data_dir.join("Stencila").join("Buildpacks"),
        _ => user_data_dir.join("stencila").join("buildpacks"),
    };
    Ok(dir)
}

/// Generate a locally unique tag for an image based on a directory path
///
/// Build a container image with a tag `<name>-<hash>` where `<name>` is the
/// name of the directory containing the `Dockerfile` and `<hash>` is the 12-character
/// truncated SHA256 hash of its path (to avoid clashes between directories with the same name).
pub fn tag_for_path(path: &Path) -> String {
    let name = path
        .file_name()
        .map(|name| name.to_string_lossy().to_string())
        .unwrap_or_else(|| "unnamed".to_string());

    let mut hash = hash_utils::str_sha256_hex(&path.display().to_string());
    hash.truncate(12);

    [&name, "-", &hash].concat()
}

// The `libcnb` crate provides similar structs to those below, often with stronger typing,
// but those do not implement `Serialize` or `Clone` and so for our purposes
// it was easier to reimplement them here. This decision should be revisited at some time.

/// A Buildpack Descriptor (`buildpack.toml`)
///
/// Used primarily to read in and display the spec for a buildpack for
/// use in commands such a `stencila buildpacks show <label>`.
///
/// See https://github.com/buildpacks/spec/blob/main/buildpack.md#buildpacktoml-toml
#[skip_serializing_none]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BuildpackToml {
    pub api: String,
    pub buildpack: Buildpack,
    pub stacks: Option<Vec<BuildpackStack>>,
    pub order: Option<Vec<BuildpackGroup>>,
    pub metadata: Option<serde_json::Value>,
}

#[skip_serializing_none]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Buildpack {
    pub id: BuildpackId,
    pub version: String,
    pub name: String,
    pub clear_env: Option<bool>,
    pub homepage: Option<String>,
    pub description: Option<String>,
    pub keywords: Option<Vec<String>>,
    #[serde(rename = "sbom-formats")]
    pub sbom_formats: Option<Vec<String>>,
    pub licenses: Option<Vec<BuildpackLicense>>,
}

#[skip_serializing_none]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BuildpackLicense {
    pub r#type: Option<String>,
    pub uri: Option<String>,
}

#[skip_serializing_none]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BuildpackStack {
    pub id: String,
    pub mixins: Option<Vec<String>>,
}

#[derive(Debug, Default, Clone, Deserialize, Serialize)]
#[serde(default)]
pub struct BuildpackGroup {
    pub id: String,
    pub version: String,
    pub optional: bool,
}

/// A Build Plan
///
/// Generated by the `detect` method of a buildpack (if it matches against a folder).
///
/// See https://github.com/buildpacks/spec/blob/main/buildpack.md#build-plan-toml
#[skip_serializing_none]
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct BuildPlan {
    pub provides: Option<Vec<BuildPlanProvides>>,
    pub requires: Option<Vec<BuildPlanRequires>>,
    pub or: Option<Vec<BuildPlanOr>>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct BuildPlanProvides {
    pub name: String,
}

#[skip_serializing_none]
#[derive(Debug, Deserialize, Serialize)]
pub struct BuildPlanRequires {
    pub name: String,
    pub metadata: Option<toml::Value>,
}

#[skip_serializing_none]
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct BuildPlanOr {
    pub provides: Option<BuildPlanProvides>,
    pub requires: Option<BuildPlanRequires>,
}

/// A Buildpack Plan
///
/// Passed to the `build` method of each buildpack involved in the build.
///
/// See https://github.com/buildpacks/spec/blob/main/buildpack.md#buildpack-plan-toml
#[derive(Debug, Default, Deserialize, Serialize)]
#[serde(default)]
pub struct BuildpackPlan {
    pub entries: Vec<BuildPlanRequires>,
}

/// Runtime entry point function
///
/// Just sets up logging and calls `libcnb::libcnb_runtime`
/// to do the actual work.
pub fn runtime<B: libcnb::Buildpack>(buildpack: &B) {
    tracing_subscriber::fmt().init();
    libcnb::libcnb_runtime(buildpack);
}

/// Generate a `main` function for a buildpack
#[macro_export]
macro_rules! buildpack_main {
    ($buildpack:ident) => {
        #[tokio::main]
        async fn main() {
            buildpack::runtime(&$buildpack);
        }
    };
}
<|endoftext|>