<commit_before>//! The `conf` module contains functions for loading and saving game
//! configurations.
//!
//! A [`Conf`](struct.Conf.html) struct is used to create a config file
//! which specifies hardware setup stuff, mostly video display settings.
//!
//! By default a ggez game will search its resource paths for a `/conf.toml`
//! file and load values from it when the [`Context`](../struct.Context.html) is created.  This file
//! must be complete (ie you cannot just fill in some fields and have the
//! rest be default) and provides a nice way to specify settings that
//! can be tweaked such as window resolution, multisampling options, etc.
//! If no file is found, it will create a `Conf` object from the settings
//! passed to the [`ContextBuilder`](../struct.ContextBuilder.html).

use std::io;

use crate::error::GameResult;

/// Possible fullscreen modes.
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum FullscreenType {
    /// Windowed mode.
    Windowed,
    /// True fullscreen, which used to be preferred 'cause it can have
    /// small performance benefits over windowed fullscreen.
    True,
    /// Windowed fullscreen, generally preferred over real fullscreen
    /// these days 'cause it plays nicer with multiple monitors.
    Desktop,
}

/// A builder structure containing window settings
/// that can be set at runtime and changed with [`graphics::set_mode()`](../graphics/fn.set_mode.html).
///
/// Defaults:
///
/// ```rust
/// # use ggez::conf::*;
/// # fn main() { assert_eq!(
/// WindowMode {
///     width: 800.0,
///     height: 600.0,
///     maximized: false,
///     fullscreen_type: FullscreenType::Windowed,
///     borderless: false,
///     min_width: 0.0,
///     max_width: 0.0,
///     min_height: 0.0,
///     max_height: 0.0,
///     resizable: false,
///     visible: true,
/// }
/// # , WindowMode::default());}
/// ```
#[derive(Debug, Copy, Clone, SmartDefault, Serialize, Deserialize, PartialEq)]
pub struct WindowMode {
    /// Window width
    #[default = 800.0]
    pub width: f32,
    /// Window height
    #[default = 600.0]
    pub height: f32,
    /// Whether or not to maximize the window
    #[default = false]
    pub maximized: bool,
    /// Fullscreen type
    #[default(FullscreenType::Windowed)]
    pub fullscreen_type: FullscreenType,
    /// Whether or not to show window decorations
    #[default = false]
    pub borderless: bool,
    /// Minimum width for resizable windows; 0 means no limit
    #[default = 0.0]
    pub min_width: f32,
    /// Minimum height for resizable windows; 0 means no limit
    #[default = 0.0]
    pub min_height: f32,
    /// Maximum width for resizable windows; 0 means no limit
    #[default = 0.0]
    pub max_width: f32,
    /// Maximum height for resizable windows; 0 means no limit
    #[default = 0.0]
    pub max_height: f32,
    /// Whether or not the window is resizable
    #[default = false]
    pub resizable: bool,
    /// Whether this window should displayed (true) or hidden (false)
    #[default = true]
    pub visible: bool,
}

impl WindowMode {
    /// Set default window size, or screen resolution in true fullscreen mode.
    pub fn dimensions(mut self, width: f32, height: f32) -> Self {
        self.width = width;
        self.height = height;
        self
    }

    /// Set whether the window should be maximized.
    pub fn maximized(mut self, maximized: bool) -> Self {
        self.maximized = maximized;
        self
    }

    /// Set the fullscreen type.
    pub fn fullscreen_type(mut self, fullscreen_type: FullscreenType) -> Self {
        self.fullscreen_type = fullscreen_type;
        self
    }

    /// Set whether a window should be borderless in windowed mode.
    pub fn borderless(mut self, borderless: bool) -> Self {
        self.borderless = borderless;
        self
    }

    /// Set minimum window dimensions for windowed mode.
    pub fn min_dimensions(mut self, width: f32, height: f32) -> Self {
        self.min_width = width;
        self.min_height = height;
        self
    }

    /// Set maximum window dimensions for windowed mode.
    pub fn max_dimensions(mut self, width: f32, height: f32) -> Self {
        self.max_width = width;
        self.max_height = height;
        self
    }

    /// Set resizable.
    pub fn resizable(mut self, resizable: bool) -> Self {
        self.resizable = resizable;
        self
    }

    /// Set visibility
    pub fn visible(mut self, visible: bool) -> Self {
        self.visible = visible;
        self
    }
}

/// A builder structure containing window settings
/// that must be set at init time and cannot be changed afterwards.
///
/// Defaults:
///
/// ```rust
/// # use ggez::conf::*;
/// # fn main() { assert_eq!(
/// WindowSetup {
///     title: "An easy, good game".to_owned(),
///     samples: NumSamples::Zero,
///     vsync: true,
///     icon: "".to_owned(),
///     srgb: true,
/// }
/// # , WindowSetup::default()); }
/// ```
#[derive(Debug, Clone, SmartDefault, Serialize, Deserialize, PartialEq)]
pub struct WindowSetup {
    /// The window title.
    #[default(String::from("An easy, good game"))]
    pub title: String,
    /// Number of samples to use for multisample anti-aliasing.
    #[default(NumSamples::Zero)]
    pub samples: NumSamples,
    /// Whether or not to enable vsync.
    #[default = true]
    pub vsync: bool,
    /// A file path to the window's icon.
    /// It takes a path rooted in the `resources` directory (see the [`filesystem`](../filesystem/index.html)
    /// module for details), and an empty string results in a blank/default icon.
    #[default(String::new())]
    pub icon: String,
    /// Whether or not to enable sRGB (gamma corrected color)
    /// handling on the display.
    #[default = true]
    pub srgb: bool,
}

impl WindowSetup {
    /// Set window title.
    pub fn title(mut self, title: &str) -> Self {
        self.title = title.to_owned();
        self
    }

    /// Set number of samples to use for multisample anti-aliasing.
    pub fn samples(mut self, samples: NumSamples) -> Self {
        self.samples = samples;
        self
    }

    /// Set whether vsync is enabled.
    pub fn vsync(mut self, vsync: bool) -> Self {
        self.vsync = vsync;
        self
    }

    /// Set the window's icon.
    pub fn icon(mut self, icon: &str) -> Self {
        self.icon = icon.to_owned();
        self
    }

    /// Set sRGB color mode.
    pub fn srgb(mut self, active: bool) -> Self {
        self.srgb = active;
        self
    }
}

/// Possible backends.
/// Currently, only OpenGL and OpenGL ES Core specs are supported,
/// but this lets you specify which to use as well as the version numbers.
///
/// Defaults:
///
/// ```rust
/// # use ggez::conf::*;
/// # fn main() { assert_eq!(
/// Backend::OpenGL {
///     major: 3,
///     minor: 2,
/// }
/// # , Backend::default()); }
/// ```
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, SmartDefault)]
#[serde(tag = "type")]
pub enum Backend {
    /// Defaults to OpenGL 3.2, which is supported by basically
    /// every machine since 2009 or so (apart from the ones that don't).
    #[default]
    OpenGL {
        /// OpenGL major version
        #[default = 3]
        major: u8,
        /// OpenGL minor version
        #[default = 2]
        minor: u8,
    },
    /// OpenGL ES, defaults to 3.0.  Used for phones and other mobile
    /// devices.  Using something older
    /// than 3.0 starts to running into sticky limitations, particularly
    /// with instanced drawing (used for `SpriteBatch`), but might be
    /// possible.
    OpenGLES {
        /// OpenGL ES major version
        #[default = 3]
        major: u8,
        /// OpenGL ES minor version
        #[default = 0]
        minor: u8,
    },
}

impl Backend {
    /// Set requested OpenGL/OpenGL ES version.
    pub fn version(self, new_major: u8, new_minor: u8) -> Self {
        match self {
            Backend::OpenGL { .. } => Backend::OpenGL {
                major: new_major,
                minor: new_minor,
            },
            Backend::OpenGLES { .. } => Backend::OpenGLES {
                major: new_major,
                minor: new_minor,
            },
        }
    }

    /// Use OpenGL
    pub fn gl(self) -> Self {
        match self {
            Backend::OpenGLES { major, minor } => Backend::OpenGL { major, minor },
            gl => gl,
        }
    }

    /// Use OpenGL ES
    pub fn gles(self) -> Self {
        match self {
            Backend::OpenGL { major, minor } => Backend::OpenGLES { major, minor },
            es => es,
        }
    }
}

/// The possible number of samples for multisample anti-aliasing.
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum NumSamples {
    /// Multisampling disabled.
    Zero = 0,
    /// One sample
    One = 1,
    /// Two samples
    Two = 2,
    /// Four samples
    Four = 4,
    /// Eight samples
    Eight = 8,
    /// Sixteen samples
    Sixteen = 16,
}

impl NumSamples {
    /// Create a `NumSamples` from a number.
    /// Returns `None` if `i` is invalid.
    pub fn from_u32(i: u32) -> Option<NumSamples> {
        match i {
            0 => Some(NumSamples::Zero),
            1 => Some(NumSamples::One),
            2 => Some(NumSamples::Two),
            4 => Some(NumSamples::Four),
            8 => Some(NumSamples::Eight),
            16 => Some(NumSamples::Sixteen),
            _ => None,
        }
    }
}

/// Defines which submodules to enable in ggez.
/// If one tries to use a submodule that is not enabled,
/// it will panic.  Currently, not all subsystems can be
/// disabled.
///
/// Defaults:
///
/// ```rust
/// # use ggez::conf::*;
/// # fn main() { assert_eq!(
/// ModuleConf {
///     gamepad: true,
///     audio: true,
/// }
/// # , ModuleConf::default()); }
/// ```
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, SmartDefault)]
pub struct ModuleConf {
    /// The gamepad input module.
    #[default = true]
    pub gamepad: bool,

    /// The audio module.
    #[default = true]
    pub audio: bool,
}

impl ModuleConf {
    /// Sets whether or not to enable the gamepad input module.
    pub fn gamepad(mut self, gamepad: bool) -> Self {
        self.gamepad = gamepad;
        self
    }

    /// Sets whether or not to enable the audio module.
    pub fn audio(mut self, audio: bool) -> Self {
        self.audio = audio;
        self
    }
}

/// A structure containing configuration data
/// for the game engine.
///
/// Defaults:
///
/// ```rust
/// # use ggez::conf::*;
/// # fn main() { assert_eq!(
/// Conf {
///     window_mode: WindowMode::default(),
///     window_setup: WindowSetup::default(),
///     backend: Backend::default(),
///     modules: ModuleConf::default(),
/// }
/// # , Conf::default()); }
/// ```
#[derive(Serialize, Deserialize, Debug, PartialEq, SmartDefault, Clone)]
pub struct Conf {
    /// Window setting information that can be set at runtime
    pub window_mode: WindowMode,
    /// Window setting information that must be set at init-time
    pub window_setup: WindowSetup,
    /// Graphics backend configuration
    pub backend: Backend,
    /// Which modules to enable.
    pub modules: ModuleConf,
}

impl Conf {
    /// Same as `Conf::default()`
    pub fn new() -> Self {
        Self::default()
    }

    /// Load a TOML file from the given `Read` and attempts to parse
    /// a `Conf` from it.
    pub fn from_toml_file<R: io::Read>(file: &mut R) -> GameResult<Conf> {
        let mut s = String::new();
        let _ = file.read_to_string(&mut s)?;
        let decoded = toml::from_str(&s)?;
        Ok(decoded)
    }

