<commit_before>use std::{
    sync::{
        atomic::{self, AtomicU16, AtomicU32, AtomicU64},
        Arc, Mutex,
    },
    time::Instant,
};

use anyhow::{Context as _, Error};
use gba::{Button, ButtonSet};
use sdl2::{
    event::Event,
    keyboard::Keycode,
    pixels::{Color, PixelFormatEnum},
};

const LOCK_FPS: bool = false;

fn main() {
    if let Err(err) = run() {
        eprintln!("{:#}", err);
        std::process::exit(1);
    }
    std::process::exit(0)
}

fn run() -> anyhow::Result<()> {
    pretty_env_logger::formatted_builder()
        .parse_filters(&std::env::var("RUST_LOG").unwrap_or_else(|_| String::from("info")))
        .try_init()
        .context("failed to initiaize logger")?;

    let config = pyrite::config::from_toml_path("pyrite.toml")
        .map_err(|err| {
            let err = anyhow::Error::from(err);
            log::error!("{:#}", err);
        })
        .unwrap_or_default();

    let frame_delay_target = std::time::Duration::from_secs_f64(1.0 / 60.0);

    let sdl_context = sdl2::init()
        .map_err(Error::msg)
        .context("failed to initialize SDL")?;
    let video_subsystem = sdl_context
        .video()
        .map_err(Error::msg)
        .context("failed to initialize SDL video")?;
    let window = video_subsystem
        .window("Pyrite", 480, 320)
        .position_centered()
        .resizable()
        .build()
        .context("failed to create SDL window")?;
    let mut event_pump = sdl_context
        .event_pump()
        .map_err(Error::msg)
        .context("failed to initialize SDL event pump")?;
    let mut canvas_builder = window.into_canvas().accelerated();

    if config.graphics.vsync.unwrap_or(true) {
        canvas_builder = canvas_builder.present_vsync();
    }

    let mut canvas = canvas_builder
        .build()
        .context("failed to initialize SDL canvas")?;
    let texture_creator = canvas.texture_creator();

    let mut gba_frame_texture = texture_creator
        .create_texture_streaming(PixelFormatEnum::BGR555, 240, 160)
        .context("failed to create GBA frame texture")?;
    gba_frame_texture
        .with_lock(None, |buffer: &mut [u8], _: usize| buffer.fill(0))
        .map_err(Error::msg)
        .context("failed to update GBA frame texture")?;
    let screen_buffer = Arc::new(Mutex::new([0u16; gba::SCREEN_PIXEL_COUNT]));

    let gba = pyrite::GbaHandle::new();

    let rom = get_rom_from_args().context("error occurred retrieving ROM from args")?;
    let bios = config.gba.bios_path.and_then(|path| {
        match std::fs::read(&path)
            .with_context(|| format!("failed to read path {}", path.display()))
        {
            Ok(data) => Some(data),
            Err(err) => {
                log::error!("error occurred while loading BIOS: {:?}", err);
                None
            }
        }
    });
    let boot_from_bios = config.gba.boot_from_bios.unwrap_or(true);

    gba.after_frame_wait(move |gba, _| {
        gba.set_gamepak(rom);
        gba.set_bios(bios);
        gba.reset(boot_from_bios);
    });

    let mut buttons = ButtonSet::default();
    let buttons_u16 = Arc::new(AtomicU16::new(buttons.into()));
    let buttons_u16_gba = buttons_u16.clone();

    let frame_duration_accumulate = Arc::new(AtomicU64::new(0));
    let frame_duration_count = Arc::new(AtomicU32::new(0));

    let frame_duration_accumulate_gba = frame_duration_accumulate.clone();
    let frame_duration_count_gba = frame_duration_count.clone();
    let screen = screen_buffer.clone();
    gba.on_frame(move |gba, state| {
        let buttons = buttons_u16_gba.load(atomic::Ordering::Acquire);
        gba.set_buttons(ButtonSet::from(buttons));

        let mut screen = screen.lock().expect("failed to lock screen buffer");
        screen.copy_from_slice(gba.video().screen());

        let frame_duration_us: u64 = state.frame_duration().as_micros().try_into().unwrap();
        frame_duration_accumulate_gba.fetch_add(frame_duration_us, atomic::Ordering::Release);
        frame_duration_count_gba.fetch_add(1, atomic::Ordering::Release);
    });
    gba.set_paused(false);

    let mut fps_counter = FPSCounter::default();

    log::trace!("starting SDL loop...");
    canvas.set_draw_color(Color::RGB(255, 0, 255));
    'main_loop: loop {
        let frame_start_time = Instant::now();

        for event in event_pump.poll_iter() {
            match event {
                Event::Quit { .. }
                | Event::KeyUp {
                    keycode: Some(Keycode::Escape),
                    ..
                } => {
                    log::trace!("exiting SDL loop...");
                    break 'main_loop;
                }

                Event::KeyDown {
                    keycode: Some(keycode),
                    ..
                } => {
                    if let Some(button) = keycode_to_button(keycode) {
                        buttons.set_pressed(button, true)
                    }
                }

                Event::KeyUp {
                    keycode: Some(keycode),
                    ..
                } => {
                    if let Some(button) = keycode_to_button(keycode) {
                        buttons.set_pressed(button, false)
                    }
                }

                _ => (),
            }
        }

        buttons_u16.store(buttons.into(), atomic::Ordering::Release);

        gba_frame_texture
            .with_lock(None, |buffer: &mut [u8], pitch: usize| {
                let screen = screen_buffer.lock().expect("failed to lock screen buffer");
                for y in 0..160 {
                    for x in 0..240 {
                        let offset_src = y * 240 + x;
                        let offset_dst = y * pitch + x * 2;

                        buffer[offset_dst] = screen[offset_src] as u8;
                        buffer[offset_dst + 1] = (screen[offset_src] >> 8) as u8;
                    }
                }
            })
            .map_err(Error::msg)
            .context("failed to update GBA frame texture")?;

        canvas.clear();
        canvas
            .copy(&gba_frame_texture, None, None)
            .map_err(Error::msg)
            .context("failed to copy GBA frame texture to canvas")?;
        canvas.present();

        let frame_end_time = Instant::now();
        let frame_duration = frame_end_time.duration_since(frame_start_time);
        if frame_duration < frame_delay_target && LOCK_FPS {
            let grace = std::time::Duration::from_millis(1);
            std::thread::sleep((frame_delay_target - frame_duration).saturating_sub(grace));
        }

        if let Some(fps) = fps_counter.count(frame_end_time) {
            const GBA_TARGET_FRAME_DURATION_US: f64 = 1000000.0 / 60.0;

            let frame_duration_accumulate_value =
                frame_duration_accumulate.load(atomic::Ordering::Acquire);
            let frame_duration_count_value = frame_duration_count.load(atomic::Ordering::Acquire);
            let average_frame_duration_us =
                frame_duration_accumulate_value as f64 / frame_duration_count_value as f64;
            frame_duration_accumulate
                .fetch_sub(frame_duration_accumulate_value, atomic::Ordering::Release);
            frame_duration_count.fetch_sub(frame_duration_count_value, atomic::Ordering::Release);

            let gba_average_frame_duration = average_frame_duration_us / 1000.0;
            let gba_performance_percentage =
                (GBA_TARGET_FRAME_DURATION_US / average_frame_duration_us) * 100.0;

            let title = format!(
                "Pyrite ({:.1} FPS) (GBA: {:.1} ms | {:.1} %)",
                fps, gba_average_frame_duration, gba_performance_percentage
            );
            canvas
                .window_mut()
                .set_title(&title)
                .context("failed to set window title")?;
        }
    }

    gba.set_paused(true);
    gba.after_frame_wait(|gba, _| {
        log::debug!(
            "end address: 0x{:08X} (THUMB: {})",
            gba.cpu().next_exec_pc(),
            gba.cpu().registers.getf_t()
        );
    });

    log::info!("exiting...");

    Ok(())
}