    /// Saves the `Conf` to the given `Write` object,
    /// formatted as TOML.
    pub fn to_toml_file<W: io::Write>(&self, file: &mut W) -> GameResult {
        let s = toml::to_vec(self)?;
        file.write_all(&s)?;
        Ok(())
    }

    /// Sets the window mode
    pub fn window_mode(mut self, window_mode: WindowMode) -> Self {
        self.window_mode = window_mode;
        self
    }

    /// Sets the backend
    pub fn backend(mut self, backend: Backend) -> Self {
        self.backend = backend;
        self
    }

    /// Sets the backend
    pub fn modules(mut self, modules: ModuleConf) -> Self {
        self.modules = modules;
        self
    }
}

#[cfg(test)]
mod tests {
    use crate::conf;

    /// Tries to encode and decode a `Conf` object
    /// and makes sure it gets the same result it had.
    #[test]
    fn headless_encode_round_trip() {
        let c1 = conf::Conf::new();
        let mut writer = Vec::new();
        let _c = c1.to_toml_file(&mut writer).unwrap();
        let mut reader = writer.as_slice();
        let c2 = conf::Conf::from_toml_file(&mut reader).unwrap();
        assert_eq!(c1, c2);
    }
}
<commit_msg>modified the docs a little to point out that logical and physical window sizes can differ;<commit_after>//! The `conf` module contains functions for loading and saving game
//! configurations.
//!
//! A [`Conf`](struct.Conf.html) struct is used to create a config file
//! which specifies hardware setup stuff, mostly video display settings.
//!
//! By default a ggez game will search its resource paths for a `/conf.toml`
//! file and load values from it when the [`Context`](../struct.Context.html) is created.  This file
//! must be complete (ie you cannot just fill in some fields and have the
//! rest be default) and provides a nice way to specify settings that
//! can be tweaked such as window resolution, multisampling options, etc.
//! If no file is found, it will create a `Conf` object from the settings
//! passed to the [`ContextBuilder`](../struct.ContextBuilder.html).

use std::io;

use crate::error::GameResult;

/// Possible fullscreen modes.
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum FullscreenType {
    /// Windowed mode.
    Windowed,
    /// True fullscreen, which used to be preferred 'cause it can have
    /// small performance benefits over windowed fullscreen.
    True,
    /// Windowed fullscreen, generally preferred over real fullscreen
    /// these days 'cause it plays nicer with multiple monitors.
    Desktop,
}

/// A builder structure containing window settings
/// that can be set at runtime and changed with [`graphics::set_mode()`](../graphics/fn.set_mode.html).
///
/// Defaults:
///
/// ```rust
/// # use ggez::conf::*;
/// # fn main() { assert_eq!(
/// WindowMode {
///     width: 800.0,
///     height: 600.0,
///     maximized: false,
///     fullscreen_type: FullscreenType::Windowed,
///     borderless: false,
///     min_width: 0.0,
///     max_width: 0.0,
///     min_height: 0.0,
///     max_height: 0.0,
///     resizable: false,
///     visible: true,
/// }
/// # , WindowMode::default());}
/// ```
#[derive(Debug, Copy, Clone, SmartDefault, Serialize, Deserialize, PartialEq)]
pub struct WindowMode {
    /// Logical window width
    #[default = 800.0]
    pub width: f32,
    /// Logical window height
    #[default = 600.0]
    pub height: f32,
    /// Whether or not to maximize the window
    #[default = false]
    pub maximized: bool,
    /// Fullscreen type
    #[default(FullscreenType::Windowed)]
    pub fullscreen_type: FullscreenType,
    /// Whether or not to show window decorations
    #[default = false]
    pub borderless: bool,
    /// Minimum width for resizable windows; 0 means no limit
    #[default = 0.0]
    pub min_width: f32,
    /// Minimum height for resizable windows; 0 means no limit
    #[default = 0.0]
    pub min_height: f32,
    /// Maximum width for resizable windows; 0 means no limit
    #[default = 0.0]
    pub max_width: f32,
    /// Maximum height for resizable windows; 0 means no limit
    #[default = 0.0]
    pub max_height: f32,
    /// Whether or not the window is resizable
    #[default = false]
    pub resizable: bool,
    /// Whether this window should displayed (true) or hidden (false)
    #[default = true]
    pub visible: bool,
}

impl WindowMode {
    /// Set default window size, or screen resolution in true fullscreen mode.
    /// Note that the physical window dimensions also depend on the
    /// [`scale_factor`](https://docs.rs/winit/0.24.0/winit/window/struct.Window.html#method.scale_factor) of the window.
    pub fn dimensions(mut self, width: f32, height: f32) -> Self {
        self.width = width;
        self.height = height;
        self
    }

    /// Set whether the window should be maximized.
    pub fn maximized(mut self, maximized: bool) -> Self {
        self.maximized = maximized;
        self
    }

    /// Set the fullscreen type.
    pub fn fullscreen_type(mut self, fullscreen_type: FullscreenType) -> Self {
        self.fullscreen_type = fullscreen_type;
        self
    }

    /// Set whether a window should be borderless in windowed mode.
    pub fn borderless(mut self, borderless: bool) -> Self {
        self.borderless = borderless;
        self
    }

    /// Set minimum window dimensions for windowed mode.
    pub fn min_dimensions(mut self, width: f32, height: f32) -> Self {
        self.min_width = width;
        self.min_height = height;
        self
    }

    /// Set maximum window dimensions for windowed mode.
    pub fn max_dimensions(mut self, width: f32, height: f32) -> Self {
        self.max_width = width;
        self.max_height = height;
        self
    }

    /// Set resizable.
    pub fn resizable(mut self, resizable: bool) -> Self {
        self.resizable = resizable;
        self
    }

    /// Set visibility
    pub fn visible(mut self, visible: bool) -> Self {
        self.visible = visible;
        self
    }
}

/// A builder structure containing window settings
/// that must be set at init time and cannot be changed afterwards.
///
/// Defaults:
///
/// ```rust
/// # use ggez::conf::*;
/// # fn main() { assert_eq!(
/// WindowSetup {
///     title: "An easy, good game".to_owned(),
///     samples: NumSamples::Zero,
///     vsync: true,
///     icon: "".to_owned(),
///     srgb: true,
/// }
/// # , WindowSetup::default()); }
/// ```
#[derive(Debug, Clone, SmartDefault, Serialize, Deserialize, PartialEq)]
pub struct WindowSetup {
    /// The window title.
    #[default(String::from("An easy, good game"))]
    pub title: String,
    /// Number of samples to use for multisample anti-aliasing.
    #[default(NumSamples::Zero)]
    pub samples: NumSamples,
    /// Whether or not to enable vsync.
    #[default = true]
    pub vsync: bool,
    /// A file path to the window's icon.
    /// It takes a path rooted in the `resources` directory (see the [`filesystem`](../filesystem/index.html)
    /// module for details), and an empty string results in a blank/default icon.
    #[default(String::new())]
    pub icon: String,
    /// Whether or not to enable sRGB (gamma corrected color)
    /// handling on the display.
    #[default = true]
    pub srgb: bool,
}

impl WindowSetup {
    /// Set window title.
    pub fn title(mut self, title: &str) -> Self {
        self.title = title.to_owned();
        self
    }

    /// Set number of samples to use for multisample anti-aliasing.
    pub fn samples(mut self, samples: NumSamples) -> Self {
        self.samples = samples;
        self
    }

    /// Set whether vsync is enabled.
    pub fn vsync(mut self, vsync: bool) -> Self {
        self.vsync = vsync;
        self
    }

    /// Set the window's icon.
    pub fn icon(mut self, icon: &str) -> Self {
        self.icon = icon.to_owned();
        self
    }

    /// Set sRGB color mode.
    pub fn srgb(mut self, active: bool) -> Self {
        self.srgb = active;
        self
    }
}

/// Possible backends.
/// Currently, only OpenGL and OpenGL ES Core specs are supported,
/// but this lets you specify which to use as well as the version numbers.
///
/// Defaults:
///
/// ```rust
/// # use ggez::conf::*;
/// # fn main() { assert_eq!(
/// Backend::OpenGL {
///     major: 3,
///     minor: 2,
/// }
/// # , Backend::default()); }
/// ```
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, SmartDefault)]
#[serde(tag = "type")]
pub enum Backend {
    /// Defaults to OpenGL 3.2, which is supported by basically
    /// every machine since 2009 or so (apart from the ones that don't).
    #[default]
    OpenGL {
        /// OpenGL major version
        #[default = 3]
        major: u8,
        /// OpenGL minor version
        #[default = 2]
        minor: u8,
    },
    /// OpenGL ES, defaults to 3.0.  Used for phones and other mobile
    /// devices.  Using something older
    /// than 3.0 starts to running into sticky limitations, particularly
    /// with instanced drawing (used for `SpriteBatch`), but might be
    /// possible.
    OpenGLES {
        /// OpenGL ES major version
        #[default = 3]
        major: u8,
        /// OpenGL ES minor version
        #[default = 0]
        minor: u8,
    },
}

impl Backend {
    /// Set requested OpenGL/OpenGL ES version.
    pub fn version(self, new_major: u8, new_minor: u8) -> Self {
        match self {
            Backend::OpenGL { .. } => Backend::OpenGL {
                major: new_major,
                minor: new_minor,
            },
            Backend::OpenGLES { .. } => Backend::OpenGLES {
                major: new_major,
                minor: new_minor,
            },
        }
    }

    /// Use OpenGL
    pub fn gl(self) -> Self {
        match self {
            Backend::OpenGLES { major, minor } => Backend::OpenGL { major, minor },
            gl => gl,
        }
    }

    /// Use OpenGL ES
    pub fn gles(self) -> Self {
        match self {
            Backend::OpenGL { major, minor } => Backend::OpenGLES { major, minor },
            es => es,
        }
    }
}

/// The possible number of samples for multisample anti-aliasing.
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum NumSamples {
    /// Multisampling disabled.
    Zero = 0,
    /// One sample
    One = 1,
    /// Two samples
    Two = 2,
    /// Four samples
    Four = 4,
    /// Eight samples
    Eight = 8,
    /// Sixteen samples
    Sixteen = 16,
}

impl NumSamples {
    /// Create a `NumSamples` from a number.
    /// Returns `None` if `i` is invalid.
    pub fn from_u32(i: u32) -> Option<NumSamples> {
        match i {
            0 => Some(NumSamples::Zero),
            1 => Some(NumSamples::One),
            2 => Some(NumSamples::Two),
            4 => Some(NumSamples::Four),
            8 => Some(NumSamples::Eight),
            16 => Some(NumSamples::Sixteen),
            _ => None,
        }
    }
}

/// Defines which submodules to enable in ggez.
/// If one tries to use a submodule that is not enabled,
/// it will panic.  Currently, not all subsystems can be
/// disabled.
///
/// Defaults:
///
/// ```rust
/// # use ggez::conf::*;
/// # fn main() { assert_eq!(
/// ModuleConf {
///     gamepad: true,
///     audio: true,
/// }
/// # , ModuleConf::default()); }
/// ```
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, SmartDefault)]
pub struct ModuleConf {
    /// The gamepad input module.
    #[default = true]
    pub gamepad: bool,

    /// The audio module.
    #[default = true]
    pub audio: bool,
}