fn get_rom_from_args() -> anyhow::Result<Vec<u8>> {
    let rom_path = std::env::args()
        .nth(1)
        .map(std::path::PathBuf::from)
        .ok_or_else(|| anyhow::anyhow!("expected ROM path as first argument"))?;
    std::fs::read(&rom_path)
        .with_context(|| format!("failed to read ROM path `{}`", rom_path.display()))
}

#[derive(Default)]
struct FPSCounter {
    start_time: Option<std::time::Instant>,
    frames: u32,
}

impl FPSCounter {
    pub fn count(&mut self, now: std::time::Instant) -> Option<f64> {
        self.frames += 1;
        if self.start_time.is_none() {
            self.start_time = Some(now);
            return None;
        }

        let elapsed = now.duration_since(self.start_time.unwrap());
        if elapsed < std::time::Duration::from_secs(1) {
            return None;
        }
        self.start_time = Some(now);

        let seconds = elapsed.as_secs_f64();
        let fps = self.frames as f64 / seconds;
        self.frames = 0;

        Some(fps)
    }
}

fn keycode_to_button(keycode: sdl2::keyboard::Keycode) -> Option<Button> {
    match keycode {
        sdl2::keyboard::Keycode::Z => Some(Button::A),
        sdl2::keyboard::Keycode::X => Some(Button::B),
        sdl2::keyboard::Keycode::Left => Some(Button::Left),
        sdl2::keyboard::Keycode::Right => Some(Button::Right),
        sdl2::keyboard::Keycode::Up => Some(Button::Up),
        sdl2::keyboard::Keycode::Down => Some(Button::Down),
        sdl2::keyboard::Keycode::A => Some(Button::L),
        sdl2::keyboard::Keycode::S => Some(Button::R),
        sdl2::keyboard::Keycode::Return => Some(Button::Start),
        sdl2::keyboard::Keycode::Backspace => Some(Button::Select),
        _ => None,
    }
}
<commit_msg>Stub audio implementation.<commit_after>use std::{
    sync::{
        atomic::{self, AtomicU16, AtomicU32, AtomicU64},
        Arc, Mutex,
    },
    time::Instant,
};

use anyhow::{Context as _, Error};
use gba::{Button, ButtonSet};
use sdl2::{
    audio::{AudioCallback, AudioSpec, AudioSpecDesired},
    event::Event,
    keyboard::Keycode,
    pixels::{Color, PixelFormatEnum},
};

const LOCK_FPS: bool = false;

fn main() {
    if let Err(err) = run() {
        eprintln!("{:#}", err);
        std::process::exit(1);
    }
    std::process::exit(0)
}

fn run() -> anyhow::Result<()> {
    pretty_env_logger::formatted_builder()
        .parse_filters(&std::env::var("RUST_LOG").unwrap_or_else(|_| String::from("info")))
        .try_init()
        .context("failed to initiaize logger")?;

    let config = pyrite::config::from_toml_path("pyrite.toml")
        .map_err(|err| {
            let err = anyhow::Error::from(err);
            log::error!("{:#}", err);
        })
        .unwrap_or_default();

    let frame_delay_target = std::time::Duration::from_secs_f64(1.0 / 60.0);

    let sdl_context = sdl2::init()
        .map_err(Error::msg)
        .context("failed to initialize SDL")?;
    let video_subsystem = sdl_context
        .video()
        .map_err(Error::msg)
        .context("failed to initialize SDL video")?;
    let audio_subsystem = sdl_context
        .audio()
        .map_err(Error::msg)
        .context("failed to initialize SDL audio")?;

    let desired_spec = AudioSpecDesired {
        freq: Some(44100),
        channels: Some(1),
        samples: None,
    };
    let device = audio_subsystem
        .open_playback(None, &desired_spec, |spec| GbaAudio { spec })
        .map_err(Error::msg)
        .context("failed to open audio playback")?;
    device.resume();

    let window = video_subsystem
        .window("Pyrite", 480, 320)
        .position_centered()
        .resizable()
        .build()
        .context("failed to create SDL window")?;
    let mut event_pump = sdl_context
        .event_pump()
        .map_err(Error::msg)
        .context("failed to initialize SDL event pump")?;
    let mut canvas_builder = window.into_canvas().accelerated();

    if config.graphics.vsync.unwrap_or(true) {
        canvas_builder = canvas_builder.present_vsync();
    }

    let mut canvas = canvas_builder
        .build()
        .context("failed to initialize SDL canvas")?;
    let texture_creator = canvas.texture_creator();

    let mut gba_frame_texture = texture_creator
        .create_texture_streaming(PixelFormatEnum::BGR555, 240, 160)
        .context("failed to create GBA frame texture")?;
    gba_frame_texture
        .with_lock(None, |buffer: &mut [u8], _: usize| buffer.fill(0))
        .map_err(Error::msg)
        .context("failed to update GBA frame texture")?;
    let screen_buffer = Arc::new(Mutex::new([0u16; gba::SCREEN_PIXEL_COUNT]));

    let gba = pyrite::GbaHandle::new();

    let rom = get_rom_from_args().context("error occurred retrieving ROM from args")?;
    let bios = config.gba.bios_path.and_then(|path| {
        match std::fs::read(&path)
            .with_context(|| format!("failed to read path {}", path.display()))
        {
            Ok(data) => Some(data),
            Err(err) => {
                log::error!("error occurred while loading BIOS: {:?}", err);
                None
            }
        }
    });
    let boot_from_bios = config.gba.boot_from_bios.unwrap_or(true);

    gba.after_frame_wait(move |gba, _| {
        gba.set_gamepak(rom);
        gba.set_bios(bios);
        gba.reset(boot_from_bios);
    });

    let mut buttons = ButtonSet::default();
    let buttons_u16 = Arc::new(AtomicU16::new(buttons.into()));
    let buttons_u16_gba = buttons_u16.clone();

    let frame_duration_accumulate = Arc::new(AtomicU64::new(0));
    let frame_duration_count = Arc::new(AtomicU32::new(0));

    let frame_duration_accumulate_gba = frame_duration_accumulate.clone();
    let frame_duration_count_gba = frame_duration_count.clone();
    let screen = screen_buffer.clone();
    gba.on_frame(move |gba, state| {
        let buttons = buttons_u16_gba.load(atomic::Ordering::Acquire);
        gba.set_buttons(ButtonSet::from(buttons));

        let mut screen = screen.lock().expect("failed to lock screen buffer");
        screen.copy_from_slice(gba.video().screen());

        let frame_duration_us: u64 = state.frame_duration().as_micros().try_into().unwrap();
        frame_duration_accumulate_gba.fetch_add(frame_duration_us, atomic::Ordering::Release);
        frame_duration_count_gba.fetch_add(1, atomic::Ordering::Release);
    });
    gba.set_paused(false);

    let mut fps_counter = FPSCounter::default();

    log::trace!("starting SDL loop...");
    canvas.set_draw_color(Color::RGB(255, 0, 255));
    'main_loop: loop {
        let frame_start_time = Instant::now();

        for event in event_pump.poll_iter() {
            match event {
                Event::Quit { .. }
                | Event::KeyUp {
                    keycode: Some(Keycode::Escape),
                    ..
                } => {
                    log::trace!("exiting SDL loop...");
                    break 'main_loop;
                }

                Event::KeyDown {
                    keycode: Some(keycode),
                    ..
                } => {
                    if let Some(button) = keycode_to_button(keycode) {
                        buttons.set_pressed(button, true)
                    }
                }

                Event::KeyUp {
                    keycode: Some(keycode),
                    ..
                } => {
                    if let Some(button) = keycode_to_button(keycode) {
                        buttons.set_pressed(button, false)
                    }
                }

                _ => (),
            }
        }

        buttons_u16.store(buttons.into(), atomic::Ordering::Release);

        gba_frame_texture
            .with_lock(None, |buffer: &mut [u8], pitch: usize| {
                let screen = screen_buffer.lock().expect("failed to lock screen buffer");
                for y in 0..160 {
                    for x in 0..240 {
                        let offset_src = y * 240 + x;
                        let offset_dst = y * pitch + x * 2;

                        buffer[offset_dst] = screen[offset_src] as u8;
                        buffer[offset_dst + 1] = (screen[offset_src] >> 8) as u8;
                    }
                }
            })
            .map_err(Error::msg)
            .context("failed to update GBA frame texture")?;

        canvas.clear();
        canvas
            .copy(&gba_frame_texture, None, None)
            .map_err(Error::msg)
            .context("failed to copy GBA frame texture to canvas")?;
        canvas.present();

        let frame_end_time = Instant::now();
        let frame_duration = frame_end_time.duration_since(frame_start_time);
        if frame_duration < frame_delay_target && LOCK_FPS {
            let grace = std::time::Duration::from_millis(1);
            std::thread::sleep((frame_delay_target - frame_duration).saturating_sub(grace));
        }

        if let Some(fps) = fps_counter.count(frame_end_time) {
            const GBA_TARGET_FRAME_DURATION_US: f64 = 1000000.0 / 60.0;

            let frame_duration_accumulate_value =
                frame_duration_accumulate.load(atomic::Ordering::Acquire);
            let frame_duration_count_value = frame_duration_count.load(atomic::Ordering::Acquire);
            let average_frame_duration_us =
                frame_duration_accumulate_value as f64 / frame_duration_count_value as f64;
            frame_duration_accumulate
                .fetch_sub(frame_duration_accumulate_value, atomic::Ordering::Release);
            frame_duration_count.fetch_sub(frame_duration_count_value, atomic::Ordering::Release);

            let gba_average_frame_duration = average_frame_duration_us / 1000.0;
            let gba_performance_percentage =
                (GBA_TARGET_FRAME_DURATION_US / average_frame_duration_us) * 100.0;

            let title = format!(
                "Pyrite ({:.1} FPS) (GBA: {:.1} ms | {:.1} %)",
                fps, gba_average_frame_duration, gba_performance_percentage
            );
            canvas
                .window_mut()
                .set_title(&title)
                .context("failed to set window title")?;
        }
    }

    device.pause();
    gba.set_paused(true);
    gba.after_frame_wait(|gba, _| {
        log::debug!(
            "end address: 0x{:08X} (THUMB: {})",
            gba.cpu().next_exec_pc(),
            gba.cpu().registers.getf_t()
        );
    });

    log::info!("exiting...");

    Ok(())
}