impl ModuleConf {
    /// Sets whether or not to enable the gamepad input module.
    pub fn gamepad(mut self, gamepad: bool) -> Self {
        self.gamepad = gamepad;
        self
    }

    /// Sets whether or not to enable the audio module.
    pub fn audio(mut self, audio: bool) -> Self {
        self.audio = audio;
        self
    }
}

/// A structure containing configuration data
/// for the game engine.
///
/// Defaults:
///
/// ```rust
/// # use ggez::conf::*;
/// # fn main() { assert_eq!(
/// Conf {
///     window_mode: WindowMode::default(),
///     window_setup: WindowSetup::default(),
///     backend: Backend::default(),
///     modules: ModuleConf::default(),
/// }
/// # , Conf::default()); }
/// ```
#[derive(Serialize, Deserialize, Debug, PartialEq, SmartDefault, Clone)]
pub struct Conf {
    /// Window setting information that can be set at runtime
    pub window_mode: WindowMode,
    /// Window setting information that must be set at init-time
    pub window_setup: WindowSetup,
    /// Graphics backend configuration
    pub backend: Backend,
    /// Which modules to enable.
    pub modules: ModuleConf,
}

impl Conf {
    /// Same as `Conf::default()`
    pub fn new() -> Self {
        Self::default()
    }

    /// Load a TOML file from the given `Read` and attempts to parse
    /// a `Conf` from it.
    pub fn from_toml_file<R: io::Read>(file: &mut R) -> GameResult<Conf> {
        let mut s = String::new();
        let _ = file.read_to_string(&mut s)?;
        let decoded = toml::from_str(&s)?;
        Ok(decoded)
    }

    /// Saves the `Conf` to the given `Write` object,
    /// formatted as TOML.
    pub fn to_toml_file<W: io::Write>(&self, file: &mut W) -> GameResult {
        let s = toml::to_vec(self)?;
        file.write_all(&s)?;
        Ok(())
    }

    /// Sets the window mode
    pub fn window_mode(mut self, window_mode: WindowMode) -> Self {
        self.window_mode = window_mode;
        self
    }

    /// Sets the backend
    pub fn backend(mut self, backend: Backend) -> Self {
        self.backend = backend;
        self
    }

    /// Sets the backend
    pub fn modules(mut self, modules: ModuleConf) -> Self {
        self.modules = modules;
        self
    }
}

#[cfg(test)]
mod tests {
    use crate::conf;

    /// Tries to encode and decode a `Conf` object
    /// and makes sure it gets the same result it had.
    #[test]
    fn headless_encode_round_trip() {
        let c1 = conf::Conf::new();
        let mut writer = Vec::new();
        let _c = c1.to_toml_file(&mut writer).unwrap();
        let mut reader = writer.as_slice();
        let c2 = conf::Conf::from_toml_file(&mut reader).unwrap();
        assert_eq!(c1, c2);
    }
}
<|endoftext|>


<commit_before>use std::io::Write;
use std::io::Read;
use std::fmt;
use std::cmp;
use std::io;

const DEFAULT_NUMBER_OF_SAMPLES:usize = 31;
const DEFAULT_NUMBER_OF_ROWS_PER_PATTERN:usize = 64;
const DEFAULT_NUMBER_OF_CHANNELS_PER_ROW:usize = 4;
const MAGIC_MK:[u8; 4] =['M' as u8, '.' as u8, 'K' as u8, '.' as u8];

#[derive(Debug)]
pub enum PTMFError {
	Io(io::Error),
	Parse(String)
}

impl From<io::Error> for PTMFError {
    fn from(err: io::Error) -> PTMFError {
        PTMFError::Io(err)
    }
}

#[derive(Debug)]
pub struct SampleInfo {
	pub name: String, // [char; 22],
	pub length: u16, // In words, so multiply by 2
	pub finetune: u8,
	pub volume: u8, // 0-64
	pub repeat_start: u16, // In words from start of sample data, so multiply by 2
	pub repeat_length: u16, // Number of words to loop, multiply by 2
}

impl SampleInfo {
	pub fn new() -> SampleInfo {
		SampleInfo{name: String::new(), length:0, finetune:0, volume:0, repeat_start:0, repeat_length:0}
	}
}

#[derive(Debug)]
pub struct Channel {
	pub period: u16, // Really u12
	pub sample_number: u8,
	pub effect: u16 // Really u12
}

impl Channel {
	pub fn new() -> Channel {
		Channel{period: 0, sample_number: 0, effect: 0}
	}
}

#[derive(Debug)]
pub struct Row {
	pub channels: Vec<Channel>
}

impl Row {
	pub fn new() -> Row {
		let mut row = Row{channels: Vec::new()};
		for _ in 0..DEFAULT_NUMBER_OF_CHANNELS_PER_ROW {
			row.channels.push(Channel::new());
		}

		row
	}
}

#[derive(Debug)]
pub struct Pattern {
	pub rows: Vec<Row>
}

impl Pattern {
	pub fn new() -> Pattern {
		let mut p = Pattern{rows: Vec::new()};

		for _ in 0..DEFAULT_NUMBER_OF_ROWS_PER_PATTERN {
			p.rows.push(Row::new());
		}

		p
	}
}

pub struct Positions {
	pub data: [u8; 128]
}

impl fmt::Debug for Positions {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        self.data[..].fmt(formatter)
    }
}

#[derive(Debug)]
pub struct PTModule {
	pub name: String, // [char; 20],
	pub sample_info: Vec<SampleInfo>, // [SampleInfo; DEFAULT_NUMBER_OF_SAMPLES]
	pub length: u8, // Length 1-128
	pub nt_restart: u8, // Set to 127
	pub positions: Positions, // The order in which to play patterns
	pub mk: [u8; 4], // Set to M.K.
	pub patterns: Vec<Pattern>,
	pub sample_data: Vec<Vec<u8>> // The bytes in the samples -127 - 127
}

impl PTModule {
	pub fn new() -> PTModule {
		let mut ptmod = PTModule{name: String::new(), sample_info: Vec::new(), length:0,
			nt_restart: 127, positions: Positions{data: [0; 128]}, mk: MAGIC_MK,
			patterns: Vec::new(), sample_data: Vec::new() };

		for _ in 0..DEFAULT_NUMBER_OF_SAMPLES {
			ptmod.sample_info.push(SampleInfo::new());
		}

		ptmod
	}
}

fn read_all(reader: &mut Read, data: &mut [u8]) -> io::Result<usize> {
	let mut pos = 0;
	while pos < data.len() {
		let slice = &mut data[pos..];
		let n = try!(reader.read(slice));
		pos += n;
	}

	Ok(pos)
}

fn write_all(writer: &mut Write, data: &[u8]) -> io::Result<()> {
	let n = try!{writer.write_all(&data)};

	Ok(n)
}

fn read_0_padded_string(reader: &mut Read, len: usize) -> Result<String, io::Error> {
	let mut data = vec![0u8; len];
	try!(read_all(reader,&mut data));
	let mut str = String::new();
	for byte in data {
		if byte != 0 {
			str.push(byte as char);
		}
	}

	Ok(str)
}

fn write_0_padded_string(writer: &mut Write, str: &String, len: usize) -> io::Result<()> {
	let mut data = vec![0u8; len];
	let str_buf = str.as_bytes();
	let m = cmp::min(data.len(),str_buf.len());
	for i in 0..m {
		data[i] = str_buf[i];
	}

	let n = try!(write_all(writer, &data));

	Ok(n)
}

fn read_big_endian_u16(reader: &mut Read) -> Result<u16, io::Error> {
	let mut data_arr = [0u8; 2];
	try!(read_all(reader,&mut data_arr));

	let mut data:u16 = 0;

	for n in data_arr.iter() {
		data = (data << 8) + *n as u16;
	}

	Ok(data)
}

fn write_big_endian_u16(writer: &mut Write, val: u16)  -> io::Result<()> {
	let mut data = [0u8; 2];
	data[0] = (val >> 8) as u8;
	data[1] = (val & 0xff) as u8;

	let n = try!(write_all(writer, &data));

	Ok(n)
}

fn read_u8(reader: &mut Read) -> Result<u8, io::Error> {
	let mut data = [0u8; 1];

	try!(read_all(reader, &mut data));

	Ok(data[0])
}

fn write_u8(writer: &mut Write, val: u8)  -> io::Result<()> {
	let mut data = [0u8; 1];
	data[0] = val;

	let n = try!(write_all(writer, &data));

	Ok(n)
}

/// Write a 31 sample Amiga ProTracker mod-file
pub fn write_mod(writer: &mut Write, module: &mut PTModule) -> Result<(),PTMFError> {

	// First write songname, 20 bytes, pad with 0
	try!(write_0_padded_string(writer,&module.name,20));
	// Then write all 32 samples
	// TODO Handle the case when the vector has less than 31 samples
	let mut num_samples = 0;
	for i in 0..DEFAULT_NUMBER_OF_SAMPLES {
		let ref si = module.sample_info[i];
		// Sample name
		try!(write_0_padded_string(writer, &si.name, 22));
		// Sample length
		try!(write_big_endian_u16(writer, si.length));
		if si.length > 0 {
			num_samples += 1;
		}
		// Finetune
		try!(write_u8(writer, si.finetune));
		// Volume
		try!(write_u8(writer, si.volume));
		// Repeat start
		try!(write_big_endian_u16(writer, si.repeat_start));
		// Repeat length
		try!(write_big_endian_u16(writer, si.repeat_length));
	}

	let num_samples = num_samples;
	// Songlength
	try!(write_u8(writer, module.length));
	// nt_restart
	try!(write_u8(writer, module.nt_restart));
	// Song positions
	try!(write_all(writer,&module.positions.data));
	// M.K.
	try!(write_all(writer,&module.mk));

	// All patterns
	for pattern_it in module.patterns.iter() {
		// All rows
		for row_it in pattern_it.rows.iter() {
			// All channels
			for channel_it in row_it.channels.iter() {
				let mut data = [0u8; 4];

				data[0] = (channel_it.sample_number & 0xf0) | ((channel_it.period & 0xf00) >> 8) as u8;
				data[1] = (channel_it.period & 0xff) as u8;
				data[2] = ((channel_it.sample_number & 0x0f) << 4) | ((channel_it.effect & 0xf00) >> 8) as u8;
				data[3] = (channel_it.effect & 0xff) as u8;

				try!(write_all(writer,&data));
			}
		}
	}

	// And finally all samples
	for sample_it in module.sample_data.iter() {
		try!(write_all(writer,sample_it));
	}

	if num_samples != module.sample_data.len() {
		return Err(PTMFError::Parse(format!("Warning! Number of samples '{}' does not match sample data '{}'", num_samples, module.sample_data.len())));
	}

	Ok(())
}

/// Read a 31 sample Amiga ProTracker mod-file
pub fn read_mod(reader: &mut Read) -> Result<PTModule, PTMFError> {
	let mut module = PTModule::new();

	// First read 20 bytes songname
	module.name = try!(read_0_padded_string(reader, 20));
	// Read all sample info
	// TODO Handle 15 sample files
	for i in 0..DEFAULT_NUMBER_OF_SAMPLES {
		let si = &mut module.sample_info[i];
		// Sample name
		si.name = try!(read_0_padded_string(reader, 22));
		si.length = try!(read_big_endian_u16(reader));
		// Finetune
		si.finetune = try!(read_u8(reader));
		// Volume
		si.volume = try!(read_u8(reader));
		// Repeat start
		si.repeat_start = try!(read_big_endian_u16(reader));
		// Repeat length
		si.repeat_length = try!(read_big_endian_u16(reader));
	}

	// Songlength
	module.length = try!(read_u8(reader));
	// nt_restart
	module.nt_restart = try!(read_u8(reader));
	// Song positions
	try!(read_all(reader,&mut module.positions.data));
	// M.K.
	try!(read_all(reader,&mut module.mk));
	if module.mk != MAGIC_MK {
		return Err(PTMFError::Parse(format!("Unknown format {:?}", module.mk)));
	}

	// Read all patterns
	let mut num_patterns = 0;
	for n in module.positions.data.iter() {
		num_patterns = cmp::max(num_patterns,*n);
	}
	num_patterns += 1;
	for _ in 0..num_patterns {
		let mut pattern = Pattern::new();
		for mut row in &mut pattern.rows {
			for channel in &mut row.channels {
				let mut data  = [0u8;4];
				try!(read_all(reader,&mut data));
				channel.sample_number = (data[0] & 0xf0) | ((data[2] & 0xf0) >> 4);
				channel.period = (((data[0] & 0x0f) as u16) << 8) | (data[1] as u16);
				channel.effect = (((data[2] & 0x0f) as u16) << 8) | (data[3] as u16);
			}
		}
		module.patterns.push(pattern);
	}

	// Read all samples
	for si in &module.sample_info {
		if si.length > 0 {
			let length_in_bytes = si.length * 2;
			let mut data = vec![0u8; length_in_bytes as usize];
			try!(read_all(reader,&mut data));
			module.sample_data.push(data);
		}
	}

	// Now we have read all data.
	// Sanity check that the reader is empty
	let mut data = [0u8; 1];
	match reader.read(&mut data) {
		Ok(n) if n == data.len() => return Err(PTMFError::Parse(format!("Unread data left in file"))),
		_ => ()
	};

	Ok(module)
}
<commit_msg>Fixed loop on empty file<commit_after>use std::io::Write;
use std::io::Read;
use std::fmt;
use std::cmp;
use std::io;

const DEFAULT_NUMBER_OF_SAMPLES:usize = 31;
const DEFAULT_NUMBER_OF_ROWS_PER_PATTERN:usize = 64;
const DEFAULT_NUMBER_OF_CHANNELS_PER_ROW:usize = 4;
const MAGIC_MK:[u8; 4] =['M' as u8, '.' as u8, 'K' as u8, '.' as u8];

#[derive(Debug)]
pub enum PTMFError {
	Io(io::Error),
	Parse(String)
}

impl From<io::Error> for PTMFError {
    fn from(err: io::Error) -> PTMFError {
        PTMFError::Io(err)
    }
}

#[derive(Debug)]
pub struct SampleInfo {
	pub name: String, // [char; 22],
	pub length: u16, // In words, so multiply by 2
	pub finetune: u8,
	pub volume: u8, // 0-64
	pub repeat_start: u16, // In words from start of sample data, so multiply by 2
	pub repeat_length: u16, // Number of words to loop, multiply by 2
}

impl SampleInfo {
	pub fn new() -> SampleInfo {
		SampleInfo{name: String::new(), length:0, finetune:0, volume:0, repeat_start:0, repeat_length:0}
	}
}

#[derive(Debug)]
pub struct Channel {
	pub period: u16, // Really u12
	pub sample_number: u8,
	pub effect: u16 // Really u12
}

impl Channel {
	pub fn new() -> Channel {
		Channel{period: 0, sample_number: 0, effect: 0}
	}
}

#[derive(Debug)]
pub struct Row {
	pub channels: Vec<Channel>
}

impl Row {
	pub fn new() -> Row {
		let mut row = Row{channels: Vec::new()};
		for _ in 0..DEFAULT_NUMBER_OF_CHANNELS_PER_ROW {
			row.channels.push(Channel::new());
		}

		row
	}
}

#[derive(Debug)]
pub struct Pattern {
	pub rows: Vec<Row>
}

impl Pattern {
	pub fn new() -> Pattern {
		let mut p = Pattern{rows: Vec::new()};

		for _ in 0..DEFAULT_NUMBER_OF_ROWS_PER_PATTERN {
			p.rows.push(Row::new());
		}

		p
	}
}

pub struct Positions {
	pub data: [u8; 128]
}

impl fmt::Debug for Positions {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        self.data[..].fmt(formatter)
    }
}

#[derive(Debug)]
pub struct PTModule {
	pub name: String, // [char; 20],
	pub sample_info: Vec<SampleInfo>, // [SampleInfo; DEFAULT_NUMBER_OF_SAMPLES]
	pub length: u8, // Length 1-128
	pub nt_restart: u8, // Set to 127
	pub positions: Positions, // The order in which to play patterns
	pub mk: [u8; 4], // Set to M.K.
	pub patterns: Vec<Pattern>,
	pub sample_data: Vec<Vec<u8>> // The bytes in the samples -127 - 127
}

impl PTModule {
	pub fn new() -> PTModule {
		let mut ptmod = PTModule{name: String::new(), sample_info: Vec::new(), length:0,
			nt_restart: 127, positions: Positions{data: [0; 128]}, mk: MAGIC_MK,
			patterns: Vec::new(), sample_data: Vec::new() };

		for _ in 0..DEFAULT_NUMBER_OF_SAMPLES {
			ptmod.sample_info.push(SampleInfo::new());
		}

		ptmod
	}
}

fn read_all(reader: &mut Read, data: &mut [u8]) -> io::Result<usize> {
	let mut pos = 0;
	while pos < data.len() {
		let slice = &mut data[pos..];
		let n = try!(reader.read(slice));
		if n == 0 {
			return Err(io::Error::new(io::ErrorKind::Other, "0 was returned from read"))
		}
		pos += n;
	}

	Ok(pos)
}

fn write_all(writer: &mut Write, data: &[u8]) -> io::Result<()> {
	let n = try!{writer.write_all(&data)};

	Ok(n)
}

fn read_0_padded_string(reader: &mut Read, len: usize) -> Result<String, io::Error> {
	let mut data = vec![0u8; len];
	try!(read_all(reader,&mut data));
	let mut str = String::new();
	for byte in data {
		if byte != 0 {
			str.push(byte as char);
		}
	}

	Ok(str)
}

fn write_0_padded_string(writer: &mut Write, str: &String, len: usize) -> io::Result<()> {
	let mut data = vec![0u8; len];
	let str_buf = str.as_bytes();
	let m = cmp::min(data.len(),str_buf.len());
	for i in 0..m {
		data[i] = str_buf[i];
	}

	let n = try!(write_all(writer, &data));

	Ok(n)
}

fn read_big_endian_u16(reader: &mut Read) -> Result<u16, io::Error> {
	let mut data_arr = [0u8; 2];
	try!(read_all(reader,&mut data_arr));

	let mut data:u16 = 0;

	for n in data_arr.iter() {
		data = (data << 8) + *n as u16;
	}

	Ok(data)
}

fn write_big_endian_u16(writer: &mut Write, val: u16)  -> io::Result<()> {
	let mut data = [0u8; 2];
	data[0] = (val >> 8) as u8;
	data[1] = (val & 0xff) as u8;

	let n = try!(write_all(writer, &data));

	Ok(n)
}

fn read_u8(reader: &mut Read) -> Result<u8, io::Error> {
	let mut data = [0u8; 1];

	try!(read_all(reader, &mut data));

	Ok(data[0])
}

fn write_u8(writer: &mut Write, val: u8)  -> io::Result<()> {
	let mut data = [0u8; 1];
	data[0] = val;

	let n = try!(write_all(writer, &data));

	Ok(n)
}

/// Write a 31 sample Amiga ProTracker mod-file
pub fn write_mod(writer: &mut Write, module: &mut PTModule) -> Result<(),PTMFError> {

	// First write songname, 20 bytes, pad with 0
	try!(write_0_padded_string(writer,&module.name,20));
	// Then write all 32 samples
	// TODO Handle the case when the vector has less than 31 samples
	let mut num_samples = 0;
	for i in 0..DEFAULT_NUMBER_OF_SAMPLES {
		let ref si = module.sample_info[i];
		// Sample name
		try!(write_0_padded_string(writer, &si.name, 22));
		// Sample length
		try!(write_big_endian_u16(writer, si.length));
		if si.length > 0 {
			num_samples += 1;
		}
		// Finetune
		try!(write_u8(writer, si.finetune));
		// Volume
		try!(write_u8(writer, si.volume));
		// Repeat start
		try!(write_big_endian_u16(writer, si.repeat_start));
		// Repeat length
		try!(write_big_endian_u16(writer, si.repeat_length));
	}

	let num_samples = num_samples;
	// Songlength
	try!(write_u8(writer, module.length));
	// nt_restart
	try!(write_u8(writer, module.nt_restart));
	// Song positions
	try!(write_all(writer,&module.positions.data));
	// M.K.
	try!(write_all(writer,&module.mk));

	// All patterns
	for pattern_it in module.patterns.iter() {
		// All rows
		for row_it in pattern_it.rows.iter() {
			// All channels
			for channel_it in row_it.channels.iter() {
				let mut data = [0u8; 4];

				data[0] = (channel_it.sample_number & 0xf0) | ((channel_it.period & 0xf00) >> 8) as u8;
				data[1] = (channel_it.period & 0xff) as u8;
				data[2] = ((channel_it.sample_number & 0x0f) << 4) | ((channel_it.effect & 0xf00) >> 8) as u8;
				data[3] = (channel_it.effect & 0xff) as u8;

				try!(write_all(writer,&data));
			}
		}
	}

	// And finally all samples
	for sample_it in module.sample_data.iter() {
		try!(write_all(writer,sample_it));
	}

	if num_samples != module.sample_data.len() {
		return Err(PTMFError::Parse(format!("Warning! Number of samples '{}' does not match sample data '{}'", num_samples, module.sample_data.len())));
	}

	Ok(())
}

/// Read a 31 sample Amiga ProTracker mod-file
pub fn read_mod(reader: &mut Read) -> Result<PTModule, PTMFError> {
	let mut module = PTModule::new();

	// First read 20 bytes songname
	module.name = try!(read_0_padded_string(reader, 20));

	// Read all sample info
	// TODO Handle 15 sample files
	for i in 0..DEFAULT_NUMBER_OF_SAMPLES {
		let si = &mut module.sample_info[i];
		// Sample name
		si.name = try!(read_0_padded_string(reader, 22));
		si.length = try!(read_big_endian_u16(reader));
		// Finetune
		si.finetune = try!(read_u8(reader));
		// Volume
		si.volume = try!(read_u8(reader));
		// Repeat start
		si.repeat_start = try!(read_big_endian_u16(reader));
		// Repeat length
		si.repeat_length = try!(read_big_endian_u16(reader));
	}

	// Songlength
	module.length = try!(read_u8(reader));
	// nt_restart
	module.nt_restart = try!(read_u8(reader));
	// Song positions
	try!(read_all(reader,&mut module.positions.data));
	// M.K.
	try!(read_all(reader,&mut module.mk));
	if module.mk != MAGIC_MK {
		return Err(PTMFError::Parse(format!("Unknown format {:?}", module.mk)));
	}

	// Read all patterns
	let mut num_patterns = 0;
	for n in module.positions.data.iter() {
		num_patterns = cmp::max(num_patterns,*n);
	}
	num_patterns += 1;
	for _ in 0..num_patterns {
		let mut pattern = Pattern::new();
		for mut row in &mut pattern.rows {
			for channel in &mut row.channels {
				let mut data  = [0u8;4];
				try!(read_all(reader,&mut data));
				channel.sample_number = (data[0] & 0xf0) | ((data[2] & 0xf0) >> 4);
				channel.period = (((data[0] & 0x0f) as u16) << 8) | (data[1] as u16);
				channel.effect = (((data[2] & 0x0f) as u16) << 8) | (data[3] as u16);
			}
		}
		module.patterns.push(pattern);
	}

	// Read all samples
	for si in &module.sample_info {
		if si.length > 0 {
			let length_in_bytes = si.length * 2;
			let mut data = vec![0u8; length_in_bytes as usize];
			try!(read_all(reader,&mut data));
			module.sample_data.push(data);
		}
	}

	// Now we have read all data.
	// Sanity check that the reader is empty
	let mut data = [0u8; 1];
	match reader.read(&mut data) {
		Ok(n) if n == data.len() => return Err(PTMFError::Parse(format!("Unread data left in file"))),
		_ => ()
	};

	Ok(module)
}
<|endoftext|>