pub struct GbaAudio {
    spec: AudioSpec,
}

impl AudioCallback for GbaAudio {
    type Channel = f32;

    fn callback(&mut self, out: &mut [Self::Channel]) {
        static mut PHASE: f32 = 0.0;
        static VOLUME: f32 = 0.001;

        let phase_inc = 440.0 / self.spec.freq as f32;
        out.iter_mut().for_each(|sample| {
            *sample = if unsafe { PHASE } <= 0.5 {
                VOLUME
            } else {
                -VOLUME
            };
            unsafe { PHASE = (PHASE + phase_inc) % 1.0 };
        });
    }
}

fn get_rom_from_args() -> anyhow::Result<Vec<u8>> {
    let rom_path = std::env::args()
        .nth(1)
        .map(std::path::PathBuf::from)
        .ok_or_else(|| anyhow::anyhow!("expected ROM path as first argument"))?;
    std::fs::read(&rom_path)
        .with_context(|| format!("failed to read ROM path `{}`", rom_path.display()))
}

#[derive(Default)]
struct FPSCounter {
    start_time: Option<std::time::Instant>,
    frames: u32,
}

impl FPSCounter {
    pub fn count(&mut self, now: std::time::Instant) -> Option<f64> {
        self.frames += 1;
        if self.start_time.is_none() {
            self.start_time = Some(now);
            return None;
        }

        let elapsed = now.duration_since(self.start_time.unwrap());
        if elapsed < std::time::Duration::from_secs(1) {
            return None;
        }
        self.start_time = Some(now);

        let seconds = elapsed.as_secs_f64();
        let fps = self.frames as f64 / seconds;
        self.frames = 0;

        Some(fps)
    }
}

fn keycode_to_button(keycode: sdl2::keyboard::Keycode) -> Option<Button> {
    match keycode {
        sdl2::keyboard::Keycode::Z => Some(Button::A),
        sdl2::keyboard::Keycode::X => Some(Button::B),
        sdl2::keyboard::Keycode::Left => Some(Button::Left),
        sdl2::keyboard::Keycode::Right => Some(Button::Right),
        sdl2::keyboard::Keycode::Up => Some(Button::Up),
        sdl2::keyboard::Keycode::Down => Some(Button::Down),
        sdl2::keyboard::Keycode::A => Some(Button::L),
        sdl2::keyboard::Keycode::S => Some(Button::R),
        sdl2::keyboard::Keycode::Return => Some(Button::Start),
        sdl2::keyboard::Keycode::Backspace => Some(Button::Select),
        _ => None,
    }
}
<|endoftext|>


<commit_before>//! Platform-specific types, as defined by C.
//!
//! Code that interacts via FFI will almost certainly be using the
//! base types provided by C, which aren't nearly as nicely defined
//! as Rust's primitive types. This module provides types which will
//! match those defined by C, so that code that interacts with C will
//! refer to the correct types.

#![stable(feature = "raw_os", since = "1.1.0")]

#[cfg(test)]
mod tests;

#[doc(include = "char.md")]
#[cfg(any(
    all(
        target_os = "linux",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "hexagon",
            target_arch = "powerpc",
            target_arch = "powerpc64",
            target_arch = "s390x",
            target_arch = "riscv64",
            target_arch = "riscv32"
        )
    ),
    all(target_os = "android", any(target_arch = "aarch64", target_arch = "arm")),
    all(target_os = "l4re", target_arch = "x86_64"),
    all(
        target_os = "freebsd",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "powerpc",
            target_arch = "powerpc64"
        )
    ),
    all(
        target_os = "netbsd",
        any(target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc")
    ),
    all(target_os = "openbsd", target_arch = "aarch64"),
    all(
        target_os = "vxworks",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "powerpc64",
            target_arch = "powerpc"
        )
    ),
    all(target_os = "fuchsia", target_arch = "aarch64")
))]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_char = u8;
#[doc(include = "char.md")]
#[cfg(not(any(
    all(
        target_os = "linux",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "hexagon",
            target_arch = "powerpc",
            target_arch = "powerpc64",
            target_arch = "s390x",
            target_arch = "riscv64",
            target_arch = "riscv32"
        )
    ),
    all(target_os = "android", any(target_arch = "aarch64", target_arch = "arm")),
    all(target_os = "l4re", target_arch = "x86_64"),
    all(
        target_os = "freebsd",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "powerpc",
            target_arch = "powerpc64"
        )
    ),
    all(
        target_os = "netbsd",
        any(target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc")
    ),
    all(target_os = "openbsd", target_arch = "aarch64"),
    all(
        target_os = "vxworks",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "powerpc64",
            target_arch = "powerpc"
        )
    ),
    all(target_os = "fuchsia", target_arch = "aarch64")
)))]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_char = i8;
#[doc(include = "schar.md")]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_schar = i8;
#[doc(include = "uchar.md")]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_uchar = u8;
#[doc(include = "short.md")]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_short = i16;
#[doc(include = "ushort.md")]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_ushort = u16;
#[doc(include = "int.md")]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_int = i32;
#[doc(include = "uint.md")]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_uint = u32;
#[doc(include = "long.md")]
#[cfg(any(target_pointer_width = "32", windows))]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_long = i32;
#[doc(include = "ulong.md")]
#[cfg(any(target_pointer_width = "32", windows))]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_ulong = u32;
#[doc(include = "long.md")]
#[cfg(all(target_pointer_width = "64", not(windows)))]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_long = i64;
#[doc(include = "ulong.md")]
#[cfg(all(target_pointer_width = "64", not(windows)))]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_ulong = u64;
#[doc(include = "longlong.md")]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_longlong = i64;
#[doc(include = "ulonglong.md")]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_ulonglong = u64;
#[doc(include = "float.md")]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_float = f32;
#[doc(include = "double.md")]
#[stable(feature = "raw_os", since = "1.1.0")]
pub type c_double = f64;