<commit_before>#[cfg(all(unix, not(target_os = "macos")))]
use pretty_assertions::assert_ne;

use crate::common::util::*;

// spell-checker:ignore (flags) lwmcL clmwL ; (path) bogusfile emptyfile manyemptylines moby notrailingnewline onelongemptyline onelongword weirdchars

#[test]
fn test_count_bytes_large_stdin() {
    for n in [
        0,
        1,
        42,
        16 * 1024 - 7,
        16 * 1024 - 1,
        16 * 1024,
        16 * 1024 + 1,
        16 * 1024 + 3,
        32 * 1024,
        64 * 1024,
        80 * 1024,
        96 * 1024,
        112 * 1024,
        128 * 1024,
    ] {
        let data = vec_of_size(n);
        let expected = format!("{}\n", n);
        new_ucmd!()
            .args(&["-c"])
            .pipe_in(data)
            .succeeds()
            .stdout_is_bytes(&expected.as_bytes());
    }
}

#[test]
fn test_stdin_default() {
    new_ucmd!()
        .pipe_in_fixture("lorem_ipsum.txt")
        .run()
        .stdout_is("     13     109     772\n");
}

#[test]
fn test_stdin_explicit() {
    new_ucmd!()
        .pipe_in_fixture("lorem_ipsum.txt")
        .arg("-")
        .run()
        .stdout_is("     13     109     772 -\n");
}

#[test]
fn test_utf8() {
    new_ucmd!()
        .args(&["-lwmcL"])
        .pipe_in_fixture("UTF_8_test.txt")
        .run()
        .stdout_is("    303    2119   22457   23025      79\n");
}

#[test]
fn test_utf8_extra() {
    new_ucmd!()
        .arg("-lwmcL")
        .pipe_in_fixture("UTF_8_weirdchars.txt")
        .run()
        .stdout_is("     25      87     442     513      48\n");
}

#[test]
fn test_stdin_line_len_regression() {
    new_ucmd!()
        .args(&["-L"])
        .pipe_in("\n123456")
        .run()
        .stdout_is("6\n");
}

#[test]
fn test_stdin_only_bytes() {
    new_ucmd!()
        .args(&["-c"])
        .pipe_in_fixture("lorem_ipsum.txt")
        .run()
        .stdout_is("772\n");
}

#[test]
fn test_stdin_all_counts() {
    new_ucmd!()
        .args(&["-c", "-m", "-l", "-L", "-w"])
        .pipe_in_fixture("alice_in_wonderland.txt")
        .run()
        .stdout_is("      5      57     302     302      66\n");
}

#[test]
fn test_single_default() {
    new_ucmd!()
        .arg("moby_dick.txt")
        .run()
        .stdout_is("  18  204 1115 moby_dick.txt\n");
}

#[test]
fn test_single_only_lines() {
    new_ucmd!()
        .args(&["-l", "moby_dick.txt"])
        .run()
        .stdout_is("18 moby_dick.txt\n");
}

#[test]
fn test_single_all_counts() {
    new_ucmd!()
        .args(&["-c", "-l", "-L", "-m", "-w", "alice_in_wonderland.txt"])
        .run()
        .stdout_is("  5  57 302 302  66 alice_in_wonderland.txt\n");
}

#[test]
fn test_multiple_default() {
    new_ucmd!()
        .args(&[
            "lorem_ipsum.txt",
            "moby_dick.txt",
            "alice_in_wonderland.txt",
        ])
        .run()
        .stdout_is(
            "  13  109  772 lorem_ipsum.txt\n  18  204 1115 moby_dick.txt\n   5   57  302 \
             alice_in_wonderland.txt\n  36  370 2189 total\n",
        );
}

/// Test for an empty file.
#[test]
fn test_file_empty() {
    new_ucmd!()
        .args(&["-clmwL", "emptyfile.txt"])
        .run()
        .stdout_is("0 0 0 0 0 emptyfile.txt\n");
}

/// Test for an file containing a single non-whitespace character
/// *without* a trailing newline.
#[test]
fn test_file_single_line_no_trailing_newline() {
    new_ucmd!()
        .args(&["-clmwL", "notrailingnewline.txt"])
        .run()
        .stdout_is("1 1 2 2 1 notrailingnewline.txt\n");
}

/// Test for a file that has 100 empty lines (that is, the contents of
/// the file are the newline character repeated one hundred times).
#[test]
fn test_file_many_empty_lines() {
    new_ucmd!()
        .args(&["-clmwL", "manyemptylines.txt"])
        .run()
        .stdout_is("100   0 100 100   0 manyemptylines.txt\n");
}

/// Test for a file that has one long line comprising only spaces.
#[test]
fn test_file_one_long_line_only_spaces() {
    new_ucmd!()
        .args(&["-clmwL", "onelongemptyline.txt"])
        .run()
        .stdout_is("    1     0 10001 10001 10000 onelongemptyline.txt\n");
}

/// Test for a file that has one long line comprising a single "word".
#[test]
fn test_file_one_long_word() {
    new_ucmd!()
        .args(&["-clmwL", "onelongword.txt"])
        .run()
        .stdout_is("    1     1 10001 10001 10000 onelongword.txt\n");
}

/// Test that the total size of all the files in the input dictates
/// the display width.
///
/// The width in digits of any count is the width in digits of the
/// number of bytes in the file, regardless of whether the number of
/// bytes are displayed.
#[test]
fn test_file_bytes_dictate_width() {
    // This file has 10,001 bytes. Five digits are required to
    // represent that. Even though the number of lines is 1 and the
    // number of words is 0, each of those counts is formatted with
    // five characters, filled with whitespace.
    new_ucmd!()
        .args(&["-lw", "onelongemptyline.txt"])
        .run()
        .stdout_is("    1     0 onelongemptyline.txt\n");

    // This file has zero bytes. Only one digit is required to
    // represent that.
    new_ucmd!()
        .args(&["-lw", "emptyfile.txt"])
        .run()
        .stdout_is("0 0 emptyfile.txt\n");

    // lorem_ipsum.txt contains 772 bytes, and alice_in_wonderland.txt contains
    // 302 bytes. The total is 1074 bytes, which has a width of 4
    new_ucmd!()
        .args(&["-lwc", "alice_in_wonderland.txt", "lorem_ipsum.txt"])
        .run()
        .stdout_is(
            "   5   57  302 alice_in_wonderland.txt\n  13  109  772 \
                    lorem_ipsum.txt\n  18  166 1074 total\n",
        );

    // . is a directory, so minimum_width should get set to 7
    #[cfg(not(windows))]
    const STDOUT: &str = "      0       0       0 emptyfile.txt\n      0       0       0 \
                          .\n      0       0       0 total\n";
    #[cfg(windows)]
    const STDOUT: &str = "      0       0       0 emptyfile.txt\n      0       0       0 total\n";
    new_ucmd!()
        .args(&["-lwc", "emptyfile.txt", "."])
        .run()
        .stdout_is(STDOUT);
}

/// Test that getting counts from a directory is an error.
#[test]
fn test_read_from_directory_error() {
    #[cfg(not(windows))]
    const STDERR: &str = ".: Is a directory";
    #[cfg(windows)]
    const STDERR: &str = ".: Access is denied";

    #[cfg(not(windows))]
    const STDOUT: &str = "      0       0       0 .\n";
    #[cfg(windows)]
    const STDOUT: &str = "";

    new_ucmd!()
        .args(&["."])
        .fails()
        .stderr_contains(STDERR)
        .stdout_is(STDOUT);
}

/// Test that getting counts from nonexistent file is an error.
#[test]
fn test_read_from_nonexistent_file() {
    #[cfg(not(windows))]
    const MSG: &str = "bogusfile: No such file or directory";
    #[cfg(windows)]
    const MSG: &str = "bogusfile: The system cannot find the file specified";
    new_ucmd!()
        .args(&["bogusfile"])
        .fails()
        .stderr_contains(MSG)
        .stdout_is("");
}

#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_files_from_pseudo_filesystem() {
    let result = new_ucmd!().arg("-c").arg("/proc/cpuinfo").succeeds();
    assert_ne!(result.stdout_str(), "0 /proc/cpuinfo\n");
}

#[test]
fn test_files0_disabled_files_argument() {
    const MSG: &str = "file operands cannot be combined with --files0-from";
    new_ucmd!()
        .args(&["--files0-from=files0_list.txt"])
        .arg("lorem_ipsum.txt")
        .fails()
        .stderr_contains(MSG)
        .stdout_is("");
}

#[test]
fn test_files0_from() {
    new_ucmd!()
        .args(&["--files0-from=files0_list.txt"])
        .run()
        .stdout_is(
            "  13  109  772 lorem_ipsum.txt\n  18  204 1115 moby_dick.txt\n   5   57  302 \
             alice_in_wonderland.txt\n  36  370 2189 total\n",
        );
}

#[test]
fn test_files0_from_with_stdin() {
    new_ucmd!()
        .args(&["--files0-from=-"])
        .pipe_in("lorem_ipsum.txt")
        .run()
        .stdout_is(" 13 109 772 lorem_ipsum.txt\n");
}

#[test]
fn test_files0_from_with_stdin_in_file() {
    new_ucmd!()
        .args(&["--files0-from=files0_list_with_stdin.txt"])
        .pipe_in_fixture("alice_in_wonderland.txt")
        .run()
        .stdout_is(
            "     13     109     772 lorem_ipsum.txt\n     18     204    1115 moby_dick.txt\n      5      57     302 \
             -\n     36     370    2189 total\n",
        );
}

#[test]
fn test_files0_from_with_stdin_try_read_from_stdin() {
    const MSG: &str = "when reading file names from stdin, no file name of '-' allowed";
    new_ucmd!()
        .args(&["--files0-from=-"])
        .pipe_in("-")
        .fails()
        .stderr_contains(MSG)
        .stdout_is("");
}
<commit_msg>Add new test to verify GNU compatible quotation.<commit_after>#[cfg(all(unix, not(target_os = "macos")))]
use pretty_assertions::assert_ne;

use crate::common::util::*;

// spell-checker:ignore (flags) lwmcL clmwL ; (path) bogusfile emptyfile manyemptylines moby notrailingnewline onelongemptyline onelongword weirdchars

#[test]
fn test_count_bytes_large_stdin() {
    for n in [
        0,
        1,
        42,
        16 * 1024 - 7,
        16 * 1024 - 1,
        16 * 1024,
        16 * 1024 + 1,
        16 * 1024 + 3,
        32 * 1024,
        64 * 1024,
        80 * 1024,
        96 * 1024,
        112 * 1024,
        128 * 1024,
    ] {
        let data = vec_of_size(n);
        let expected = format!("{}\n", n);
        new_ucmd!()
            .args(&["-c"])
            .pipe_in(data)
            .succeeds()
            .stdout_is_bytes(&expected.as_bytes());
    }
}

#[test]
fn test_stdin_default() {
    new_ucmd!()
        .pipe_in_fixture("lorem_ipsum.txt")
        .run()
        .stdout_is("     13     109     772\n");
}

#[test]
fn test_stdin_explicit() {
    new_ucmd!()
        .pipe_in_fixture("lorem_ipsum.txt")
        .arg("-")
        .run()
        .stdout_is("     13     109     772 -\n");
}

#[test]
fn test_utf8() {
    new_ucmd!()
        .args(&["-lwmcL"])
        .pipe_in_fixture("UTF_8_test.txt")
        .run()
        .stdout_is("    303    2119   22457   23025      79\n");
}

#[test]
fn test_utf8_extra() {
    new_ucmd!()
        .arg("-lwmcL")
        .pipe_in_fixture("UTF_8_weirdchars.txt")
        .run()
        .stdout_is("     25      87     442     513      48\n");
}

#[test]
fn test_stdin_line_len_regression() {
    new_ucmd!()
        .args(&["-L"])
        .pipe_in("\n123456")
        .run()
        .stdout_is("6\n");
}

#[test]
fn test_stdin_only_bytes() {
    new_ucmd!()
        .args(&["-c"])
        .pipe_in_fixture("lorem_ipsum.txt")
        .run()
        .stdout_is("772\n");
}

#[test]
fn test_stdin_all_counts() {
    new_ucmd!()
        .args(&["-c", "-m", "-l", "-L", "-w"])
        .pipe_in_fixture("alice_in_wonderland.txt")
        .run()
        .stdout_is("      5      57     302     302      66\n");
}

#[test]
fn test_single_default() {
    new_ucmd!()
        .arg("moby_dick.txt")
        .run()
        .stdout_is("  18  204 1115 moby_dick.txt\n");
}

#[test]
fn test_single_only_lines() {
    new_ucmd!()
        .args(&["-l", "moby_dick.txt"])
        .run()
        .stdout_is("18 moby_dick.txt\n");
}

#[test]
fn test_single_all_counts() {
    new_ucmd!()
        .args(&["-c", "-l", "-L", "-m", "-w", "alice_in_wonderland.txt"])
        .run()
        .stdout_is("  5  57 302 302  66 alice_in_wonderland.txt\n");
}

#[cfg(unix)]
#[test]
fn test_gnu_compatible_quotation() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;
    at.mkdir("some-dir1");
    at.touch("some-dir1/12\n34.txt");
    scene
        .ucmd()
        .args(&["some-dir1/12\n34.txt"])
        .run()
        .stdout_is("0 0 0 'some-dir1/12'$'\\n''34.txt'\n");
}

#[test]
fn test_multiple_default() {
    new_ucmd!()
        .args(&[
            "lorem_ipsum.txt",
            "moby_dick.txt",
            "alice_in_wonderland.txt",
        ])
        .run()
        .stdout_is(
            "  13  109  772 lorem_ipsum.txt\n  18  204 1115 moby_dick.txt\n   5   57  302 \
             alice_in_wonderland.txt\n  36  370 2189 total\n",
        );
}

/// Test for an empty file.
#[test]
fn test_file_empty() {
    new_ucmd!()
        .args(&["-clmwL", "emptyfile.txt"])
        .run()
        .stdout_is("0 0 0 0 0 emptyfile.txt\n");
}

/// Test for an file containing a single non-whitespace character
/// *without* a trailing newline.
#[test]
fn test_file_single_line_no_trailing_newline() {
    new_ucmd!()
        .args(&["-clmwL", "notrailingnewline.txt"])
        .run()
        .stdout_is("1 1 2 2 1 notrailingnewline.txt\n");
}

/// Test for a file that has 100 empty lines (that is, the contents of
/// the file are the newline character repeated one hundred times).
#[test]
fn test_file_many_empty_lines() {
    new_ucmd!()
        .args(&["-clmwL", "manyemptylines.txt"])
        .run()
        .stdout_is("100   0 100 100   0 manyemptylines.txt\n");
}

/// Test for a file that has one long line comprising only spaces.
#[test]
fn test_file_one_long_line_only_spaces() {
    new_ucmd!()
        .args(&["-clmwL", "onelongemptyline.txt"])
        .run()
        .stdout_is("    1     0 10001 10001 10000 onelongemptyline.txt\n");
}

/// Test for a file that has one long line comprising a single "word".
#[test]
fn test_file_one_long_word() {
    new_ucmd!()
        .args(&["-clmwL", "onelongword.txt"])
        .run()
        .stdout_is("    1     1 10001 10001 10000 onelongword.txt\n");
}

/// Test that the total size of all the files in the input dictates
/// the display width.
///
/// The width in digits of any count is the width in digits of the
/// number of bytes in the file, regardless of whether the number of
/// bytes are displayed.
#[test]
fn test_file_bytes_dictate_width() {
    // This file has 10,001 bytes. Five digits are required to
    // represent that. Even though the number of lines is 1 and the
    // number of words is 0, each of those counts is formatted with
    // five characters, filled with whitespace.
    new_ucmd!()
        .args(&["-lw", "onelongemptyline.txt"])
        .run()
        .stdout_is("    1     0 onelongemptyline.txt\n");

    // This file has zero bytes. Only one digit is required to
    // represent that.
    new_ucmd!()
        .args(&["-lw", "emptyfile.txt"])
        .run()
        .stdout_is("0 0 emptyfile.txt\n");

    // lorem_ipsum.txt contains 772 bytes, and alice_in_wonderland.txt contains
    // 302 bytes. The total is 1074 bytes, which has a width of 4
    new_ucmd!()
        .args(&["-lwc", "alice_in_wonderland.txt", "lorem_ipsum.txt"])
        .run()
        .stdout_is(
            "   5   57  302 alice_in_wonderland.txt\n  13  109  772 \
                    lorem_ipsum.txt\n  18  166 1074 total\n",
        );

    // . is a directory, so minimum_width should get set to 7
    #[cfg(not(windows))]
    const STDOUT: &str = "      0       0       0 emptyfile.txt\n      0       0       0 \
                          .\n      0       0       0 total\n";
    #[cfg(windows)]
    const STDOUT: &str = "      0       0       0 emptyfile.txt\n      0       0       0 total\n";
    new_ucmd!()
        .args(&["-lwc", "emptyfile.txt", "."])
        .run()
        .stdout_is(STDOUT);
}

/// Test that getting counts from a directory is an error.
#[test]
fn test_read_from_directory_error() {
    #[cfg(not(windows))]
    const STDERR: &str = ".: Is a directory";
    #[cfg(windows)]
    const STDERR: &str = ".: Access is denied";

    #[cfg(not(windows))]
    const STDOUT: &str = "      0       0       0 .\n";
    #[cfg(windows)]
    const STDOUT: &str = "";

    new_ucmd!()
        .args(&["."])
        .fails()
        .stderr_contains(STDERR)
        .stdout_is(STDOUT);
}

/// Test that getting counts from nonexistent file is an error.
#[test]
fn test_read_from_nonexistent_file() {
    #[cfg(not(windows))]
    const MSG: &str = "bogusfile: No such file or directory";
    #[cfg(windows)]
    const MSG: &str = "bogusfile: The system cannot find the file specified";
    new_ucmd!()
        .args(&["bogusfile"])
        .fails()
        .stderr_contains(MSG)
        .stdout_is("");
}

#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_files_from_pseudo_filesystem() {
    let result = new_ucmd!().arg("-c").arg("/proc/cpuinfo").succeeds();
    assert_ne!(result.stdout_str(), "0 /proc/cpuinfo\n");
}

#[test]
fn test_files0_disabled_files_argument() {
    const MSG: &str = "file operands cannot be combined with --files0-from";
    new_ucmd!()
        .args(&["--files0-from=files0_list.txt"])
        .arg("lorem_ipsum.txt")
        .fails()
        .stderr_contains(MSG)
        .stdout_is("");
}

#[test]
fn test_files0_from() {
    new_ucmd!()
        .args(&["--files0-from=files0_list.txt"])
        .run()
        .stdout_is(
            "  13  109  772 lorem_ipsum.txt\n  18  204 1115 moby_dick.txt\n   5   57  302 \
             alice_in_wonderland.txt\n  36  370 2189 total\n",
        );
}

#[test]
fn test_files0_from_with_stdin() {
    new_ucmd!()
        .args(&["--files0-from=-"])
        .pipe_in("lorem_ipsum.txt")
        .run()
        .stdout_is(" 13 109 772 lorem_ipsum.txt\n");
}

#[test]
fn test_files0_from_with_stdin_in_file() {
    new_ucmd!()
        .args(&["--files0-from=files0_list_with_stdin.txt"])
        .pipe_in_fixture("alice_in_wonderland.txt")
        .run()
        .stdout_is(
            "     13     109     772 lorem_ipsum.txt\n     18     204    1115 moby_dick.txt\n      5      57     302 \
             -\n     36     370    2189 total\n",
        );
}

#[test]
fn test_files0_from_with_stdin_try_read_from_stdin() {
    const MSG: &str = "when reading file names from stdin, no file name of '-' allowed";
    new_ucmd!()
        .args(&["--files0-from=-"])
        .pipe_in("-")
        .fails()
        .stderr_contains(MSG)
        .stdout_is("");
}
<|endoftext|>


<commit_before>use crate::rparser::{RBuilder, RParser, R_STATUS_FAIL, R_STATUS_OK};
use crate::{gen_get_variants, Variant};
use dhcp4r::{
    options::DhcpOption,
    packet::Packet
};
use std::net::Ipv4Addr;

pub struct DHCPBuilder {}
impl RBuilder for DHCPBuilder {
    fn new(&self) -> Box<dyn RParser> { Box::new(DHCPParser::new(b"DHCP")) }
    fn probe(&self, i:&[u8]) -> bool { dhcp_probe(i) }
}

#[derive(Default)]
pub struct DHCPParser<'a> {
    _name: Option<&'a[u8]>,
    reply: bool,
    xid: u32,
    hostname: Option<String>,
    server_identifier: Option<Ipv4Addr>,
}

impl<'a> DHCPParser<'a> {
    pub fn new(name:&[u8]) -> DHCPParser {
        DHCPParser {
            _name: Some(name),
            ..DHCPParser::default()
        }
    }
}

impl<'a> RParser for DHCPParser<'a> {
    fn parse(&mut self, i: &[u8], _direction: u8) -> u32 {
        match Packet::from(i) {
            Ok(pkt) => {
                // debug!("pkt: {:?}", pkt);
                self.reply = pkt.reply;
                if self.xid == 0 {
                    self.xid = pkt.xid;
                }
                for option in pkt.options.iter() {
                    debug!("  DHCP option: {}", option.code());
                    match option {
                        DhcpOption::HostName(s) => {
                            self.hostname = Some(s.clone());
                        },
                        DhcpOption::ServerIdentifier(ip4) => {
                            self.server_identifier = Some(ip4.clone());
                        },
                        _ => {},
                    }
                }
                R_STATUS_OK
            }
            _ => R_STATUS_FAIL
        }
    }