#[stable(feature = "raw_os", since = "1.1.0")]
#[doc(no_inline)]
pub use core::ffi::c_void;
<commit_msg>std::src::os::raw: Refactor, introducing macro type_alias!<commit_after>//! Platform-specific types, as defined by C.
//!
//! Code that interacts via FFI will almost certainly be using the
//! base types provided by C, which aren't nearly as nicely defined
//! as Rust's primitive types. This module provides types which will
//! match those defined by C, so that code that interacts with C will
//! refer to the correct types.

#![stable(feature = "raw_os", since = "1.1.0")]

#[cfg(test)]
mod tests;

macro_rules! type_alias {
    { $Docfile:tt, $Alias:ident = $Real:ty; $( $Cfg:tt )* } => {
        #[doc(include = $Docfile)]
        $( $Cfg )*
        #[stable(feature = "raw_os", since = "1.1.0")]
        pub type $Alias = $Real;
    }
}

type_alias! { "char.md", c_char = u8;
#[cfg(any(
    all(
        target_os = "linux",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "hexagon",
            target_arch = "powerpc",
            target_arch = "powerpc64",
            target_arch = "s390x",
            target_arch = "riscv64",
            target_arch = "riscv32"
        )
    ),
    all(target_os = "android", any(target_arch = "aarch64", target_arch = "arm")),
    all(target_os = "l4re", target_arch = "x86_64"),
    all(
        target_os = "freebsd",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "powerpc",
            target_arch = "powerpc64"
        )
    ),
    all(
        target_os = "netbsd",
        any(target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc")
    ),
    all(target_os = "openbsd", target_arch = "aarch64"),
    all(
        target_os = "vxworks",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "powerpc64",
            target_arch = "powerpc"
        )
    ),
    all(target_os = "fuchsia", target_arch = "aarch64")
))]}
type_alias! { "char.md", c_char = i8;
#[cfg(not(any(
    all(
        target_os = "linux",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "hexagon",
            target_arch = "powerpc",
            target_arch = "powerpc64",
            target_arch = "s390x",
            target_arch = "riscv64",
            target_arch = "riscv32"
        )
    ),
    all(target_os = "android", any(target_arch = "aarch64", target_arch = "arm")),
    all(target_os = "l4re", target_arch = "x86_64"),
    all(
        target_os = "freebsd",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "powerpc",
            target_arch = "powerpc64"
        )
    ),
    all(
        target_os = "netbsd",
        any(target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc")
    ),
    all(target_os = "openbsd", target_arch = "aarch64"),
    all(
        target_os = "vxworks",
        any(
            target_arch = "aarch64",
            target_arch = "arm",
            target_arch = "powerpc64",
            target_arch = "powerpc"
        )
    ),
    all(target_os = "fuchsia", target_arch = "aarch64")
)))]}
type_alias! { "schar.md", c_schar = i8; }
type_alias! { "uchar.md", c_uchar = u8; }
type_alias! { "short.md", c_short = i16; }
type_alias! { "ushort.md", c_ushort = u16; }
type_alias! { "int.md", c_int = i32; }
type_alias! { "uint.md", c_uint = u32; }
type_alias! { "long.md", c_long = i32; #[cfg(any(target_pointer_width = "32", windows))] }
type_alias! { "ulong.md", c_ulong = u32; #[cfg(any(target_pointer_width = "32", windows))] }
type_alias! { "long.md", c_long = i64; #[cfg(all(target_pointer_width = "64", not(windows)))] }
type_alias! { "ulong.md", c_ulong = u64; #[cfg(all(target_pointer_width = "64", not(windows)))] }
type_alias! { "longlong.md", c_longlong = i64; }
type_alias! { "ulonglong.md", c_ulonglong = u64; }
type_alias! { "float.md", c_float = f32; }
type_alias! { "double.md", c_double = f64; }

#[stable(feature = "raw_os", since = "1.1.0")]
#[doc(no_inline)]
pub use core::ffi::c_void;
<|endoftext|>


<commit_before>use std::fmt::Debug;

peg::parser!{
    grammar g() for str {
        pub rule returns_impl_trait() -> impl Debug
            = "" { Box::new(5) }
    }
}

fn main() {
    assert_eq!(format!("{:?}", g::returns_impl_trait("")), "Ok(5)");
}
<commit_msg>Disable `impl Trait` test with --features trace<commit_after>use std::fmt::Debug;

// `--features trace` code names the return type, so doesn't work with `impl Trait`
#[cfg(not(feature = "trace"))]
peg::parser!{
    grammar g() for str {
        pub rule returns_impl_trait() -> impl Debug
            = "" { Box::new(5) }
    }
}

fn main() {
    #[cfg(not(feature = "trace"))]
    assert_eq!(format!("{:?}", g::returns_impl_trait("")), "Ok(5)");
}
<|endoftext|>


<commit_before>//! This module provides all

pub mod components;

use std::sync::{RwLock, RwLockWriteGuard};
use std::any::TypeId;
use std::collections::HashMap;
use std::collections::hash_map;
use entities::components::{Component, PositionComponent};
use std::sync::Arc;
use mopa;

type ID = u64;

// Once upon a time this was in different files.
// Then I realized that was a pain without making everything public.
// TODO: Rewrite all the locking and stuff. This is horrible for performance and lock contention.

lazy_static! {
    /// A global [`World`](struct.world.html) for common use.
    /// This is a lazy static, but it can be used as a global variable.
    pub static ref THEWORLD: RwLock<World> = {
        let mut world = World::new();

        world.register_component::<PositionComponent>();

        RwLock::new(world)
    };
}

// Get around a case sensitivity collision with WORLD and World,
// Which breaks cargo doc.
pub use self::THEWORLD as WORLD;

/// Represents a "storage" for entities and their components.
pub struct World {
    // The `Box<Any + Send + Sync>` is supposed to represent a ComponentStorage.
    components: HashMap<TypeId, Box<ComponentStorageTrait>>,
    entities: HashMap<ID, Arc<RwLock<Entity>>>,
    /// The last ID allocated.
    id: ID,
}

impl World {
    /// Create a new world.
    ///
    /// Do not use this outside tests, use the static `WORLD` instead.
    pub fn new() -> World {
        World {
            components: HashMap::new(),
            entities: HashMap::new(),
            id: 0,
        }
    }

    /// Register a new component type for being used by entities.
    /// This must always be called before attempting to use a component type!
    pub fn register_component<T: Component>(&mut self) {
        self.components.insert(TypeId::of::<T>(), Box::new(ComponentStorage::<T>::new()));
    }

    /// Get an entity by ID.
    pub fn get_entity(&self, id: ID) -> Option<Arc<RwLock<Entity>>> {
        self.entities.get(&id).map(|x| x.clone())
    }

    /// Gets a component of an entity, by the entity ID.
    ///
    /// # Panics.
    /// Panics if the component type is not registered.
    pub fn get_component<T: Component>(&self, id: ID) -> Option<Arc<RwLock<T>>> {
        let storage = &self.get_storage().0;
        let ret = storage.get(&id).map(|x| x.clone());
        ret
    }

    /// Returns an iterator over all components of a certain type.
    ///
    /// # Panics.
    /// Panics if the component type is not registered.
    pub fn iter_components<'a, T: Component>(&'a self) -> ComponentIter<'a, T> {
        let storage = self.get_storage();
        let iter = storage.0.iter();
        ComponentIter { iter: iter }
    }

    /// Returns an iterator over all the entities.
    pub fn iter_entities<'a>(&'a self) -> EntityIter<'a> {
        EntityIter { iter: self.entities.iter() }
    }
}