    gen_get_variants!{DHCPParser, "dhcp.",
        reply => into,
        xid => into,
        hostname => |s| { s.hostname.as_ref().map(|x| Variant::OwnedStr(x.clone())) },
        server_identifier => |s| { s.server_identifier.as_ref().map(|x| Variant::OwnedStr(x.to_string())) },
    }
}

pub fn dhcp_probe(i: &[u8]) -> bool {
    Packet::from(i).is_ok()
}
<commit_msg>DHCP: add client hardware address<commit_after>use crate::rparser::{RBuilder, RParser, R_STATUS_FAIL, R_STATUS_OK};
use crate::{gen_get_variants, Variant};
use dhcp4r::{
    options::DhcpOption,
    packet::Packet
};
use std::net::Ipv4Addr;

pub struct DHCPBuilder {}
impl RBuilder for DHCPBuilder {
    fn new(&self) -> Box<dyn RParser> { Box::new(DHCPParser::new(b"DHCP")) }
    fn probe(&self, i:&[u8]) -> bool { dhcp_probe(i) }
}

#[derive(Default)]
pub struct DHCPParser<'a> {
    _name: Option<&'a[u8]>,
    reply: bool,
    xid: u32,
    chaddr: Option<[u8; 6]>,
    hostname: Option<String>,
    server_identifier: Option<Ipv4Addr>,
}

impl<'a> DHCPParser<'a> {
    pub fn new(name:&[u8]) -> DHCPParser {
        DHCPParser {
            _name: Some(name),
            ..DHCPParser::default()
        }
    }
}

impl<'a> RParser for DHCPParser<'a> {
    fn parse(&mut self, i: &[u8], _direction: u8) -> u32 {
        match Packet::from(i) {
            Ok(pkt) => {
                // debug!("pkt: {:?}", pkt);
                self.reply = pkt.reply;
                if self.xid == 0 {
                    self.xid = pkt.xid;
                }
                if self.chaddr.is_none() {
                    self.chaddr = Some(pkt.chaddr.clone());
                }
                for option in pkt.options.iter() {
                    debug!("  DHCP option: {}", option.code());
                    match option {
                        DhcpOption::HostName(s) => {
                            self.hostname = Some(s.clone());
                        },
                        DhcpOption::ServerIdentifier(ip4) => {
                            self.server_identifier = Some(ip4.clone());
                        },
                        _ => {},
                    }
                }
                R_STATUS_OK
            }
            _ => R_STATUS_FAIL
        }
    }

    gen_get_variants!{DHCPParser, "dhcp.",
        reply => into,
        xid => into,
        chaddr => |s| {
            s.chaddr.as_ref().map(|x| Variant::OwnedStr(
                    format!("{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", x[0], x[1], x[2], x[3], x[4], x[5])))
        },
        hostname => |s| { s.hostname.as_ref().map(|x| Variant::OwnedStr(x.clone())) },
        server_identifier => |s| { s.server_identifier.as_ref().map(|x| Variant::OwnedStr(x.to_string())) },
    }
}

pub fn dhcp_probe(i: &[u8]) -> bool {
    Packet::from(i).is_ok()
}
<|endoftext|>


<commit_before>//! `las` file management.

use std::fs;
use std::io::{BufReader, BufWriter, Seek, Read, Write};
use std::path::Path;

use byteorder::{LittleEndian, WriteBytesExt};

use Result;
use error::Error;
use header::Header;
use io::write_zeros;
use point::Point;
use scale::descale;
use stream::Stream;
use vlr::Vlr;

/// A las file.
#[derive(Debug, PartialEq)]
pub struct File {
    header: Header,
    vlrs: Vec<Vlr>,
    points: Vec<Point>,
}

impl File {
    /// Reads a las file from the filesystem.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::file::File;
    /// let file = File::from_path("data/1.0_0.las").unwrap();
    /// ```
    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<File> {
        let reader = BufReader::new(try!(fs::File::open(path)));
        File::read_from(reader)
    }

    /// Reads a las file from a `Read`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::fs;
    /// use las::file::File;
    /// let reader = fs::File::open("data/1.0_0.las").unwrap();
    /// let file = File::read_from(reader).unwrap();
    /// ```
    pub fn read_from<R: Read + Seek>(reader: R) -> Result<File> {
        let mut file = File::new();
        let mut stream = try!(Stream::new(reader));
        file.header = stream.header();
        file.vlrs = (*stream.vlrs()).clone();
        file.points.reserve(stream.npoints() as usize);
        loop {
            match try!(stream.next_point()) {
                Some(point) => file.points.push(point),
                None => break,
            }
        }
        Ok(file)
    }

    /// Creates a new, empty las file.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::file::File;
    /// let file = File::new();
    /// ```
    pub fn new() -> File {
        File {
            header: Header::new(),
            vlrs: Vec::new(),
            points: Vec::new(),
        }
    }

    /// Returns a reference to a vector of this file's points.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::file::File;
    /// let file = File::from_path("data/1.0_0.las").unwrap();
    /// let points = file.points();
    /// ```
    pub fn points(&self) -> &Vec<Point> {
        &self.points
    }

    /// Adds a point to this lasfile.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::file::File;
    /// use las::point::Point;
    /// let mut file = File::new();
    /// let point = Point::new();
    /// file.add_point(point);
    /// ```
    pub fn add_point(&mut self, point: Point) {
        self.points.push(point);
    }

    /// Writes out this las file to a `Path`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::fs::remove_file;
    /// use las::file::File;
    /// let mut file = File::new();
    /// file.to_path("temp.las").unwrap();
    /// remove_file("temp.las");
    /// ```
    pub fn to_path<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
        let ref mut writer = BufWriter::new(try!(fs::File::create(path)));
        self.write_to(writer)
    }

    /// Writes this las file to a `Write`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::io::Cursor;
    /// use las::file::File;
    /// let mut file = File::from_path("data/1.0_0.las").unwrap();
    /// let ref mut cursor = Cursor::new(Vec::new());
    /// file.write_to(cursor).unwrap();
    /// ```
    pub fn write_to<W: Write>(&mut self, writer: &mut W) -> Result<()> {
        self.header.calculate_size();
        self.header.number_of_point_records = self.points.len() as u32;
        self.header.offset_to_point_data = self.header.header_size as u32 +
                                           self.vlrs.iter().fold(0, |a, v| a + v.len());
        self.header.point_data_record_length = self.header.point_data_format.record_length();

        let mut number_of_points_by_return = [0u32; 5];
        for point in &self.points {
            let return_number = point.return_number.as_u8();
            if return_number > 0 {
                number_of_points_by_return[(return_number - 1) as usize] += 1;
            }
        }

        let mut bytes_written = try!(self.header.write_to(writer)) as usize;
        if bytes_written < self.header.header_size as usize {
            bytes_written += try!(write_zeros(writer,
                                              self.header.header_size as usize - bytes_written));
        }
        for vlr in &self.vlrs {
            bytes_written += try!(vlr.write_to(writer)) as usize;
        }
        if bytes_written < self.header.offset_to_point_data as usize {
            try!(write_zeros(writer,
                             self.header.offset_to_point_data as usize - bytes_written));
        }
        for point in &self.points {
            try!(self.write_point_to(writer, point));
        }
        Ok(())
    }

    fn write_point_to<W: Write>(&self, writer: &mut W, point: &Point) -> Result<()> {
        try!(writer.write_i32::<LittleEndian>(descale(point.x,
                                                      self.header.x_scale_factor,
                                                      self.header.x_offset)));
        try!(writer.write_i32::<LittleEndian>(descale(point.y,
                                                      self.header.y_scale_factor,
                                                      self.header.y_offset)));
        try!(writer.write_i32::<LittleEndian>(descale(point.z,
                                                      self.header.z_scale_factor,
                                                      self.header.z_offset)));
        try!(writer.write_u16::<LittleEndian>(point.intensity));
        let byte = point.return_number.as_u8() + (point.number_of_returns.as_u8() << 3) +
                   (point.scan_direction.as_u8() << 6) +
                   ((point.edge_of_flight_line as u8) << 7);
        try!(writer.write_u8(byte));
        let byte = point.classification.as_u8() + ((point.synthetic as u8) << 5) +
                   ((point.key_point as u8) << 6) +
                   ((point.withheld as u8) << 7);
        try!(writer.write_u8(byte));
        try!(writer.write_i8(point.scan_angle_rank));
        try!(writer.write_u8(point.user_data));
        try!(writer.write_u16::<LittleEndian>(point.point_source_id));
        if self.header.point_data_format.has_time() {
            match point.gps_time {
                Some(gps_time) => try!(writer.write_f64::<LittleEndian>(gps_time)),
                None => {
                    return Err(Error::PointFormat(self.header.point_data_format,
                                                     "gps_time".to_string()))
                }
            }
        }
        if self.header.point_data_format.has_color() {
            match point.red {
                Some(red) => try!(writer.write_u16::<LittleEndian>(red)),
                None => {
                    return Err(Error::PointFormat(self.header.point_data_format,
                                                     "red".to_string()))
                }
            }
            match point.green {
                Some(green) => try!(writer.write_u16::<LittleEndian>(green)),
                None => {
                    return Err(Error::PointFormat(self.header.point_data_format,
                                                     "green".to_string()))
                }
            }
            match point.blue {
                Some(blue) => try!(writer.write_u16::<LittleEndian>(blue)),
                None => {
                    return Err(Error::PointFormat(self.header.point_data_format,
                                                     "blue".to_string()))
                }
            }
        }
        match point.extra_bytes {
            Some(ref bytes) => try!(writer.write_all(&bytes[..])),
            None => {}
        }
        Ok(())
    }
}

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

    use std::fs::remove_file;
    use std::io::Cursor;
    use std::path::Path;

    use point::Point;

    fn roundtrip<P: AsRef<Path>>(path: P) {
        let mut lasfile = File::from_path(path).unwrap();
        let ref mut cursor = Cursor::new(Vec::new());
        lasfile.write_to(cursor).unwrap();
        cursor.set_position(0);
        let lasfile2 = File::read_from(cursor).unwrap();
        assert_eq!(lasfile, lasfile2);
    }

    #[test]
    fn roundtrip_1_0_0() {
        roundtrip("data/1.0_0.las");
    }

    #[test]
    fn roundtrip_1_0_1() {
        roundtrip("data/1.0_1.las");
    }

    #[test]
    fn roundtrip_1_1_0() {
        roundtrip("data/1.1_0.las");
    }

    #[test]
    fn roundtrip_1_1_1() {
        roundtrip("data/1.1_1.las");
    }

    #[test]
    fn roundtrip_1_2_0() {
        roundtrip("data/1.2_0.las");
    }

    #[test]
    fn roundtrip_1_2_1() {
        roundtrip("data/1.2_1.las");
    }

    #[test]
    fn roundtrip_1_2_2() {
        roundtrip("data/1.2_2.las");
    }

    #[test]
    fn roundtrip_1_2_3() {
        roundtrip("data/1.2_3.las");
    }

    /// This file is good as it exercieses a weird use case, but the test fails at the moment. I'm
    /// not sure why, so I'm going to keep it around but ignore it.
    #[test]
    #[ignore]
    fn roundtrip_extrabytes() {
        roundtrip("data/extrabytes.las");
    }

    #[test]
    fn point_format_1_has_gps_time() {
        let lasfile = File::from_path("data/1.0_1.las").unwrap();
        let ref point = lasfile.points()[0];
        assert!(point.gps_time.is_some());
    }

    #[test]
    fn point_format_2_has_color() {
        let lasfile = File::from_path("data/1.2_2.las").unwrap();
        let ref point = lasfile.points()[0];
        assert!(point.red.is_some());
        assert!(point.green.is_some());
        assert!(point.blue.is_some());
    }

    #[test]
    fn write_one_point() {
        let mut point = Point::new();
        point.x = 1.0;
        point.y = 2.0;
        point.z = 3.0;
        let mut lasfile = File::new();
        lasfile.add_point(point);
        lasfile.to_path("temp.las").unwrap();

        let lasfile = File::from_path("temp.las").unwrap();
        let ref point = lasfile.points()[0];
        assert_eq!(1.0, point.x);
        assert_eq!(2.0, point.y);
        assert_eq!(3.0, point.z);

        remove_file("temp.las").unwrap();
    }
}
<commit_msg>Set mins and maxes in the header<commit_after>//! `las` file management.