impl World {
    fn get_storage<'a, T: Component>(&'a self) -> &'a ComponentStorage<T> {
        self.components
            .get(&TypeId::of::<T>())
            .expect("Tried to retrieve unregistered component storage.")
            .downcast_ref()
            .unwrap()
    }

    fn get_storage_mut<T: Component>(&mut self) -> &mut ComponentStorage<T> {
        self.components
            .get_mut(&TypeId::of::<T>())
            .expect("Tried to retrieve unregistered component storage.")
            .downcast_mut()
            .unwrap()
    }
}

/// Returns an `EntityBuilder` to make a new entity.
/// See the documentation on `EntityBuilder` for more info.
///
/// This function takes in a reference to an `RwLock<World>`.
/// As such you do not want to lock the `World` beforehand.
/// Pass the global `WORLD` for common use instead.
///
/// **NOTE**: While the `EntityBuilder` is held, the world is mutably locked.
/// If already have a lock on the world (or are planning to make one), this *will* deadlock!
pub fn make_builder<'a>(world: &'a RwLock<World>) -> EntityBuilder<'a> {
    let mut world = world.write().unwrap();
    let id = world.id;
    world.id += 1;
    world.entities.insert(id, Arc::new(RwLock::new(Entity { id: id })));

    EntityBuilder::new(world, id)
}

trait ComponentStorageTrait: mopa::Any + Send + Sync {}
mopafy!(ComponentStorageTrait);

struct ComponentStorage<T: Component>(HashMap<ID, Arc<RwLock<T>>>);
impl<T: Component> ComponentStorageTrait for ComponentStorage<T> {}
impl<T: Component> ComponentStorage<T> {
    fn new() -> ComponentStorage<T> {
        ComponentStorage(HashMap::new())
    }
}

/// An iterator over all components of a certain type in a `World`.
pub struct ComponentIter<'a, T: Component> {
    iter: hash_map::Iter<'a, ID, Arc<RwLock<T>>>,
}

impl<'a, T: Component> Iterator for ComponentIter<'a, T> {
    type Item = (ID, Arc<RwLock<T>>);

    fn next(&mut self) -> Option<(ID, Arc<RwLock<T>>)> {
        self.iter.next().map(|(id, arc)| (*id, arc.clone()))
    }
}

/// An iterator over all entities in a `World`.
pub struct EntityIter<'a> {
    iter: hash_map::Iter<'a, ID, Arc<RwLock<Entity>>>,
}

impl<'a> Iterator for EntityIter<'a> {
    type Item = (ID, Arc<RwLock<Entity>>);

    fn next(&mut self) -> Option<(ID, Arc<RwLock<Entity>>)> {
        self.iter.next().map(|(id, arc)| (*id, arc.clone()))
    }
}

/// An interface into building an entity.
///
/// An `EntityBuilder` is returned by [`make_builder`](fn.make_builder.html),
/// it is a "window" into the creation of the entity,
/// and allows you to give it components on creation.
///
/// # Example
///
/// ```
/// use shared::entities::{WORLD, make_builder};
/// use shared::entities::components::PositionComponent;
/// let new_entity = make_builder(&WORLD)
///                  .with_component(PositionComponent::empty())
///                  .finish();
/// assert_eq!(new_entity.read().unwrap().get_id(), 0);
/// ```
pub struct EntityBuilder<'a> {
    id: ID,
    world: RwLockWriteGuard<'a, World>,
}

impl<'a> EntityBuilder<'a> {
    fn new(world: RwLockWriteGuard<World>, id: ID) -> EntityBuilder {
        EntityBuilder {
            id: id,
            world: world,
        }
    }

    /// Add a new component instance `T` to be added to the final entity.
    pub fn with_component<T: Component>(mut self, component: T) -> EntityBuilder<'a> {
        {
            let ref mut storage = self.world.get_storage_mut().0;
            storage.insert(self.id, Arc::new(RwLock::new(component)));
        }
        self
    }

    /// Consumes the `EntityBuilder`, returning the entity that was created.
    ///
    /// It should be noted that, as of the time of writing,
    /// This function doesn't need to be called to have the entity be created.
    pub fn finish(self) -> Arc<RwLock<Entity>> {
        self.world.get_entity(self.id).unwrap()
    }
}

#[derive(Hash, Debug)]
pub struct Entity {
    id: ID,
}

impl Entity {
    pub fn get_id(&self) -> ID {
        self.id
    }
}

impl PartialEq for Entity {
    fn eq(&self, other: &Entity) -> bool {
        self.id == other.id
    }
}

impl Eq for Entity {}

impl PartialEq<ID> for Entity {
    fn eq(&self, other: &ID) -> bool {
        self.id == *other
    }
}

impl<'a> PartialEq<RwLockWriteGuard<'a, Entity>> for Entity {
    fn eq(&self, other: &RwLockWriteGuard<'a, Entity>) -> bool {
        self.id == other.id
    }
}

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

    struct TestComponent {
        a: i32,
    }

    impl Component for TestComponent {}

    #[test]
    fn test_basic() {
        let world = RwLock::new(World::new());
        if let Some(_) = world.read().unwrap().get_entity(0) {
            panic!("World returned a Some!")
        }
        let entity = make_builder(&world).finish();
        let other = world.read()
            .unwrap()
            .get_entity(0)
            .unwrap();

        assert_eq!(entity.read().unwrap().get_id(),
                   other.read().unwrap().get_id());
    }

    #[test]
    fn test_component() {
        let world = RwLock::new(World::new());
        world.write().unwrap().register_component::<TestComponent>();
        make_builder(&world).with_component(TestComponent { a: 123 }).finish();

        let comp = world.read()
            .unwrap()
            .get_component::<TestComponent>(0)
            .unwrap();

        assert_eq!(comp.read().unwrap().a, 123);
    }

    #[test]
    fn test_iter_entities() {
        let world = RwLock::new(World::new());
        make_builder(&world).finish();
        make_builder(&world).finish();
        make_builder(&world).finish();

        let lock = world.read().unwrap();
        let mut iter = lock.iter_entities();
        let a = iter.next().unwrap();
        let b = iter.next().unwrap();
        let c = iter.next().unwrap();
        assert_eq!(a.0,
                   a.1
                       .read()
                       .unwrap()
                       .get_id());
        assert_eq!(b.0,
                   b.1
                       .read()
                       .unwrap()
                       .get_id());
        assert_eq!(c.0,
                   c.1
                       .read()
                       .unwrap()
                       .get_id());
    }
}
<commit_msg>I forgot to run git add again. Point fingers and laugh.<commit_after>//! This module provides all

pub mod components;

use std::sync::{RwLock, RwLockWriteGuard};
use std::any::TypeId;
use std::collections::HashMap;
use std::collections::hash_map;
pub use entities::components::{Component, PositionComponent, Positional};
use std::sync::Arc;
use mopa;

type ID = u64;

// Once upon a time this was in different files.
// Then I realized that was a pain without making everything public.
// TODO: Rewrite all the locking and stuff. This is horrible for performance and lock contention.

lazy_static! {
    /// A global [`World`](struct.world.html) for common use.
    /// This is a lazy static, but it can be used as a global variable.
    pub static ref THEWORLD: RwLock<World> = {
        let mut world = World::new();

        world.register_component::<PositionComponent>();

        RwLock::new(world)
    };
}

// Get around a case sensitivity collision with WORLD and World,
// Which breaks cargo doc.
pub use self::THEWORLD as WORLD;

/// Represents a "storage" for entities and their components.
pub struct World {
    // The `Box<Any + Send + Sync>` is supposed to represent a ComponentStorage.
    components: HashMap<TypeId, Box<ComponentStorageTrait>>,
    entities: HashMap<ID, Arc<RwLock<Entity>>>,
    /// The last ID allocated.
    id: ID,
}

impl World {
    /// Create a new world.
    ///
    /// Do not use this outside tests, use the static `WORLD` instead.
    pub fn new() -> World {
        World {
            components: HashMap::new(),
            entities: HashMap::new(),
            id: 0,
        }
    }

    /// Register a new component type for being used by entities.
    /// This must always be called before attempting to use a component type!
    pub fn register_component<T: Component>(&mut self) {
        self.components.insert(TypeId::of::<T>(), Box::new(ComponentStorage::<T>::new()));
    }

    /// Get an entity by ID.
    pub fn get_entity(&self, id: ID) -> Option<Arc<RwLock<Entity>>> {
        self.entities.get(&id).map(|x| x.clone())
    }

    /// Gets a component of an entity, by the entity ID.
    ///
    /// # Panics.
    /// Panics if the component type is not registered.
    pub fn get_component<T: Component>(&self, id: ID) -> Option<Arc<RwLock<T>>> {
        let storage = &self.get_storage().0;
        let ret = storage.get(&id).map(|x| x.clone());
        ret
    }

    /// Returns an iterator over all components of a certain type.
    ///
    /// # Panics.
    /// Panics if the component type is not registered.
    pub fn iter_components<'a, T: Component>(&'a self) -> ComponentIter<'a, T> {
        let storage = self.get_storage();
        let iter = storage.0.iter();
        ComponentIter { iter: iter }
    }

    /// Returns an iterator over all the entities.
    pub fn iter_entities<'a>(&'a self) -> EntityIter<'a> {
        EntityIter { iter: self.entities.iter() }
    }
}

impl World {
    fn get_storage<'a, T: Component>(&'a self) -> &'a ComponentStorage<T> {
        self.components
            .get(&TypeId::of::<T>())
            .expect("Tried to retrieve unregistered component storage.")
            .downcast_ref()
            .unwrap()
    }

    fn get_storage_mut<T: Component>(&mut self) -> &mut ComponentStorage<T> {
        self.components
            .get_mut(&TypeId::of::<T>())
            .expect("Tried to retrieve unregistered component storage.")
            .downcast_mut()
            .unwrap()
    }
}

/// Returns an `EntityBuilder` to make a new entity.
/// See the documentation on `EntityBuilder` for more info.
///
/// This function takes in a reference to an `RwLock<World>`.
/// As such you do not want to lock the `World` beforehand.
/// Pass the global `WORLD` for common use instead.
///
/// **NOTE**: While the `EntityBuilder` is held, the world is mutably locked.
/// If already have a lock on the world (or are planning to make one), this *will* deadlock!
pub fn make_builder<'a>(world: &'a RwLock<World>) -> EntityBuilder<'a> {
    let mut world = world.write().unwrap();
    let id = world.id;
    world.id += 1;
    world.entities.insert(id, Arc::new(RwLock::new(Entity { id: id })));

    EntityBuilder::new(world, id)
}

trait ComponentStorageTrait: mopa::Any + Send + Sync {}
mopafy!(ComponentStorageTrait);

struct ComponentStorage<T: Component>(HashMap<ID, Arc<RwLock<T>>>);
impl<T: Component> ComponentStorageTrait for ComponentStorage<T> {}
impl<T: Component> ComponentStorage<T> {
    fn new() -> ComponentStorage<T> {
        ComponentStorage(HashMap::new())
    }
}

/// An iterator over all components of a certain type in a `World`.
pub struct ComponentIter<'a, T: Component> {
    iter: hash_map::Iter<'a, ID, Arc<RwLock<T>>>,
}

impl<'a, T: Component> Iterator for ComponentIter<'a, T> {
    type Item = (ID, Arc<RwLock<T>>);

    fn next(&mut self) -> Option<(ID, Arc<RwLock<T>>)> {
        self.iter.next().map(|(id, arc)| (*id, arc.clone()))
    }
}

/// An iterator over all entities in a `World`.
pub struct EntityIter<'a> {
    iter: hash_map::Iter<'a, ID, Arc<RwLock<Entity>>>,
}

impl<'a> Iterator for EntityIter<'a> {
    type Item = (ID, Arc<RwLock<Entity>>);

    fn next(&mut self) -> Option<(ID, Arc<RwLock<Entity>>)> {
        self.iter.next().map(|(id, arc)| (*id, arc.clone()))
    }
}

/// An interface into building an entity.
///
/// An `EntityBuilder` is returned by [`make_builder`](fn.make_builder.html),
/// it is a "window" into the creation of the entity,
/// and allows you to give it components on creation.
///
/// # Example
///
/// ```
/// use shared::entities::{WORLD, make_builder};
/// use shared::entities::components::PositionComponent;
/// let new_entity = make_builder(&WORLD)
///                  .with_component(PositionComponent::empty())
///                  .finish();
/// assert_eq!(new_entity.read().unwrap().get_id(), 0);
/// ```
pub struct EntityBuilder<'a> {
    id: ID,
    world: RwLockWriteGuard<'a, World>,
}

impl<'a> EntityBuilder<'a> {
    fn new(world: RwLockWriteGuard<World>, id: ID) -> EntityBuilder {
        EntityBuilder {
            id: id,
            world: world,
        }
    }

    /// Add a new component instance `T` to be added to the final entity.
    pub fn with_component<T: Component>(mut self, component: T) -> EntityBuilder<'a> {
        {
            let ref mut storage = self.world.get_storage_mut().0;
            storage.insert(self.id, Arc::new(RwLock::new(component)));
        }
        self
    }

    /// Consumes the `EntityBuilder`, returning the entity that was created.
    ///
    /// It should be noted that, as of the time of writing,
    /// This function doesn't need to be called to have the entity be created.
    pub fn finish(self) -> Arc<RwLock<Entity>> {
        self.world.get_entity(self.id).unwrap()
    }
}

#[derive(Hash, Debug)]
pub struct Entity {
    id: ID,
}

impl Entity {
    pub fn get_id(&self) -> ID {
        self.id
    }
}

impl PartialEq for Entity {
    fn eq(&self, other: &Entity) -> bool {
        self.id == other.id
    }
}

impl Eq for Entity {}

impl PartialEq<ID> for Entity {
    fn eq(&self, other: &ID) -> bool {
        self.id == *other
    }
}