use std::f64;
use std::fs;
use std::io::{BufReader, BufWriter, Seek, Read, Write};
use std::path::Path;

use byteorder::{LittleEndian, WriteBytesExt};

use Result;
use error::Error;
use header::Header;
use io::write_zeros;
use point::Point;
use scale::descale;
use stream::Stream;
use vlr::Vlr;

/// A las file.
#[derive(Debug, PartialEq)]
pub struct File {
    header: Header,
    vlrs: Vec<Vlr>,
    points: Vec<Point>,
}

impl File {
    /// Reads a las file from the filesystem.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::file::File;
    /// let file = File::from_path("data/1.0_0.las").unwrap();
    /// ```
    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<File> {
        let reader = BufReader::new(try!(fs::File::open(path)));
        File::read_from(reader)
    }

    /// Reads a las file from a `Read`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::fs;
    /// use las::file::File;
    /// let reader = fs::File::open("data/1.0_0.las").unwrap();
    /// let file = File::read_from(reader).unwrap();
    /// ```
    pub fn read_from<R: Read + Seek>(reader: R) -> Result<File> {
        let mut file = File::new();
        let mut stream = try!(Stream::new(reader));
        file.header = stream.header();
        file.vlrs = (*stream.vlrs()).clone();
        file.points.reserve(stream.npoints() as usize);
        loop {
            match try!(stream.next_point()) {
                Some(point) => file.points.push(point),
                None => break,
            }
        }
        Ok(file)
    }

    /// Creates a new, empty las file.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::file::File;
    /// let file = File::new();
    /// ```
    pub fn new() -> File {
        File {
            header: Header::new(),
            vlrs: Vec::new(),
            points: Vec::new(),
        }
    }

    /// Returns a reference to a vector of this file's points.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::file::File;
    /// let file = File::from_path("data/1.0_0.las").unwrap();
    /// let points = file.points();
    /// ```
    pub fn points(&self) -> &Vec<Point> {
        &self.points
    }

    /// Adds a point to this lasfile.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::file::File;
    /// use las::point::Point;
    /// let mut file = File::new();
    /// let point = Point::new();
    /// file.add_point(point);
    /// ```
    pub fn add_point(&mut self, point: Point) {
        self.points.push(point);
    }

    /// Writes out this las file to a `Path`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::fs::remove_file;
    /// use las::file::File;
    /// let mut file = File::new();
    /// file.to_path("temp.las").unwrap();
    /// remove_file("temp.las");
    /// ```
    pub fn to_path<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
        let ref mut writer = BufWriter::new(try!(fs::File::create(path)));
        self.write_to(writer)
    }

    /// Writes this las file to a `Write`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::io::Cursor;
    /// use las::file::File;
    /// let mut file = File::from_path("data/1.0_0.las").unwrap();
    /// let ref mut cursor = Cursor::new(Vec::new());
    /// file.write_to(cursor).unwrap();
    /// ```
    pub fn write_to<W: Write>(&mut self, writer: &mut W) -> Result<()> {
        self.header.calculate_size();
        self.header.number_of_point_records = self.points.len() as u32;
        self.header.offset_to_point_data = self.header.header_size as u32 +
                                           self.vlrs.iter().fold(0, |a, v| a + v.len());
        self.header.point_data_record_length = self.header.point_data_format.record_length();

        let mut number_of_points_by_return = [0u32; 5];
        let mut x_min = f64::MAX;
        let mut y_min = f64::MAX;
        let mut z_min = f64::MAX;
        let mut x_max = f64::MIN;
        let mut y_max = f64::MIN;
        let mut z_max = f64::MIN;
        for point in &self.points {
            let return_number = point.return_number.as_u8();
            if return_number > 0 {
                number_of_points_by_return[(return_number - 1) as usize] += 1;
            }
            if point.x < x_min {
                x_min = point.x;
            }
            if point.y < y_min {
                y_min = point.y;
            }
            if point.z < z_min {
                z_min = point.z;
            }
            if point.x > x_max {
                x_max = point.x;
            }
            if point.y > y_max {
                y_max = point.y;
            }
            if point.z > z_max {
                z_max = point.z;
            }
        }
        self.header.x_min = x_min;
        self.header.y_min = y_min;
        self.header.z_min = z_min;
        self.header.x_max = x_max;
        self.header.y_max = y_max;
        self.header.z_max = z_max;

        let mut bytes_written = try!(self.header.write_to(writer)) as usize;
        if bytes_written < self.header.header_size as usize {
            bytes_written += try!(write_zeros(writer,
                                              self.header.header_size as usize - bytes_written));
        }
        for vlr in &self.vlrs {
            bytes_written += try!(vlr.write_to(writer)) as usize;
        }
        if bytes_written < self.header.offset_to_point_data as usize {
            try!(write_zeros(writer,
                             self.header.offset_to_point_data as usize - bytes_written));
        }
        for point in &self.points {
            try!(self.write_point_to(writer, point));
        }
        Ok(())
    }

    fn write_point_to<W: Write>(&self, writer: &mut W, point: &Point) -> Result<()> {
        try!(writer.write_i32::<LittleEndian>(descale(point.x,
                                                      self.header.x_scale_factor,
                                                      self.header.x_offset)));
        try!(writer.write_i32::<LittleEndian>(descale(point.y,
                                                      self.header.y_scale_factor,
                                                      self.header.y_offset)));
        try!(writer.write_i32::<LittleEndian>(descale(point.z,
                                                      self.header.z_scale_factor,
                                                      self.header.z_offset)));
        try!(writer.write_u16::<LittleEndian>(point.intensity));
        let byte = point.return_number.as_u8() + (point.number_of_returns.as_u8() << 3) +
                   (point.scan_direction.as_u8() << 6) +
                   ((point.edge_of_flight_line as u8) << 7);
        try!(writer.write_u8(byte));
        let byte = point.classification.as_u8() + ((point.synthetic as u8) << 5) +
                   ((point.key_point as u8) << 6) +
                   ((point.withheld as u8) << 7);
        try!(writer.write_u8(byte));
        try!(writer.write_i8(point.scan_angle_rank));
        try!(writer.write_u8(point.user_data));
        try!(writer.write_u16::<LittleEndian>(point.point_source_id));
        if self.header.point_data_format.has_time() {
            match point.gps_time {
                Some(gps_time) => try!(writer.write_f64::<LittleEndian>(gps_time)),
                None => {
                    return Err(Error::PointFormat(self.header.point_data_format,
                                                     "gps_time".to_string()))
                }
            }
        }
        if self.header.point_data_format.has_color() {
            match point.red {
                Some(red) => try!(writer.write_u16::<LittleEndian>(red)),
                None => {
                    return Err(Error::PointFormat(self.header.point_data_format,
                                                     "red".to_string()))
                }
            }
            match point.green {
                Some(green) => try!(writer.write_u16::<LittleEndian>(green)),
                None => {
                    return Err(Error::PointFormat(self.header.point_data_format,
                                                     "green".to_string()))
                }
            }
            match point.blue {
                Some(blue) => try!(writer.write_u16::<LittleEndian>(blue)),
                None => {
                    return Err(Error::PointFormat(self.header.point_data_format,
                                                     "blue".to_string()))
                }
            }
        }
        match point.extra_bytes {
            Some(ref bytes) => try!(writer.write_all(&bytes[..])),
            None => {}
        }
        Ok(())
    }
}

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

    use std::fs::remove_file;
    use std::io::Cursor;
    use std::path::Path;

    use point::Point;

    fn roundtrip<P: AsRef<Path>>(path: P) {
        let mut lasfile = File::from_path(path).unwrap();
        let ref mut cursor = Cursor::new(Vec::new());
        lasfile.write_to(cursor).unwrap();
        cursor.set_position(0);
        let lasfile2 = File::read_from(cursor).unwrap();
        assert_eq!(lasfile, lasfile2);
    }

    #[test]
    fn roundtrip_1_0_0() {
        roundtrip("data/1.0_0.las");
    }

    #[test]
    fn roundtrip_1_0_1() {
        roundtrip("data/1.0_1.las");
    }

    #[test]
    fn roundtrip_1_1_0() {
        roundtrip("data/1.1_0.las");
    }

    #[test]
    fn roundtrip_1_1_1() {
        roundtrip("data/1.1_1.las");
    }

    #[test]
    fn roundtrip_1_2_0() {
        roundtrip("data/1.2_0.las");
    }

    #[test]
    fn roundtrip_1_2_1() {
        roundtrip("data/1.2_1.las");
    }

    #[test]
    fn roundtrip_1_2_2() {
        roundtrip("data/1.2_2.las");
    }

    #[test]
    fn roundtrip_1_2_3() {
        roundtrip("data/1.2_3.las");
    }

    /// This file is good as it exercieses a weird use case, but the test fails at the moment. I'm
    /// not sure why, so I'm going to keep it around but ignore it.
    #[test]
    #[ignore]
    fn roundtrip_extrabytes() {
        roundtrip("data/extrabytes.las");
    }

    #[test]
    fn point_format_1_has_gps_time() {
        let lasfile = File::from_path("data/1.0_1.las").unwrap();
        let ref point = lasfile.points()[0];
        assert!(point.gps_time.is_some());
    }

    #[test]
    fn point_format_2_has_color() {
        let lasfile = File::from_path("data/1.2_2.las").unwrap();
        let ref point = lasfile.points()[0];
        assert!(point.red.is_some());
        assert!(point.green.is_some());
        assert!(point.blue.is_some());
    }

    #[test]
    fn write_one_point() {
        let mut point = Point::new();
        point.x = 1.0;
        point.y = 2.0;
        point.z = 3.0;
        let mut lasfile = File::new();
        lasfile.add_point(point);
        lasfile.to_path("temp.las").unwrap();

        let lasfile = File::from_path("temp.las").unwrap();
        let ref point = lasfile.points()[0];
        assert_eq!(1.0, point.x);
        assert_eq!(2.0, point.y);
        assert_eq!(3.0, point.z);

        remove_file("temp.las").unwrap();
    }
}
<|endoftext|>