impl<'a> PartialEq<RwLockWriteGuard<'a, Entity>> for Entity {
    fn eq(&self, other: &RwLockWriteGuard<'a, Entity>) -> bool {
        self.id == other.id
    }
}

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

    struct TestComponent {
        a: i32,
    }

    impl Component for TestComponent {}

    #[test]
    fn test_basic() {
        let world = RwLock::new(World::new());
        if let Some(_) = world.read().unwrap().get_entity(0) {
            panic!("World returned a Some!")
        }
        let entity = make_builder(&world).finish();
        let other = world.read()
            .unwrap()
            .get_entity(0)
            .unwrap();

        assert_eq!(entity.read().unwrap().get_id(),
                   other.read().unwrap().get_id());
    }

    #[test]
    fn test_component() {
        let world = RwLock::new(World::new());
        world.write().unwrap().register_component::<TestComponent>();
        make_builder(&world).with_component(TestComponent { a: 123 }).finish();

        let comp = world.read()
            .unwrap()
            .get_component::<TestComponent>(0)
            .unwrap();

        assert_eq!(comp.read().unwrap().a, 123);
    }

    #[test]
    fn test_iter_entities() {
        let world = RwLock::new(World::new());
        make_builder(&world).finish();
        make_builder(&world).finish();
        make_builder(&world).finish();

        let lock = world.read().unwrap();
        let mut iter = lock.iter_entities();
        let a = iter.next().unwrap();
        let b = iter.next().unwrap();
        let c = iter.next().unwrap();
        assert_eq!(a.0,
                   a.1
                       .read()
                       .unwrap()
                       .get_id());
        assert_eq!(b.0,
                   b.1
                       .read()
                       .unwrap()
                       .get_id());
        assert_eq!(c.0,
                   c.1
                       .read()
                       .unwrap()
                       .get_id());
    }
}
<|endoftext|>


<commit_before>use renderer::html_handlebars::helpers;
use renderer::Renderer;
use book::MDBook;
use book::bookitem::BookItem;
use {utils, theme};
use regex::{Regex, Captures};

use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::error::Error;
use std::io::{self, Read};
use std::collections::BTreeMap;

use handlebars::Handlebars;

use serde_json;

#[derive(Default)]
pub struct HtmlHandlebars;

impl HtmlHandlebars {
    pub fn new() -> Self {
        HtmlHandlebars
    }
}

impl Renderer for HtmlHandlebars {
    fn render(&self, book: &MDBook) -> Result<(), Box<Error>> {
        debug!("[fn]: render");
        let mut handlebars = Handlebars::new();

        // Load theme
        let theme = theme::Theme::new(book.get_theme_path());

        // Register template
        debug!("[*]: Register handlebars template");
        try!(handlebars.register_template_string("index", try!(String::from_utf8(theme.index))));

        // Register helpers
        debug!("[*]: Register handlebars helpers");
        handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc));
        handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
        handlebars.register_helper("next", Box::new(helpers::navigation::next));

        let mut data = try!(make_data(book));

        // Print version
        let mut print_content: String = String::new();

        // Check if dest directory exists
        debug!("[*]: Check if destination directory exists");
        if fs::create_dir_all(book.get_dest()).is_err() {
            return Err(Box::new(io::Error::new(io::ErrorKind::Other,
                                               "Unexpected error when constructing destination path")));
        }

        // Render a file for every entry in the book
        let mut index = true;
        for item in book.iter() {

            match *item {
                BookItem::Chapter(_, ref ch) |
                BookItem::Affix(ref ch) => {
                    if ch.path != PathBuf::new() {

                        let path = book.get_src().join(&ch.path);

                        debug!("[*]: Opening file: {:?}", path);
                        let mut f = try!(File::open(&path));
                        let mut content: String = String::new();

                        debug!("[*]: Reading file");
                        try!(f.read_to_string(&mut content));

                        // Parse for playpen links
                        if let Some(p) = path.parent() {
                            content = helpers::playpen::render_playpen(&content, p);
                        }

                        // Render markdown using the pulldown-cmark crate
                        content = utils::render_markdown(&content);
                        print_content.push_str(&content);

                        // Update the context with data for this file
                        let path = ch.path.to_str().ok_or_else(||
                            io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                        data.insert("path".to_owned(), json!(path));
                        data.insert("content".to_owned(), json!(content));
                        data.insert("chapter_title".to_owned(), json!(ch.name));
                        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(&ch.path)));

                        // Render the handlebars template with the data
                        debug!("[*]: Render template");
                        let rendered = try!(handlebars.render("index", &data));

                        // create links for headers
                        let rendered = build_header_links(rendered);

                        // Write to file
                        let filename = Path::new(&ch.path).with_extension("html");
                        info!("[*] Creating {:?} ✓", filename.display());
                        try!(book.write_file(filename, &rendered.into_bytes()));

                        // Create an index.html from the first element in SUMMARY.md
                        if index {
                            debug!("[*]: index.html");

                            let mut content = String::new();
                            let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html"))))
                                .read_to_string(&mut content);

                            // This could cause a problem when someone displays code containing <base href=...>
                            // on the front page, however this case should be very very rare...
                            content = content.lines()
                                .filter(|line| !line.contains("<base href="))
                                .collect::<Vec<&str>>()
                                .join("\n");

                            try!(book.write_file("index.html", content.as_bytes()));

                            info!("[*] Creating index.html from {:?} ✓",
                                  book.get_dest().join(&ch.path.with_extension("html")));
                            index = false;
                        }
                    }
                },
                _ => {},
            }
        }

        // Print version

        // Update the context with data for this file
        data.insert("path".to_owned(), json!("print.md"));
        data.insert("content".to_owned(), json!(print_content));
        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(Path::new("print.md"))));

        // Render the handlebars template with the data
        debug!("[*]: Render template");

        let rendered = try!(handlebars.render("index", &data));
        let rendered = build_header_links(rendered);

        try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes()));
        info!("[*] Creating print.html ✓");

        // Copy static files (js, css, images, ...)

        debug!("[*] Copy static files");
        try!(book.write_file("book.js", &theme.js));
        try!(book.write_file("book.css", &theme.css));
        try!(book.write_file("favicon.png", &theme.favicon));
        try!(book.write_file("jquery.js", &theme.jquery));
        try!(book.write_file("highlight.css", &theme.highlight_css));
        try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css));
        try!(book.write_file("highlight.js", &theme.highlight_js));
        try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2));
        try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF));

        // Copy all remaining files
        try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"]));

        Ok(())
    }
}

fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>, Box<Error>> {
    debug!("[fn]: make_data");

    let mut data = serde_json::Map::new();
    data.insert("language".to_owned(), json!("en"));
    data.insert("title".to_owned(), json!(book.get_title()));
    data.insert("description".to_owned(), json!(book.get_description()));
    data.insert("favicon".to_owned(), json!("favicon.png"));
    if let Some(livereload) = book.get_livereload() {
        data.insert("livereload".to_owned(), json!(livereload));
    }

    let mut chapters = vec![];

    for item in book.iter() {
        // Create the data to inject in the template
        let mut chapter = BTreeMap::new();

        match *item {
            BookItem::Affix(ref ch) => {
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Chapter(ref s, ref ch) => {
                chapter.insert("section".to_owned(), json!(s));
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Spacer => {
                chapter.insert("spacer".to_owned(), json!("_spacer_"));
            },

        }

        chapters.push(chapter);
    }

    data.insert("chapters".to_owned(), json!(chapters));

    debug!("[*]: JSON constructed");
    Ok(data)
}

fn build_header_links(html: String) -> String {
    let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();

    regex.replace_all(&html, |caps: &Captures| {
        let level = &caps[1];
        let text = &caps[2];
        let mut id = text.to_string();
        let repl_sub = vec!["", "", "", "",
                            "", "",
                            "<", ">", "&", "'", """];
        for sub in repl_sub {
            id = id.replace(sub, "");
        }
        let id = id.chars().filter_map(|c| {
            if c.is_alphanumeric() || c == '-' || c == '_' {
                if c.is_ascii() {
                    Some(c.to_ascii_lowercase())
                } else {
                    Some(c)
                }
            } else if c.is_whitespace() && c.is_ascii() {
                Some('-')
            } else {
                None
            }
        }).collect::<String>();

        format!("<h{level}>{text}</h{level}>", level=level, id=id, text=text)
    }).into_owned()
}
<commit_msg>Fix code blocks with comma separated classes<commit_after>use renderer::html_handlebars::helpers;
use renderer::Renderer;
use book::MDBook;
use book::bookitem::BookItem;
use {utils, theme};
use regex::{Regex, Captures};

use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::error::Error;
use std::io::{self, Read};
use std::collections::BTreeMap;

use handlebars::Handlebars;

use serde_json;

#[derive(Default)]
pub struct HtmlHandlebars;

impl HtmlHandlebars {
    pub fn new() -> Self {
        HtmlHandlebars
    }
}

impl Renderer for HtmlHandlebars {
    fn render(&self, book: &MDBook) -> Result<(), Box<Error>> {
        debug!("[fn]: render");
        let mut handlebars = Handlebars::new();

        // Load theme
        let theme = theme::Theme::new(book.get_theme_path());

        // Register template
        debug!("[*]: Register handlebars template");
        try!(handlebars.register_template_string("index", try!(String::from_utf8(theme.index))));

        // Register helpers
        debug!("[*]: Register handlebars helpers");
        handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc));
        handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
        handlebars.register_helper("next", Box::new(helpers::navigation::next));

        let mut data = try!(make_data(book));

        // Print version
        let mut print_content: String = String::new();

        // Check if dest directory exists
        debug!("[*]: Check if destination directory exists");
        if fs::create_dir_all(book.get_dest()).is_err() {
            return Err(Box::new(io::Error::new(io::ErrorKind::Other,
                                               "Unexpected error when constructing destination path")));
        }

        // Render a file for every entry in the book
        let mut index = true;
        for item in book.iter() {

            match *item {
                BookItem::Chapter(_, ref ch) |
                BookItem::Affix(ref ch) => {
                    if ch.path != PathBuf::new() {

                        let path = book.get_src().join(&ch.path);

                        debug!("[*]: Opening file: {:?}", path);
                        let mut f = try!(File::open(&path));
                        let mut content: String = String::new();

                        debug!("[*]: Reading file");
                        try!(f.read_to_string(&mut content));

                        // Parse for playpen links
                        if let Some(p) = path.parent() {
                            content = helpers::playpen::render_playpen(&content, p);
                        }

                        // Render markdown using the pulldown-cmark crate
                        content = utils::render_markdown(&content);
                        print_content.push_str(&content);

                        // Update the context with data for this file
                        let path = ch.path.to_str().ok_or_else(||
                            io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                        data.insert("path".to_owned(), json!(path));
                        data.insert("content".to_owned(), json!(content));
                        data.insert("chapter_title".to_owned(), json!(ch.name));
                        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(&ch.path)));

                        // Render the handlebars template with the data
                        debug!("[*]: Render template");
                        let rendered = try!(handlebars.render("index", &data));

                        // create links for headers
                        let rendered = build_header_links(rendered);

                        // fix code blocks
                        let rendered = fix_code_blocks(rendered);

                        // Write to file
                        let filename = Path::new(&ch.path).with_extension("html");
                        info!("[*] Creating {:?} ✓", filename.display());
                        try!(book.write_file(filename, &rendered.into_bytes()));

                        // Create an index.html from the first element in SUMMARY.md
                        if index {
                            debug!("[*]: index.html");

                            let mut content = String::new();
                            let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html"))))
                                .read_to_string(&mut content);

                            // This could cause a problem when someone displays code containing <base href=...>
                            // on the front page, however this case should be very very rare...
                            content = content.lines()
                                .filter(|line| !line.contains("<base href="))
                                .collect::<Vec<&str>>()
                                .join("\n");

                            try!(book.write_file("index.html", content.as_bytes()));

                            info!("[*] Creating index.html from {:?} ✓",
                                  book.get_dest().join(&ch.path.with_extension("html")));
                            index = false;
                        }
                    }
                },
                _ => {},
            }
        }

        // Print version

        // Update the context with data for this file
        data.insert("path".to_owned(), json!("print.md"));
        data.insert("content".to_owned(), json!(print_content));
        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(Path::new("print.md"))));

        // Render the handlebars template with the data
        debug!("[*]: Render template");

        let rendered = try!(handlebars.render("index", &data));
        let rendered = build_header_links(rendered);

        // fix code blocks
        let rendered = fix_code_blocks(rendered);

        try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes()));
        info!("[*] Creating print.html ✓");

        // Copy static files (js, css, images, ...)

        debug!("[*] Copy static files");
        try!(book.write_file("book.js", &theme.js));
        try!(book.write_file("book.css", &theme.css));
        try!(book.write_file("favicon.png", &theme.favicon));
        try!(book.write_file("jquery.js", &theme.jquery));
        try!(book.write_file("highlight.css", &theme.highlight_css));
        try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css));
        try!(book.write_file("highlight.js", &theme.highlight_js));
        try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2));
        try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF));

        // Copy all remaining files
        try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"]));

        Ok(())
    }
}

fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>, Box<Error>> {
    debug!("[fn]: make_data");

    let mut data = serde_json::Map::new();
    data.insert("language".to_owned(), json!("en"));
    data.insert("title".to_owned(), json!(book.get_title()));
    data.insert("description".to_owned(), json!(book.get_description()));
    data.insert("favicon".to_owned(), json!("favicon.png"));
    if let Some(livereload) = book.get_livereload() {
        data.insert("livereload".to_owned(), json!(livereload));
    }

    let mut chapters = vec![];

    for item in book.iter() {
        // Create the data to inject in the template
        let mut chapter = BTreeMap::new();

        match *item {
            BookItem::Affix(ref ch) => {
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Chapter(ref s, ref ch) => {
                chapter.insert("section".to_owned(), json!(s));
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Spacer => {
                chapter.insert("spacer".to_owned(), json!("_spacer_"));
            },

        }

        chapters.push(chapter);
    }

    data.insert("chapters".to_owned(), json!(chapters));

    debug!("[*]: JSON constructed");
    Ok(data)
}

fn build_header_links(html: String) -> String {
    let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();

    regex.replace_all(&html, |caps: &Captures| {
        let level = &caps[1];
        let text = &caps[2];
        let mut id = text.to_string();
        let repl_sub = vec!["", "", "", "",
                            "", "",
                            "<", ">", "&", "'", """];
        for sub in repl_sub {
            id = id.replace(sub, "");
        }
        let id = id.chars().filter_map(|c| {
            if c.is_alphanumeric() || c == '-' || c == '_' {
                if c.is_ascii() {
                    Some(c.to_ascii_lowercase())
                } else {
                    Some(c)
                }
            } else if c.is_whitespace() && c.is_ascii() {
                Some('-')
            } else {
                None
            }
        }).collect::<String>();

        format!("<h{level}>{text}</h{level}>", level=level, id=id, text=text)
    }).into_owned()
}

// The rust book uses annotations for rustdoc to test code snippets, like the following:
// ```rust,should_panic
// fn main() {
//     // Code here
// }
// ```
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: String) -> String {
    let regex = Regex::new(r##"]+)class="([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let classes = &caps[2].replace(",", " ");
        let after = &caps[3];

        format!("", before=before, classes=classes, after=after)
    }).into_owned()
}
<|endoftext|>