<commit_before>//! Module holding an in-memory implementation of a NIfTI volume.

use super::NiftiVolume;
use super::util::coords_to_index;
use std::io::{BufReader, Read};
use std::fs::File;
use std::path::Path;
use header::NiftiHeader;
use extension::{Extender, ExtensionSequence};
use error::{NiftiError, Result};
use util::{raw_to_value, Endianness};
use byteorder::{BigEndian, LittleEndian};
use flate2::bufread::GzDecoder;
use typedef::NiftiType;
use num::FromPrimitive;

#[cfg(feature = "ndarray_volumes")]
use asprim::AsPrim;
#[cfg(feature = "ndarray_volumes")]
use volume::ndarray::IntoNdArray;
#[cfg(feature = "ndarray_volumes")]
use util::{convert_vec_f32, raw_to_value_via_f32};
#[cfg(feature = "ndarray_volumes")]
use ndarray::{Array, Ix, IxDyn, ShapeBuilder};
#[cfg(feature = "ndarray_volumes")]
use std::ops::{Add, Mul};
#[cfg(feature = "ndarray_volumes")]
use num::Num;

/// A data type for a NIFTI-1 volume contained in memory.
/// Objects of this type contain raw image data, which
/// is converted automatically when using reading methods
/// or converting it to an `ndarray` (with the
/// `ndarray_volumes` feature).
#[derive(Debug, PartialEq, Clone)]
pub struct InMemNiftiVolume {
    dim: [u16; 8],
    datatype: NiftiType,
    scl_slope: f32,
    scl_inter: f32,
    raw_data: Vec<u8>,
    endianness: Endianness,
}

impl InMemNiftiVolume {
    /// Read a NIFTI volume from a stream of data. The header and expected byte order
    /// of the volume's data must be known in advance. It it also expected that the
    /// following bytes represent the first voxels of the volume (and not part of the
    /// extensions).
    pub fn from_stream<R: Read>(
        mut source: R,
        header: &NiftiHeader,
        endianness: Endianness,
    ) -> Result<Self> {
        let ndims = header.dim[0];
        let resolution: usize = header.dim[1..(ndims + 1) as usize]
            .iter()
            .map(|d| *d as usize)
            .product();
        let nbytes = resolution * header.bitpix as usize / 8;
        println!("Reading volume of {:?} bytes", nbytes);
        let mut raw_data = vec![0u8; nbytes];
        source.read_exact(&mut raw_data)?;

        let datatype: NiftiType =
            NiftiType::from_i16(header.datatype).ok_or_else(|| NiftiError::InvalidFormat)?;

        Ok(InMemNiftiVolume {
            dim: header.dim,
            datatype,
            scl_slope: header.scl_slope,
            scl_inter: header.scl_inter,
            raw_data,
            endianness,
        })
    }

    /// Read a NIFTI volume, and extensions, from a stream of data. The header,
    /// extender code and expected byte order of the volume's data must be
    /// known in advance.
    pub fn from_stream_with_extensions<R>(
        mut source: R,
        header: &NiftiHeader,
        extender: Extender,
        endianness: Endianness,
    ) -> Result<(Self, ExtensionSequence)>
    where
        R: Read,
    {
        // fetch extensions
        let len = header.vox_offset as usize;
        let len = if len < 352 { 0 } else { len - 352 };

        let ext = match endianness {
            Endianness::LE => {
                ExtensionSequence::from_stream::<LittleEndian, _>(extender, &mut source, len)
            }
            Endianness::BE => {
                ExtensionSequence::from_stream::<BigEndian, _>(extender, &mut source, len)
            }
        }?;

        // fetch volume (rest of file)
        Ok((Self::from_stream(source, &header, endianness)?, ext))
    }

    /// Read a NIFTI volume from an image file. NIFTI-1 volume files usually have the
    /// extension ".img" or ".img.gz". In the latter case, the file is automatically
    /// decoded as a Gzip stream.
    pub fn from_file<P: AsRef<Path>>(
        path: P,
        header: &NiftiHeader,
        endianness: Endianness,
    ) -> Result<Self> {
        let gz = path.as_ref()
            .extension()
            .map(|a| a.to_string_lossy() == "gz")
            .unwrap_or(false);
        let file = BufReader::new(File::open(path)?);
        if gz {
            InMemNiftiVolume::from_stream(GzDecoder::new(file)?, &header, endianness)
        } else {
            InMemNiftiVolume::from_stream(file, &header, endianness)
        }
    }

    /// Read a NIFTI volume, along with the extensions, from an image file. NIFTI-1 volume
    /// files usually have the extension ".img" or ".img.gz". In the latter case, the file
    /// is automatically decoded as a Gzip stream.
    pub fn from_file_with_extensions<P>(
        path: P,
        header: &NiftiHeader,
        endianness: Endianness,
        extender: Extender,
    ) -> Result<(Self, ExtensionSequence)>
    where
        P: AsRef<Path>,
    {
        let gz = path.as_ref()
            .extension()
            .map(|a| a.to_string_lossy() == "gz")
            .unwrap_or(false);
        let stream = BufReader::new(File::open(path)?);

        if gz {
            InMemNiftiVolume::from_stream_with_extensions(
                GzDecoder::new(stream)?,
                &header,
                extender,
                endianness,
            )
        } else {
            InMemNiftiVolume::from_stream_with_extensions(stream, &header, extender, endianness)
        }
    }

    /// Retrieve the raw data, consuming the volume.
    pub fn to_raw_data(self) -> Vec<u8> {
        self.raw_data
    }

    /// Retrieve a reference to the raw data.
    pub fn get_raw_data(&self) -> &[u8] {
        &self.raw_data
    }

    /// Retrieve a mutable reference to the raw data.
    pub fn get_raw_data_mut(&mut self) -> &mut [u8] {
        &mut self.raw_data
    }
}

#[cfg(feature = "ndarray_volumes")]
impl IntoNdArray for InMemNiftiVolume {
    /// Consume the volume into an ndarray.
    fn to_ndarray<T>(self) -> Result<Array<T, IxDyn>>
    where
        T: AsPrim,
        T: Clone,
        T: Num,
        T: Mul<Output = T>,
        T: Add<Output = T>,
    {
        let dim: Vec<_> = self.dim().iter().map(|d| *d as Ix).collect();

        match self.datatype {
            NiftiType::Uint8 => {
                let slope = self.scl_slope;
                let inter = self.scl_inter;
                let a = Array::from_shape_vec(IxDyn(&dim).f(), self.raw_data)
                    .expect("Inconsistent raw data size")
                    .mapv(|v| raw_to_value_via_f32(v, slope, inter));
                Ok(a)
            }
            NiftiType::Int8 => {
                let slope = self.scl_slope;
                let inter = self.scl_inter;
                let a = Array::from_shape_vec(IxDyn(&dim).f(), self.raw_data)
                    .expect("Inconsistent raw data size")
                    .mapv(|v| raw_to_value_via_f32(v, slope, inter));
                Ok(a)
            }
            NiftiType::Float32 => {
                let slope = self.scl_slope.as_();
                let inter = self.scl_inter.as_();
                let raw_data = convert_vec_f32(self.raw_data, self.endianness);
                let a = Array::from_shape_vec(IxDyn(&dim).f(), raw_data)
                    .expect("Inconsistent raw data size")
                    .mapv(|v| raw_to_value(v, slope, inter));
                Ok(a)
            }
            _ => Err(NiftiError::UnsupportedDataType(self.datatype)),
        }
    }
}

#[cfg(feature = "ndarray_volumes")]
impl<'a> IntoNdArray for &'a InMemNiftiVolume {
    /// Create an ndarray from the given volume.
    fn to_ndarray<T>(self) -> Result<Array<T, IxDyn>>
    where
        T: AsPrim,
        T: Clone,
        T: Num,
        T: Mul<Output = T>,
        T: Add<Output = T>,
    {
        self.clone().to_ndarray()
    }
}

impl<'a> NiftiVolume for &'a InMemNiftiVolume {
    fn dim(&self) -> &[u16] {
        (**self).dim()
    }

    fn dimensionality(&self) -> usize {
        (**self).dimensionality()
    }

    fn data_type(&self) -> NiftiType {
        (**self).data_type()
    }

    fn get_f32(&self, coords: &[u16]) -> Result<f32> {
        (**self).get_f32(coords)
    }

    fn get_f64(&self, coords: &[u16]) -> Result<f64> {
        (**self).get_f64(coords)
    }

    fn get_u8(&self, coords: &[u16]) -> Result<u8> {
        (**self).get_u8(coords)
    }
}

impl NiftiVolume for InMemNiftiVolume {
    fn dim(&self) -> &[u16] {
        &self.dim[1..(self.dim[0] + 1) as usize]
    }

    fn dimensionality(&self) -> usize {
        self.dim[0] as usize
    }

    fn data_type(&self) -> NiftiType {
        self.datatype
    }

    fn get_f32(&self, coords: &[u16]) -> Result<f32> {
        let index = coords_to_index(coords, self.dim())?;
        if self.datatype == NiftiType::Uint8 {
            let byte = self.raw_data[index];
            Ok(raw_to_value(byte, self.scl_slope, self.scl_inter))
        } else if self.datatype == NiftiType::Int8 {
            let byte = self.raw_data[index] as i8;
            Ok(raw_to_value(byte, self.scl_slope, self.scl_inter))
        } else {
            let range = &self.raw_data[index * self.datatype.size_of()..];
            self.datatype.read_primitive_value(
                range,
                self.endianness,
                self.scl_slope,
                self.scl_inter,
            )
        }
    }

    fn get_f64(&self, coords: &[u16]) -> Result<f64> {
        let index = coords_to_index(coords, self.dim())?;
        if self.datatype == NiftiType::Uint8 {
            let byte = self.raw_data[index];
            Ok(raw_to_value(
                byte as f64,
                self.scl_slope as f64,
                self.scl_inter as f64,
            ))
        } else {
            let range = &self.raw_data[index..];
            self.datatype.read_primitive_value(
                range,
                self.endianness,
                self.scl_slope,
                self.scl_inter,
            )
        }
    }

    fn get_u8(&self, coords: &[u16]) -> Result<u8> {
        let index = coords_to_index(coords, self.dim())?;
        if self.datatype == NiftiType::Uint8 {
            let byte = self.raw_data[index];
            Ok(raw_to_value_via_f32(byte, self.scl_slope, self.scl_inter))
        } else if self.datatype == NiftiType::Int8 {
            let byte = self.raw_data[index] as i8;
            Ok(raw_to_value_via_f32(byte, self.scl_slope, self.scl_inter))
        } else {
            let range = &self.raw_data[index * self.datatype.size_of()..];
            self.datatype.read_primitive_value(
                range,
                self.endianness,
                self.scl_slope,
                self.scl_inter,
            )
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use volume::Sliceable;
    use typedef::NiftiType;
    use util::Endianness;

    #[test]
    fn test_u8_inmem_volume() {
        let data: Vec<u8> = (0..64).map(|x| x * 2).collect();
        let vol = InMemNiftiVolume {
            dim: [3, 4, 4, 4, 0, 0, 0, 0],
            datatype: NiftiType::Uint8,
            scl_slope: 1.,
            scl_inter: -5.,
            raw_data: data,
            endianness: Endianness::LE,
        };

        let v = vol.get_f32(&[3, 1, 0]).unwrap();
        assert_eq!(v, 9.);

        let v = vol.get_f32(&[3, 3, 3]).unwrap();
        assert_eq!(v, 121.);

        let v = vol.get_f32(&[2, 1, 1]).unwrap();
        assert_eq!(v, 39.);

        assert!(vol.get_f32(&[4, 0, 0]).is_err());
    }

    #[test]
    fn test_u8_inmem_volume_slice() {
        let data: Vec<u8> = (0..64).map(|x| x * 2).collect();
        let vol = InMemNiftiVolume {
            dim: [3, 4, 4, 4, 0, 0, 0, 0],
            datatype: NiftiType::Uint8,
            scl_slope: 1.,
            scl_inter: -5.,
            raw_data: data,
            endianness: Endianness::LE,
        };

        let slice = (&vol).get_slice(0, 3).unwrap();
        assert_eq!(slice.dim(), &[4, 4]);
        assert_eq!(slice.dimensionality(), 2);

        let v = slice.get_f32(&[1, 0]).unwrap();
        assert_eq!(v, 9.);
        let v = slice.get_f32(&[3, 3]).unwrap();
        assert_eq!(v, 121.);

        let slice = (&vol).get_slice(1, 1).unwrap();
        assert_eq!(slice.dim(), &[4, 4]);
        assert_eq!(slice.dimensionality(), 2);
        let v = slice.get_f32(&[2, 1]).unwrap();
        assert_eq!(v, 39.);
    }
}
<commit_msg>Fix import (was conditioned on feature)<commit_after>//! Module holding an in-memory implementation of a NIfTI volume.

use super::NiftiVolume;
use super::util::coords_to_index;
use std::io::{BufReader, Read};
use std::fs::File;
use std::path::Path;
use header::NiftiHeader;
use extension::{Extender, ExtensionSequence};
use error::{NiftiError, Result};
use util::{raw_to_value, raw_to_value_via_f32, Endianness};
use byteorder::{BigEndian, LittleEndian};
use flate2::bufread::GzDecoder;
use typedef::NiftiType;
use num::FromPrimitive;

#[cfg(feature = "ndarray_volumes")]
use asprim::AsPrim;
#[cfg(feature = "ndarray_volumes")]
use volume::ndarray::IntoNdArray;
#[cfg(feature = "ndarray_volumes")]
use util::{convert_vec_f32};
#[cfg(feature = "ndarray_volumes")]
use ndarray::{Array, Ix, IxDyn, ShapeBuilder};
#[cfg(feature = "ndarray_volumes")]
use std::ops::{Add, Mul};
#[cfg(feature = "ndarray_volumes")]
use num::Num;

/// A data type for a NIFTI-1 volume contained in memory.
/// Objects of this type contain raw image data, which
/// is converted automatically when using reading methods
/// or converting it to an `ndarray` (with the
/// `ndarray_volumes` feature).
#[derive(Debug, PartialEq, Clone)]
pub struct InMemNiftiVolume {
    dim: [u16; 8],
    datatype: NiftiType,
    scl_slope: f32,
    scl_inter: f32,
    raw_data: Vec<u8>,
    endianness: Endianness,
}

impl InMemNiftiVolume {
    /// Read a NIFTI volume from a stream of data. The header and expected byte order
    /// of the volume's data must be known in advance. It it also expected that the
    /// following bytes represent the first voxels of the volume (and not part of the
    /// extensions).
    pub fn from_stream<R: Read>(
        mut source: R,
        header: &NiftiHeader,
        endianness: Endianness,
    ) -> Result<Self> {
        let ndims = header.dim[0];
        let resolution: usize = header.dim[1..(ndims + 1) as usize]
            .iter()
            .map(|d| *d as usize)
            .product();
        let nbytes = resolution * header.bitpix as usize / 8;
        println!("Reading volume of {:?} bytes", nbytes);
        let mut raw_data = vec![0u8; nbytes];
        source.read_exact(&mut raw_data)?;

        let datatype: NiftiType =
            NiftiType::from_i16(header.datatype).ok_or_else(|| NiftiError::InvalidFormat)?;

        Ok(InMemNiftiVolume {
            dim: header.dim,
            datatype,
            scl_slope: header.scl_slope,
            scl_inter: header.scl_inter,
            raw_data,
            endianness,
        })
    }

    /// Read a NIFTI volume, and extensions, from a stream of data. The header,
    /// extender code and expected byte order of the volume's data must be
    /// known in advance.
    pub fn from_stream_with_extensions<R>(
        mut source: R,
        header: &NiftiHeader,
        extender: Extender,
        endianness: Endianness,
    ) -> Result<(Self, ExtensionSequence)>
    where
        R: Read,
    {
        // fetch extensions
        let len = header.vox_offset as usize;
        let len = if len < 352 { 0 } else { len - 352 };

        let ext = match endianness {
            Endianness::LE => {
                ExtensionSequence::from_stream::<LittleEndian, _>(extender, &mut source, len)
            }
            Endianness::BE => {
                ExtensionSequence::from_stream::<BigEndian, _>(extender, &mut source, len)
            }
        }?;

        // fetch volume (rest of file)
        Ok((Self::from_stream(source, &header, endianness)?, ext))
    }

    /// Read a NIFTI volume from an image file. NIFTI-1 volume files usually have the
    /// extension ".img" or ".img.gz". In the latter case, the file is automatically
    /// decoded as a Gzip stream.
    pub fn from_file<P: AsRef<Path>>(
        path: P,
        header: &NiftiHeader,
        endianness: Endianness,
    ) -> Result<Self> {
        let gz = path.as_ref()
            .extension()
            .map(|a| a.to_string_lossy() == "gz")
            .unwrap_or(false);
        let file = BufReader::new(File::open(path)?);
        if gz {
            InMemNiftiVolume::from_stream(GzDecoder::new(file)?, &header, endianness)
        } else {
            InMemNiftiVolume::from_stream(file, &header, endianness)
        }
    }

    /// Read a NIFTI volume, along with the extensions, from an image file. NIFTI-1 volume
    /// files usually have the extension ".img" or ".img.gz". In the latter case, the file
    /// is automatically decoded as a Gzip stream.
    pub fn from_file_with_extensions<P>(
        path: P,
        header: &NiftiHeader,
        endianness: Endianness,
        extender: Extender,
    ) -> Result<(Self, ExtensionSequence)>
    where
        P: AsRef<Path>,
    {
        let gz = path.as_ref()
            .extension()
            .map(|a| a.to_string_lossy() == "gz")
            .unwrap_or(false);
        let stream = BufReader::new(File::open(path)?);

        if gz {
            InMemNiftiVolume::from_stream_with_extensions(
                GzDecoder::new(stream)?,
                &header,
                extender,
                endianness,
            )
        } else {
            InMemNiftiVolume::from_stream_with_extensions(stream, &header, extender, endianness)
        }
    }

    /// Retrieve the raw data, consuming the volume.
    pub fn to_raw_data(self) -> Vec<u8> {
        self.raw_data
    }

    /// Retrieve a reference to the raw data.
    pub fn get_raw_data(&self) -> &[u8] {
        &self.raw_data
    }

    /// Retrieve a mutable reference to the raw data.
    pub fn get_raw_data_mut(&mut self) -> &mut [u8] {
        &mut self.raw_data
    }
}

#[cfg(feature = "ndarray_volumes")]
impl IntoNdArray for InMemNiftiVolume {
    /// Consume the volume into an ndarray.
    fn to_ndarray<T>(self) -> Result<Array<T, IxDyn>>
    where
        T: AsPrim,
        T: Clone,
        T: Num,
        T: Mul<Output = T>,
        T: Add<Output = T>,
    {
        let dim: Vec<_> = self.dim().iter().map(|d| *d as Ix).collect();

        match self.datatype {
            NiftiType::Uint8 => {
                let slope = self.scl_slope;
                let inter = self.scl_inter;
                let a = Array::from_shape_vec(IxDyn(&dim).f(), self.raw_data)
                    .expect("Inconsistent raw data size")
                    .mapv(|v| raw_to_value_via_f32(v, slope, inter));
                Ok(a)
            }
            NiftiType::Int8 => {
                let slope = self.scl_slope;
                let inter = self.scl_inter;
                let a = Array::from_shape_vec(IxDyn(&dim).f(), self.raw_data)
                    .expect("Inconsistent raw data size")
                    .mapv(|v| raw_to_value_via_f32(v, slope, inter));
                Ok(a)
            }
            NiftiType::Float32 => {
                let slope = self.scl_slope.as_();
                let inter = self.scl_inter.as_();
                let raw_data = convert_vec_f32(self.raw_data, self.endianness);
                let a = Array::from_shape_vec(IxDyn(&dim).f(), raw_data)
                    .expect("Inconsistent raw data size")
                    .mapv(|v| raw_to_value(v, slope, inter));
                Ok(a)
            }
            _ => Err(NiftiError::UnsupportedDataType(self.datatype)),
        }
    }
}

#[cfg(feature = "ndarray_volumes")]
impl<'a> IntoNdArray for &'a InMemNiftiVolume {
    /// Create an ndarray from the given volume.
    fn to_ndarray<T>(self) -> Result<Array<T, IxDyn>>
    where
        T: AsPrim,
        T: Clone,
        T: Num,
        T: Mul<Output = T>,
        T: Add<Output = T>,
    {
        self.clone().to_ndarray()
    }
}

impl<'a> NiftiVolume for &'a InMemNiftiVolume {
    fn dim(&self) -> &[u16] {
        (**self).dim()
    }

    fn dimensionality(&self) -> usize {
        (**self).dimensionality()
    }

    fn data_type(&self) -> NiftiType {
        (**self).data_type()
    }

    fn get_f32(&self, coords: &[u16]) -> Result<f32> {
        (**self).get_f32(coords)
    }

    fn get_f64(&self, coords: &[u16]) -> Result<f64> {
        (**self).get_f64(coords)
    }

    fn get_u8(&self, coords: &[u16]) -> Result<u8> {
        (**self).get_u8(coords)
    }
}

impl NiftiVolume for InMemNiftiVolume {
    fn dim(&self) -> &[u16] {
        &self.dim[1..(self.dim[0] + 1) as usize]
    }

    fn dimensionality(&self) -> usize {
        self.dim[0] as usize
    }

    fn data_type(&self) -> NiftiType {
        self.datatype
    }

    fn get_f32(&self, coords: &[u16]) -> Result<f32> {
        let index = coords_to_index(coords, self.dim())?;
        if self.datatype == NiftiType::Uint8 {
            let byte = self.raw_data[index];
            Ok(raw_to_value(byte, self.scl_slope, self.scl_inter))
        } else if self.datatype == NiftiType::Int8 {
            let byte = self.raw_data[index] as i8;
            Ok(raw_to_value(byte, self.scl_slope, self.scl_inter))
        } else {
            let range = &self.raw_data[index * self.datatype.size_of()..];
            self.datatype.read_primitive_value(
                range,
                self.endianness,
                self.scl_slope,
                self.scl_inter,
            )
        }
    }

    fn get_f64(&self, coords: &[u16]) -> Result<f64> {
        let index = coords_to_index(coords, self.dim())?;
        if self.datatype == NiftiType::Uint8 {
            let byte = self.raw_data[index];
            Ok(raw_to_value(
                byte as f64,
                self.scl_slope as f64,
                self.scl_inter as f64,
            ))
        } else {
            let range = &self.raw_data[index..];
            self.datatype.read_primitive_value(
                range,
                self.endianness,
                self.scl_slope,
                self.scl_inter,
            )
        }
    }

    fn get_u8(&self, coords: &[u16]) -> Result<u8> {
        let index = coords_to_index(coords, self.dim())?;
        if self.datatype == NiftiType::Uint8 {
            let byte = self.raw_data[index];
            Ok(raw_to_value_via_f32(byte, self.scl_slope, self.scl_inter))
        } else if self.datatype == NiftiType::Int8 {
            let byte = self.raw_data[index] as i8;
            Ok(raw_to_value_via_f32(byte, self.scl_slope, self.scl_inter))
        } else {
            let range = &self.raw_data[index * self.datatype.size_of()..];
            self.datatype.read_primitive_value(
                range,
                self.endianness,
                self.scl_slope,
                self.scl_inter,
            )
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use volume::Sliceable;
    use typedef::NiftiType;
    use util::Endianness;

    #[test]
    fn test_u8_inmem_volume() {
        let data: Vec<u8> = (0..64).map(|x| x * 2).collect();
        let vol = InMemNiftiVolume {
            dim: [3, 4, 4, 4, 0, 0, 0, 0],
            datatype: NiftiType::Uint8,
            scl_slope: 1.,
            scl_inter: -5.,
            raw_data: data,
            endianness: Endianness::LE,
        };

        let v = vol.get_f32(&[3, 1, 0]).unwrap();
        assert_eq!(v, 9.);

        let v = vol.get_f32(&[3, 3, 3]).unwrap();
        assert_eq!(v, 121.);

        let v = vol.get_f32(&[2, 1, 1]).unwrap();
        assert_eq!(v, 39.);

        assert!(vol.get_f32(&[4, 0, 0]).is_err());
    }

    #[test]
    fn test_u8_inmem_volume_slice() {
        let data: Vec<u8> = (0..64).map(|x| x * 2).collect();
        let vol = InMemNiftiVolume {
            dim: [3, 4, 4, 4, 0, 0, 0, 0],
            datatype: NiftiType::Uint8,
            scl_slope: 1.,
            scl_inter: -5.,
            raw_data: data,
            endianness: Endianness::LE,
        };

        let slice = (&vol).get_slice(0, 3).unwrap();
        assert_eq!(slice.dim(), &[4, 4]);
        assert_eq!(slice.dimensionality(), 2);

        let v = slice.get_f32(&[1, 0]).unwrap();
        assert_eq!(v, 9.);
        let v = slice.get_f32(&[3, 3]).unwrap();
        assert_eq!(v, 121.);

        let slice = (&vol).get_slice(1, 1).unwrap();
        assert_eq!(slice.dim(), &[4, 4]);
        assert_eq!(slice.dimensionality(), 2);
        let v = slice.get_f32(&[2, 1]).unwrap();
        assert_eq!(v, 39.);
    }
}
<|endoftext|>


<commit_before>use unicode_width::UnicodeWidthStr;

use crate::buffer::Buffer;
use crate::layout::Rect;
use crate::style::Style;
use crate::symbols::line;
use crate::widgets::{Block, Widget};

/// A widget to display available tabs in a multiple panels context.
///
/// # Examples
///
/// ```
/// # use tui::widgets::{Block, Borders, Tabs};
/// # use tui::style::{Style, Color};
/// # use tui::symbols::{DOT};
/// # fn main() {
/// Tabs::default()
///     .block(Block::default().title("Tabs").borders(Borders::ALL))
///     .titles(&["Tab1", "Tab2", "Tab3", "Tab4"])
///     .style(Style::default().fg(Color::White))
///     .highlight_style(Style::default().fg(Color::Yellow))
///     .divider(DOT);
/// # }
/// ```
pub struct Tabs<'a, T>
where
    T: AsRef<str> + 'a,
{
    /// A block to wrap this widget in if necessary
    block: Option<Block<'a>>,
    /// One title for each tab
    titles: &'a [T],
    /// The index of the selected tabs
    selected: usize,
    /// The style used to draw the text
    style: Style,
    /// The style used to display the selected item
    highlight_style: Style,
    /// Tab divider
    divider: &'a str,
}

impl<'a, T> Default for Tabs<'a, T>
where
    T: AsRef<str>,
{
    fn default() -> Tabs<'a, T> {
        Tabs {
            block: None,
            titles: &[],
            selected: 0,
            style: Default::default(),
            highlight_style: Default::default(),
            divider: line::VERTICAL,
        }
    }
}

impl<'a, T> Tabs<'a, T>
where
    T: AsRef<str>,
{
    pub fn block(mut self, block: Block<'a>) -> Tabs<'a, T> {
        self.block = Some(block);
        self
    }

    pub fn titles(mut self, titles: &'a [T]) -> Tabs<'a, T> {
        self.titles = titles;
        self
    }

    pub fn select(mut self, selected: usize) -> Tabs<'a, T> {
        self.selected = selected;
        self
    }

    pub fn style(mut self, style: Style) -> Tabs<'a, T> {
        self.style = style;
        self
    }

    pub fn highlight_style(mut self, style: Style) -> Tabs<'a, T> {
        self.highlight_style = style;
        self
    }

    pub fn divider(mut self, divider: &'a str) -> Tabs<'a, T> {
        self.divider = divider;
        self
    }
}

impl<'a, T> Widget for Tabs<'a, T>
where
    T: AsRef<str>,
{
    fn draw(&mut self, area: Rect, buf: &mut Buffer) {
        let tabs_area = match self.block {
            Some(ref mut b) => {
                b.draw(area, buf);
                b.inner(area)
            }
            None => area,
        };

        if tabs_area.height < 1 {
            return;
        }

        self.background(&tabs_area, buf, self.style.bg);

        let mut x = tabs_area.left();
        let titles_length = self.titles.len();
        let divider_width = self.divider.chars().count() as u16;
        for (title, style, last_title) in self.titles.iter().enumerate().map(|(i, t)| {
            let lt = i + 1 == titles_length;
            if i == self.selected {
                (t, self.highlight_style, lt)
            } else {
                (t, self.style, lt)
            }
        }) {
            x += 1;
            if x > tabs_area.right() {
                break;
            } else {
                buf.set_string(x, tabs_area.top(), title.as_ref(), style);
                x += title.as_ref().width() as u16 + 1;
                if x >= tabs_area.right() || last_title {
                    break;
                } else {
                    buf.get_mut(x, tabs_area.top())
                        .set_symbol(self.divider)
                        .set_fg(self.style.fg)
                        .set_bg(self.style.bg);
                    x += divider_width;
                }
            }
        }
    }
}
<commit_msg>Use `UnicodeWidthStr::width()`<commit_after>use unicode_width::UnicodeWidthStr;

use crate::buffer::Buffer;
use crate::layout::Rect;
use crate::style::Style;
use crate::symbols::line;
use crate::widgets::{Block, Widget};

/// A widget to display available tabs in a multiple panels context.
///
/// # Examples
///
/// ```
/// # use tui::widgets::{Block, Borders, Tabs};
/// # use tui::style::{Style, Color};
/// # use tui::symbols::{DOT};
/// # fn main() {
/// Tabs::default()
///     .block(Block::default().title("Tabs").borders(Borders::ALL))
///     .titles(&["Tab1", "Tab2", "Tab3", "Tab4"])
///     .style(Style::default().fg(Color::White))
///     .highlight_style(Style::default().fg(Color::Yellow))
///     .divider(DOT);
/// # }
/// ```
pub struct Tabs<'a, T>
where
    T: AsRef<str> + 'a,
{
    /// A block to wrap this widget in if necessary
    block: Option<Block<'a>>,
    /// One title for each tab
    titles: &'a [T],
    /// The index of the selected tabs
    selected: usize,
    /// The style used to draw the text
    style: Style,
    /// The style used to display the selected item
    highlight_style: Style,
    /// Tab divider
    divider: &'a str,
}

impl<'a, T> Default for Tabs<'a, T>
where
    T: AsRef<str>,
{
    fn default() -> Tabs<'a, T> {
        Tabs {
            block: None,
            titles: &[],
            selected: 0,
            style: Default::default(),
            highlight_style: Default::default(),
            divider: line::VERTICAL,
        }
    }
}

impl<'a, T> Tabs<'a, T>
where
    T: AsRef<str>,
{
    pub fn block(mut self, block: Block<'a>) -> Tabs<'a, T> {
        self.block = Some(block);
        self
    }

    pub fn titles(mut self, titles: &'a [T]) -> Tabs<'a, T> {
        self.titles = titles;
        self
    }

    pub fn select(mut self, selected: usize) -> Tabs<'a, T> {
        self.selected = selected;
        self
    }

    pub fn style(mut self, style: Style) -> Tabs<'a, T> {
        self.style = style;
        self
    }

    pub fn highlight_style(mut self, style: Style) -> Tabs<'a, T> {
        self.highlight_style = style;
        self
    }

    pub fn divider(mut self, divider: &'a str) -> Tabs<'a, T> {
        self.divider = divider;
        self
    }
}

impl<'a, T> Widget for Tabs<'a, T>
where
    T: AsRef<str>,
{
    fn draw(&mut self, area: Rect, buf: &mut Buffer) {
        let tabs_area = match self.block {
            Some(ref mut b) => {
                b.draw(area, buf);
                b.inner(area)
            }
            None => area,
        };

        if tabs_area.height < 1 {
            return;
        }

        self.background(&tabs_area, buf, self.style.bg);

        let mut x = tabs_area.left();
        let titles_length = self.titles.len();
        let divider_width = self.divider.width() as u16;
        for (title, style, last_title) in self.titles.iter().enumerate().map(|(i, t)| {
            let lt = i + 1 == titles_length;
            if i == self.selected {
                (t, self.highlight_style, lt)
            } else {
                (t, self.style, lt)
            }
        }) {
            x += 1;
            if x > tabs_area.right() {
                break;
            } else {
                buf.set_string(x, tabs_area.top(), title.as_ref(), style);
                x += title.as_ref().width() as u16 + 1;
                if x >= tabs_area.right() || last_title {
                    break;
                } else {
                    buf.set_string(x, tabs_area.top(), self.divider, style);
                    x += divider_width;
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>#[derive(Debug, PartialEq)]
pub struct NumericLiteral(pub f64);

#[derive(Debug, PartialEq)]
pub struct StringLiteral<'a>(pub &'a str);

pub mod parsing {

    use nom::{IResult, digit, hex_digit, oct_digit};
    use parser::helpers::parsing::*;
    use parser::js::terminals::*;

    named!(pub numeric_literal<&str, NumericLiteral>, conv!(NumericLiteral(do_parse!(
        take_while_s!(is_whitespace) >>
        numeric_literal: alt!(
            decimal_literal
            |
            hex_integer_literal
            |
            octal_integer_literal
        ) >>
        (numeric_literal)
    ))));

    named!(decimal_literal<&str, f64>, do_parse!(
        dec_lit: recognize!(alt!(
            do_parse!(
                decimal_integer_literal >>
                do_parse!(
                    opt!(complete!(do_parse!(
                        tag_s!(".") >>
                        opt!(complete!(digit)) >>
                        ()
                    ))) >>
                    opt!(complete!(exponential_part)) >>
                    ()
                ) >>
                ()
            )
            |
            do_parse!(
                tag_s!(".") >>
                complete!(digit) >>
                opt!(complete!(exponential_part)) >>
                ()
            )
        )) >>
        (dec_lit.parse::<f64>().unwrap())
    ));

    named!(decimal_integer_literal<&str, &str>, recognize!(alt!(
        do_parse!(
            tag_s!("0") >>
            not!(digit) >>
            not!(tag_s!("x")) >>
            not!(tag_s!("X")) >>
            ()
        )
        |
        do_parse!(
            not!(tag_s!("0")) >>
            digit >>
            ()
        )
    )));

    named!(exponential_part<&str, &str>, recognize!(do_parse!(
        alt!(tag_s!("e") | tag_s!("E")) >>
        opt!(alt!(tag_s!("+") | tag_s!("-"))) >>
        digit >>
        ()
    )));

    named!(hex_integer_literal<&str, f64>, do_parse!(
        alt!(tag_s!("0x") | tag_s!("0X")) >>
        hex: hex_digit >>
        (i64::from_str_radix(hex, 16).unwrap() as f64)
    ));

    named!(octal_integer_literal<&str, f64>, do_parse!(
        tag_s!("0") >>
        octal: oct_digit >>
        (i64::from_str_radix(octal, 8).unwrap() as f64)
    ));

    named!(pub string_literal<&str, StringLiteral>, do_parse!(
        delimiter: alt!(
            keyword!("\"")
            |
            keyword!("'")
        ) >>
        string: take_until_s!(delimiter) >> // TODO: escape chars
        keyword!(delimiter) >>
        (StringLiteral(string))
    ));

    named!(pub identifier<&str, &str>, do_parse!(
        take_while_s!(is_whitespace) >>
        identifier: recognize!(do_parse!(
            xid_start >>
            many0!(complete!(xid_continue)) >>
            ()
        )) >>
        (identifier)
    ));

    named!(pub js_identifier<&str, &str>, alt!(
        keyword!("property")
        |
        keyword!("signal")
        |
        keyword!("readonly")
        |
        keyword!("on")
        |
        keyword!("get")
        |
        keyword!("set")
        |
        identifier
    ));

    named!(pub reserved_identifier<&str, &str>, alt!(
        keyword!("break")
        |
        keyword!("case")
        |
        keyword!("catch")
        |
        keyword!("continue")
        |
        keyword!("default")
        |
        keyword!("delete")
        |
        keyword!("do")
        |
        keyword!("else")
        |
        keyword!("false")
        |
        keyword!("finally")
        |
        keyword!("for")
        |
        keyword!("function")
        |
        keyword!("if")
        |
        keyword!("in")
        |
        keyword!("instanceof")
        |
        keyword!("new")
        |
        keyword!("null")
        |
        keyword!("return")
        |
        keyword!("switch")
        |
        keyword!("this")
        |
        keyword!("throw")
        |
        keyword!("true")
        |
        keyword!("try")
        |
        keyword!("typeof")
        |
        keyword!("var")
        |
        keyword!("void")
        |
        keyword!("while")
        |
        keyword!("const")
        |
        keyword!("debugger")
        |
        keyword!("with")
    ));

    named!(pub property_identifier<&str, &str>, alt!(
        reserved_identifier
        |
        js_identifier
    ));

    named!(pub assignment_operator<&str, &str>, alt!(
        keyword!("=")
        |
        keyword!("*=")
        |
        keyword!("/=")
        |
        keyword!("%=")
        |
        keyword!("+=")
        |
        keyword!("-=")
        |
        keyword!("<<=")
        |
        keyword!(">>>=")
        |
        keyword!(">>=")
        |
        keyword!("&=")
        |
        keyword!("^=")
        |
        keyword!("|=")
    ));

    named!(pub automatic_semicolon<&str, &str>, alt!(
        keyword!(";")
        |
        peek!(line_terminator)
        |
        peek!(keyword!("}"))
        |
        eof
    ));

    fn eof(i: &str) -> IResult<&str, &str> {
        eof!(i,)
    }

    named!(pub line_terminator<&str, &str>, alt!(
        tag_s!("\r\n")
        |
        tag_s!("\n")
        |
        tag_s!("\u{2028}")
        |
        tag_s!("\u{2029}")
    ));

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

        #[test]
        fn numeric_literal() {
            assert!(super::numeric_literal("").is_incomplete());

            // Decimal
            assert_eq!(super::numeric_literal("42"), IResult::Done("", NumericLiteral(42_f64)));
            assert_eq!(super::numeric_literal(" 42 "), IResult::Done(" ", NumericLiteral(42_f64)));
            assert_eq!(super::numeric_literal("0"), IResult::Done("", NumericLiteral(0_f64)));
            assert_eq!(super::numeric_literal("42.42e-4"), IResult::Done("", NumericLiteral(42.42e-4_f64)));
            assert_eq!(super::numeric_literal(".42"), IResult::Done("", NumericLiteral(0.42_f64)));
            assert_eq!(super::numeric_literal("42."), IResult::Done("", NumericLiteral(42_f64)));

            assert_eq!(super::numeric_literal("+42"), IResult::Error(ErrorKind::Alt)); // Identity is unary expression
            assert_eq!(super::numeric_literal("-42"), IResult::Error(ErrorKind::Alt)); // Negation is unary expression
            assert_eq!(super::numeric_literal("."), IResult::Error(ErrorKind::Alt));

            // Octal
            assert_eq!(super::numeric_literal("042"), IResult::Done("", NumericLiteral(34_f64)));

            // Hex
            assert_eq!(super::numeric_literal("0x42"), IResult::Done("", NumericLiteral(66_f64)));
            assert_eq!(super::numeric_literal("0X42"), IResult::Done("", NumericLiteral(66_f64)));
        }

        #[test]
        fn string_literal() {
            assert!(super::string_literal("").is_incomplete());

            assert_eq!(super::string_literal("\"hi\""), IResult::Done("", StringLiteral("hi")));
            assert_eq!(super::string_literal(" 'hi' "), IResult::Done(" ", StringLiteral("hi")));

            assert_eq!(super::string_literal("\"hi"), IResult::Error(ErrorKind::TakeUntil)); // FIXME: https://github.com/Geal/nom/issues/397
            assert_eq!(super::string_literal("'hi"), IResult::Error(ErrorKind::TakeUntil));
        }

        #[test]
        fn identifier() {
            assert!(super::identifier("").is_incomplete());

            assert_eq!(super::identifier("foo"), IResult::Done("", "foo"));
            assert_eq!(super::identifier(" foo"), IResult::Done("", "foo"));
            assert_eq!(super::identifier("foo bar"), IResult::Done(" bar", "foo"));

            assert_eq!(super::identifier("42foo"), IResult::Error(ErrorKind::Verify));
        }
    }
}<commit_msg>Fix eof to take into account whitespaces<commit_after>#[derive(Debug, PartialEq)]
pub struct NumericLiteral(pub f64);

#[derive(Debug, PartialEq)]
pub struct StringLiteral<'a>(pub &'a str);

pub mod parsing {

    use nom::{digit, hex_digit, oct_digit};
    use parser::helpers::parsing::*;
    use parser::js::terminals::*;

    named!(pub numeric_literal<&str, NumericLiteral>, conv!(NumericLiteral(do_parse!(
        take_while_s!(is_whitespace) >>
        numeric_literal: alt!(
            decimal_literal
            |
            hex_integer_literal
            |
            octal_integer_literal
        ) >>
        (numeric_literal)
    ))));

    named!(decimal_literal<&str, f64>, do_parse!(
        dec_lit: recognize!(alt!(
            do_parse!(
                decimal_integer_literal >>
                do_parse!(
                    opt!(complete!(do_parse!(
                        tag_s!(".") >>
                        opt!(complete!(digit)) >>
                        ()
                    ))) >>
                    opt!(complete!(exponential_part)) >>
                    ()
                ) >>
                ()
            )
            |
            do_parse!(
                tag_s!(".") >>
                complete!(digit) >>
                opt!(complete!(exponential_part)) >>
                ()
            )
        )) >>
        (dec_lit.parse::<f64>().unwrap())
    ));

    named!(decimal_integer_literal<&str, &str>, recognize!(alt!(
        do_parse!(
            tag_s!("0") >>
            not!(digit) >>
            not!(tag_s!("x")) >>
            not!(tag_s!("X")) >>
            ()
        )
        |
        do_parse!(
            not!(tag_s!("0")) >>
            digit >>
            ()
        )
    )));

    named!(exponential_part<&str, &str>, recognize!(do_parse!(
        alt!(tag_s!("e") | tag_s!("E")) >>
        opt!(alt!(tag_s!("+") | tag_s!("-"))) >>
        digit >>
        ()
    )));

    named!(hex_integer_literal<&str, f64>, do_parse!(
        alt!(tag_s!("0x") | tag_s!("0X")) >>
        hex: hex_digit >>
        (i64::from_str_radix(hex, 16).unwrap() as f64)
    ));

    named!(octal_integer_literal<&str, f64>, do_parse!(
        tag_s!("0") >>
        octal: oct_digit >>
        (i64::from_str_radix(octal, 8).unwrap() as f64)
    ));

    named!(pub string_literal<&str, StringLiteral>, do_parse!(
        delimiter: alt!(
            keyword!("\"")
            |
            keyword!("'")
        ) >>
        string: take_until_s!(delimiter) >> // TODO: escape chars
        keyword!(delimiter) >>
        (StringLiteral(string))
    ));

    named!(pub identifier<&str, &str>, do_parse!(
        take_while_s!(is_whitespace) >>
        identifier: recognize!(do_parse!(
            xid_start >>
            many0!(complete!(xid_continue)) >>
            ()
        )) >>
        (identifier)
    ));

    named!(pub js_identifier<&str, &str>, alt!(
        keyword!("property")
        |
        keyword!("signal")
        |
        keyword!("readonly")
        |
        keyword!("on")
        |
        keyword!("get")
        |
        keyword!("set")
        |
        identifier
    ));

    named!(pub reserved_identifier<&str, &str>, alt!(
        keyword!("break")
        |
        keyword!("case")
        |
        keyword!("catch")
        |
        keyword!("continue")
        |
        keyword!("default")
        |
        keyword!("delete")
        |
        keyword!("do")
        |
        keyword!("else")
        |
        keyword!("false")
        |
        keyword!("finally")
        |
        keyword!("for")
        |
        keyword!("function")
        |
        keyword!("if")
        |
        keyword!("in")
        |
        keyword!("instanceof")
        |
        keyword!("new")
        |
        keyword!("null")
        |
        keyword!("return")
        |
        keyword!("switch")
        |
        keyword!("this")
        |
        keyword!("throw")
        |
        keyword!("true")
        |
        keyword!("try")
        |
        keyword!("typeof")
        |
        keyword!("var")
        |
        keyword!("void")
        |
        keyword!("while")
        |
        keyword!("const")
        |
        keyword!("debugger")
        |
        keyword!("with")
    ));

    named!(pub property_identifier<&str, &str>, alt!(
        reserved_identifier
        |
        js_identifier
    ));

    named!(pub assignment_operator<&str, &str>, alt!(
        keyword!("=")
        |
        keyword!("*=")
        |
        keyword!("/=")
        |
        keyword!("%=")
        |
        keyword!("+=")
        |
        keyword!("-=")
        |
        keyword!("<<=")
        |
        keyword!(">>>=")
        |
        keyword!(">>=")
        |
        keyword!("&=")
        |
        keyword!("^=")
        |
        keyword!("|=")
    ));

    named!(pub automatic_semicolon<&str, &str>, alt!(
        keyword!(";")
        |
        peek!(line_terminator)
        |
        peek!(keyword!("}"))
        |
        eof
    ));

    named!(eof<&str, &str>, do_parse!(
        take_while_s!(is_whitespace) >>
        eof: eof!() >>
        (eof)
    ));

    named!(pub line_terminator<&str, &str>, alt!(
        tag_s!("\r\n")
        |
        tag_s!("\n")
        |
        tag_s!("\u{2028}")
        |
        tag_s!("\u{2029}")
    ));

    #[cfg(test)]
    mod tests {
        use nom::{ErrorKind, IResult};
        use super::*;

        #[test]
        fn numeric_literal() {
            assert!(super::numeric_literal("").is_incomplete());

            // Decimal
            assert_eq!(super::numeric_literal("42"), IResult::Done("", NumericLiteral(42_f64)));
            assert_eq!(super::numeric_literal(" 42 "), IResult::Done(" ", NumericLiteral(42_f64)));
            assert_eq!(super::numeric_literal("0"), IResult::Done("", NumericLiteral(0_f64)));
            assert_eq!(super::numeric_literal("42.42e-4"), IResult::Done("", NumericLiteral(42.42e-4_f64)));
            assert_eq!(super::numeric_literal(".42"), IResult::Done("", NumericLiteral(0.42_f64)));
            assert_eq!(super::numeric_literal("42."), IResult::Done("", NumericLiteral(42_f64)));

            assert_eq!(super::numeric_literal("+42"), IResult::Error(ErrorKind::Alt)); // Identity is unary expression
            assert_eq!(super::numeric_literal("-42"), IResult::Error(ErrorKind::Alt)); // Negation is unary expression
            assert_eq!(super::numeric_literal("."), IResult::Error(ErrorKind::Alt));

            // Octal
            assert_eq!(super::numeric_literal("042"), IResult::Done("", NumericLiteral(34_f64)));

            // Hex
            assert_eq!(super::numeric_literal("0x42"), IResult::Done("", NumericLiteral(66_f64)));
            assert_eq!(super::numeric_literal("0X42"), IResult::Done("", NumericLiteral(66_f64)));
        }

        #[test]
        fn string_literal() {
            assert!(super::string_literal("").is_incomplete());

            assert_eq!(super::string_literal("\"hi\""), IResult::Done("", StringLiteral("hi")));
            assert_eq!(super::string_literal(" 'hi' "), IResult::Done(" ", StringLiteral("hi")));

            assert_eq!(super::string_literal("\"hi"), IResult::Error(ErrorKind::TakeUntil)); // FIXME: https://github.com/Geal/nom/issues/397
            assert_eq!(super::string_literal("'hi"), IResult::Error(ErrorKind::TakeUntil));
        }

        #[test]
        fn identifier() {
            assert!(super::identifier("").is_incomplete());

            assert_eq!(super::identifier("foo"), IResult::Done("", "foo"));
            assert_eq!(super::identifier(" foo"), IResult::Done("", "foo"));
            assert_eq!(super::identifier("foo bar"), IResult::Done(" bar", "foo"));

            assert_eq!(super::identifier("42foo"), IResult::Error(ErrorKind::Verify));
        }
    }
}<|endoftext|>


<commit_before>use std::result;

use futures::sync::oneshot;
use hyper::Method;
use serde_json::{Map, Value};

use data_model::vm::BlockDeviceConfig;

use request::{IntoParsedRequest, ParsedRequest};
use vmm::VmmAction;

#[derive(Clone)]
pub struct PatchDrivePayload {
    // Leaving `fields` pub because ownership on it needs to be yielded to the
    // Request enum object. A getter couldn't move `fields` out of the borrowed
    // PatchDrivePayload object.
    pub fields: Value,
}

impl PatchDrivePayload {
    /// Checks that `field_key` exists and that the value has the type Value::String.
    fn check_field_is_string(map: &Map<String, Value>, field_key: &str) -> Result<(), String> {
        match map.get(field_key) {
            None => {
                return Err(format!(
                    "Required key {} not present in the json.",
                    field_key
                ));
            }
            Some(id) => {
                // Check that field is a string.
                if id.as_str().is_none() {
                    return Err(format!("Invalid type for key {}.", field_key));
                }
            }
        }
        Ok(())
    }

    /// Validates that only path_on_host and drive_id are present in the payload.
    fn validate(&self) -> result::Result<(), String> {
        match self.fields.as_object() {
            Some(fields_map) => {
                // Check that field `drive_id` exists and its type is String.
                PatchDrivePayload::check_field_is_string(fields_map, "drive_id")?;
                // Check that field `drive_id` exists and its type is String.
                PatchDrivePayload::check_field_is_string(fields_map, "path_on_host")?;

                // Check that there are no other fields in the object.
                if fields_map.len() > 2 {
                    return Err(
                        "Invalid PATCH payload. Only updates on path_on_host are allowed."
                            .to_string(),
                    );
                }
                Ok(())
            }
            _ => Err("Invalid json.".to_string()),
        }
    }

    /// Returns the field specified by `field_key` as a string. This is unsafe if validate
    /// is not called prior to calling this method.
    fn get_string_field_unchecked(&self, field_key: &str) -> String {
        self.fields
            .get(field_key)
            .unwrap()
            .as_str()
            .unwrap()
            .to_string()
    }
}

impl IntoParsedRequest for PatchDrivePayload {
    fn into_parsed_request(
        self,
        id_from_path: Option<String>,
        method: Method,
    ) -> result::Result<ParsedRequest, String> {
        match method {
            Method::Patch => {
                self.validate()?;
                let drive_id: String = self.get_string_field_unchecked("drive_id");
                let path_on_host: String = self.get_string_field_unchecked("path_on_host");

                let id_from_path = id_from_path.unwrap_or(String::new());
                if id_from_path != drive_id {
                    return Err(String::from(
                        "The id from the path does not match the id from the body!",
                    ));
                }

                let (sender, receiver) = oneshot::channel();
                Ok(ParsedRequest::Sync(
                    VmmAction::UpdateBlockDevicePath(drive_id, path_on_host, sender),
                    receiver,
                ))
            }
            _ => Err(format!("Invalid method {}!", method)),
        }
    }
}

impl IntoParsedRequest for BlockDeviceConfig {
    fn into_parsed_request(
        self,
        id_from_path: Option<String>,
        method: Method,
    ) -> result::Result<ParsedRequest, String> {
        let id_from_path = id_from_path.unwrap_or(String::new());
        if id_from_path != self.drive_id {
            return Err(String::from(
                "The id from the path does not match the id from the body!",
            ));
        }
        let (sender, receiver) = oneshot::channel();
        match method {
            Method::Put => Ok(ParsedRequest::Sync(
                VmmAction::InsertBlockDevice(self, sender),
                receiver,
            )),
            _ => Err(String::from("Invalid method.")),
        }
    }
}

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

    use serde_json::Number;
    use std::path::PathBuf;

    #[test]
    fn test_patch_into_parsed_request() {
        // PATCH with invalid fields.
        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(String::from("drive_id"), Value::String(String::from("bar")));
        payload_map.insert(String::from("is_read_only"), Value::Bool(false));
        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        assert!(
            patch_payload
                .into_parsed_request(None, Method::Patch)
                .is_err()
        );

        // PATCH with invalid types on fields. Adding a drive_id as number instead of string.
        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(String::from("drive_id"), Value::Number(Number::from(1000)));
        payload_map.insert(
            String::from("path_on_host"),
            Value::String(String::from("dummy")),
        );
        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        assert!(
            patch_payload
                .into_parsed_request(None, Method::Patch)
                .is_err()
        );

        // PATCH with invalid types on fields. Adding a path_on_host as bool instead of string.
        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(
            String::from("drive_id"),
            Value::String(String::from("dummy_id")),
        );
        payload_map.insert(String::from("path_on_host"), Value::Bool(true));
        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        assert!(
            patch_payload
                .into_parsed_request(None, Method::Patch)
                .is_err()
        );

        // PATCH with missing path_on_host field.
        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(
            String::from("drive_id"),
            Value::String(String::from("dummy_id")),
        );
        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        assert!(
            patch_payload
                .into_parsed_request(None, Method::Patch)
                .is_err()
        );

        // PATCH with missing drive_id field.
        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(String::from("path_on_host"), Value::Bool(true));
        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        assert!(
            patch_payload
                .into_parsed_request(None, Method::Patch)
                .is_err()
        );

        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(String::from("drive_id"), Value::String(String::from("foo")));
        payload_map.insert(
            String::from("path_on_host"),
            Value::String(String::from("dummy")),
        );
        let pdp = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        let (sender, receiver) = oneshot::channel();

        assert!(
            pdp.clone()
                .into_parsed_request(Some("foo".to_string()), Method::Patch)
                .eq(&Ok(ParsedRequest::Sync(
                    VmmAction::UpdateBlockDevicePath(
                        "foo".to_string(),
                        "dummy".to_string(),
                        sender
                    ),
                    receiver
                )))
        );

        assert!(pdp.into_parsed_request(None, Method::Put).is_err());
    }

    #[test]
    fn test_into_parsed_request() {
        let desc = BlockDeviceConfig {
            drive_id: String::from("foo"),
            path_on_host: PathBuf::from(String::from("/foo/bar")),
            is_root_device: true,
            is_read_only: true,
            partuuid: None,
            rate_limiter: None,
        };

        assert!(
            &desc
                .clone()
                .into_parsed_request(Some(String::from("foo")), Method::Options)
                .is_err()
        );
        let (sender, receiver) = oneshot::channel();
        assert!(
            &desc
                .clone()
                .into_parsed_request(Some(String::from("foo")), Method::Put)
                .eq(&Ok(ParsedRequest::Sync(
                    VmmAction::InsertBlockDevice(desc, sender),
                    receiver
                )))
        );
    }
}
<commit_msg>api_server: added unit tests for PATCH drive<commit_after>use std::result;

use futures::sync::oneshot;
use hyper::Method;
use serde_json::{Map, Value};

use data_model::vm::BlockDeviceConfig;

use request::{IntoParsedRequest, ParsedRequest};
use vmm::VmmAction;

#[derive(Clone)]
pub struct PatchDrivePayload {
    // Leaving `fields` pub because ownership on it needs to be yielded to the
    // Request enum object. A getter couldn't move `fields` out of the borrowed
    // PatchDrivePayload object.
    pub fields: Value,
}

impl PatchDrivePayload {
    /// Checks that `field_key` exists and that the value has the type Value::String.
    fn check_field_is_string(map: &Map<String, Value>, field_key: &str) -> Result<(), String> {
        match map.get(field_key) {
            None => {
                return Err(format!(
                    "Required key {} not present in the json.",
                    field_key
                ));
            }
            Some(id) => {
                // Check that field is a string.
                if id.as_str().is_none() {
                    return Err(format!("Invalid type for key {}.", field_key));
                }
            }
        }
        Ok(())
    }

    /// Validates that only path_on_host and drive_id are present in the payload.
    fn validate(&self) -> result::Result<(), String> {
        match self.fields.as_object() {
            Some(fields_map) => {
                // Check that field `drive_id` exists and its type is String.
                PatchDrivePayload::check_field_is_string(fields_map, "drive_id")?;
                // Check that field `drive_id` exists and its type is String.
                PatchDrivePayload::check_field_is_string(fields_map, "path_on_host")?;

                // Check that there are no other fields in the object.
                if fields_map.len() > 2 {
                    return Err(
                        "Invalid PATCH payload. Only updates on path_on_host are allowed."
                            .to_string(),
                    );
                }
                Ok(())
            }
            _ => Err("Invalid json.".to_string()),
        }
    }

    /// Returns the field specified by `field_key` as a string. This is unsafe if validate
    /// is not called prior to calling this method.
    fn get_string_field_unchecked(&self, field_key: &str) -> String {
        self.fields
            .get(field_key)
            .unwrap()
            .as_str()
            .unwrap()
            .to_string()
    }
}

impl IntoParsedRequest for PatchDrivePayload {
    fn into_parsed_request(
        self,
        id_from_path: Option<String>,
        method: Method,
    ) -> result::Result<ParsedRequest, String> {
        match method {
            Method::Patch => {
                self.validate()?;
                let drive_id: String = self.get_string_field_unchecked("drive_id");
                let path_on_host: String = self.get_string_field_unchecked("path_on_host");

                let id_from_path = id_from_path.unwrap_or(String::new());
                if id_from_path != drive_id {
                    return Err(String::from(
                        "The id from the path does not match the id from the body!",
                    ));
                }

                let (sender, receiver) = oneshot::channel();
                Ok(ParsedRequest::Sync(
                    VmmAction::UpdateBlockDevicePath(drive_id, path_on_host, sender),
                    receiver,
                ))
            }
            _ => Err(format!("Invalid method {}!", method)),
        }
    }
}

impl IntoParsedRequest for BlockDeviceConfig {
    fn into_parsed_request(
        self,
        id_from_path: Option<String>,
        method: Method,
    ) -> result::Result<ParsedRequest, String> {
        let id_from_path = id_from_path.unwrap_or(String::new());
        if id_from_path != self.drive_id {
            return Err(String::from(
                "The id from the path does not match the id from the body!",
            ));
        }
        let (sender, receiver) = oneshot::channel();
        match method {
            Method::Put => Ok(ParsedRequest::Sync(
                VmmAction::InsertBlockDevice(self, sender),
                receiver,
            )),
            _ => Err(String::from("Invalid method.")),
        }
    }
}

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

    use serde_json::Number;
    use std::path::PathBuf;

    #[test]
    fn test_patch_into_parsed_request() {
        // PATCH with invalid fields.
        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(String::from("drive_id"), Value::String(String::from("bar")));
        payload_map.insert(String::from("is_read_only"), Value::Bool(false));
        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        let expected_err = Err("Required key path_on_host not present in the json.".to_string());
        assert!(patch_payload.into_parsed_request(None, Method::Patch) == expected_err);

        // PATCH with invalid types on fields. Adding a drive_id as number instead of string.
        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(String::from("drive_id"), Value::Number(Number::from(1000)));
        payload_map.insert(
            String::from("path_on_host"),
            Value::String(String::from("dummy")),
        );
        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        let expected_err = Err("Invalid type for key drive_id.".to_string());
        assert!(patch_payload.into_parsed_request(None, Method::Patch) == expected_err);

        // PATCH with invalid types on fields. Adding a path_on_host as bool instead of string.
        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(
            String::from("drive_id"),
            Value::String(String::from("dummy_id")),
        );
        payload_map.insert(String::from("path_on_host"), Value::Bool(true));
        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        assert!(
            patch_payload
                .into_parsed_request(None, Method::Patch)
                .is_err()
        );

        // PATCH with missing path_on_host field.
        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(
            String::from("drive_id"),
            Value::String(String::from("dummy_id")),
        );
        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        let expected_err = Err("Required key path_on_host not present in the json.".to_string());
        assert!(patch_payload.into_parsed_request(None, Method::Patch) == expected_err);

        // PATCH with missing drive_id field.
        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(String::from("path_on_host"), Value::Bool(true));
        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        let expected_err = Err("Required key drive_id not present in the json.".to_string());
        assert!(patch_payload.into_parsed_request(None, Method::Patch) == expected_err);

        // PATCH that tries to update something else other than path_on_host.
        let mut payload_map = Map::new();
        payload_map.insert(
            String::from("drive_id"),
            Value::String(String::from("1234")),
        );
        payload_map.insert(
            String::from("path_on_host"),
            Value::String(String::from("dummy")),
        );
        payload_map.insert(String::from("is_read_only"), Value::Bool(false));

        let patch_payload = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        let expected_err =
            Err("Invalid PATCH payload. Only updates on path_on_host are allowed.".to_string());
        assert!(patch_payload.into_parsed_request(None, Method::Patch) == expected_err);

        // PATCH with payload that is not a json.
        let patch_payload = PatchDrivePayload {
            fields: Value::String(String::from("dummy_payload")),
        };
        assert!(
            patch_payload.into_parsed_request(None, Method::Patch)
                == Err("Invalid json.".to_string())
        );

        let mut payload_map = Map::<String, Value>::new();
        payload_map.insert(String::from("drive_id"), Value::String(String::from("foo")));
        payload_map.insert(
            String::from("path_on_host"),
            Value::String(String::from("dummy")),
        );
        let pdp = PatchDrivePayload {
            fields: Value::Object(payload_map),
        };
        let (sender, receiver) = oneshot::channel();

        assert!(
            pdp.clone()
                .into_parsed_request(Some("foo".to_string()), Method::Patch)
                .eq(&Ok(ParsedRequest::Sync(
                    VmmAction::UpdateBlockDevicePath(
                        "foo".to_string(),
                        "dummy".to_string(),
                        sender
                    ),
                    receiver
                )))
        );

        assert!(
            pdp.into_parsed_request(None, Method::Put) == Err(String::from("Invalid method PUT!"))
        );
    }

    #[test]
    fn test_into_parsed_request() {
        let desc = BlockDeviceConfig {
            drive_id: String::from("foo"),
            path_on_host: PathBuf::from(String::from("/foo/bar")),
            is_root_device: true,
            is_read_only: true,
            partuuid: None,
            rate_limiter: None,
        };

        assert!(
            &desc
                .clone()
                .into_parsed_request(Some(String::from("foo")), Method::Options)
                == &Err(String::from("Invalid method."))
        );
        let (sender, receiver) = oneshot::channel();
        assert!(
            &desc
                .clone()
                .into_parsed_request(Some(String::from("foo")), Method::Put)
                .eq(&Ok(ParsedRequest::Sync(
                    VmmAction::InsertBlockDevice(desc, sender),
                    receiver
                )))
        );
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate prodbg_api;

use prodbg_api::*;

struct Line {
    opcode: String,
    regs_write: String,
    regs_read: String,
    address: u64,
    _breakpoint: bool
}

struct DisassemblyView {
    location: u64,
    address_size: u8,
    reset_to_center: bool,
    lines: Vec<Line>,
}

impl DisassemblyView {
    fn set_disassembly(&mut self, reader: &mut Reader) {
        self.lines.clear();

        for entry in reader.find_array("disassembly") {
            let address = entry.find_u64("address").ok().unwrap();
            let line = entry.find_string("line").ok().unwrap();
            let mut regs_read = String::new();
            let mut regs_write =  String::new();

            entry.find_string("registers_read").map(|regs| {
                regs_read = regs.to_owned();
            }).ok();

            entry.find_string("registers_write").map(|regs| {
                regs_write = regs.to_owned();
            }).ok();

            self.lines.push(Line {
                opcode: line.to_owned(),
                regs_read: regs_read,
                regs_write: regs_write,
                address: address,
                _breakpoint: false,
            });
        }
    }

    fn get_visible_lines_count(ui: &Ui) -> usize {
        let (_, height) = ui.get_window_size();
        let text_height = ui.get_text_line_height_with_spacing();
        // - 1.0 for title text. Would be better to get the cursor pos here instead
        let visible_lines = (height / text_height) - 1.0;
        // + 0.5 to round up
        (visible_lines + 0.5) as usize
    }

    fn request_disassembly(&mut self, ui: &mut Ui, location: u64, writer: &mut Writer) {
        let visible_lines = Self::get_visible_lines_count(ui) as u64;

        self.location = location;

        // check if we have the the location within all lines, then we don't need to request more
        for line in &self.lines {
            if line.address == location {
                return;
            }
        }

        self.reset_to_center = true;
        writer.event_begin(EVENT_GET_DISASSEMBLY as u16);
        writer.write_u64("address_start", location - (visible_lines * 4));
        writer.write_u32("instruction_count", (visible_lines * 4) as u32);
        writer.event_end();
        println!("requsted {}", visible_lines * 10);
    }

    fn color_text_reg_selection(ui: &Ui, regs_use: &Vec<&str>, line: &Line, text_height: f32) {
        let (cx, cy) = ui.get_cursor_screen_pos();
        let mut color_index = 0;
        // TODO: Allocs memory, fix
        let line_text = format!("0x{:x} {}", line.address, line.opcode);

        let colors = [
            0x00b27474,
            0x00b28050,
            0x00a9b250,
            0x0060b250,

            0x004fb292,
            0x004f71b2,
            0x008850b2,
            0x00b25091,
        ];

        //let mut color_index = 0;

        //let font_size = 14.0;
        // TODO: Offset here is hardcoded with given hex size, this should be fixed
        //let start_offset = font_size * 11.0; // 0x00000000 "

        for reg in regs_use {
            let color = colors[color_index & 7];
            line_text.find(reg).map(|offset| {
                let (tx, _) = ui.calc_text_size(&line_text, offset);
                ui.fill_rect(cx + tx, cy, 22.0, text_height, Color::from_au32(200, color));
            });

            color_index += 1;
        }

        ui.text(&line_text);
    }

    fn render_ui(&mut self, ui: &mut Ui) {
        if self.lines.len() == 0 {
            return;
        }

        let (size_x, size_h) = ui.get_window_size();
        let text_height = ui.get_text_line_height_with_spacing();
        let mut regs = String::new();
        let mut regs_pc_use = Vec::new();
        //let font_size = ui.get_font_size();

        // find registerss for pc

        for line in &self.lines {
            if line.address == self.location {
                if line.regs_read.len() > 1 || line.regs_write.len() > 1 {
                    if line.regs_read.len() > 0 {
                        regs.push_str(&line.regs_read);
                    }
                    if line.regs_write.len() > 0 {
                        regs.push(' ');
                        regs.push_str(&line.regs_write);
                    }

                    let t = regs.trim_left();
                    regs_pc_use = t.split(' ').collect();
                    break;
                }
            }
        }

        for line in &self.lines {
            if line.address == self.location {
                let (cx, cy) = ui.get_cursor_screen_pos();

                if (cy < 0.0 || cy > (size_h - text_height)) || self.reset_to_center {
                    ui.set_scroll_here(0.5);
                    self.reset_to_center = false;
                }

                ui.fill_rect(cx, cy, size_x, text_height, Color::from_argb(200, 0, 0, 127));
            }

            if regs_pc_use.len() > 0 {
                Self::color_text_reg_selection(ui, &regs_pc_use, &line, text_height);
            } else {
                ui.text_fmt(format_args!("0x{:x} {}", line.address, line.opcode));
            }
        }
    }
}

impl View for DisassemblyView {
    fn new(_: &Ui, _: &Service) -> Self {
        DisassemblyView {
            location: u64::max_value(),
            address_size: 4,
            lines: Vec::new(),
            reset_to_center: false,
        }
    }

    fn update(&mut self, ui: &mut Ui, reader: &mut Reader, writer: &mut Writer) {
        for event in reader.get_events() {
            match event {
                EVENT_SET_EXCEPTION_LOCATION => {
                    let location = reader.find_u64("address").ok().unwrap();

                    reader.find_u8("address_size").ok().map(|adress_size| {
                        self.address_size = adress_size;
                    });

                    if self.location != location {
                        self.request_disassembly(ui, location, writer);
                    }
                }

                EVENT_SET_DISASSEMBLY => {
                    self.set_disassembly(reader);
                }

                _ => (),
            }
        }

        self.render_ui(ui);
    }
}

#[no_mangle]
pub fn init_plugin(plugin_handler: &mut PluginHandler) {
    define_view_plugin!(PLUGIN, b"Disassembly2 View", DisassemblyView);
    plugin_handler.register_view(&PLUGIN);
}
<commit_msg>Inital version to set breakpoints<commit_after>#[macro_use]
extern crate prodbg_api;

use prodbg_api::*;

struct Line {
    opcode: String,
    regs_write: String,
    regs_read: String,
    address: u64,
}

///
/// Breakpoint
///
struct Breakpoint {
    address: u64,
}

///
/// Holds colors use for the disassembly view. This should be possible to configure later on.
///
/*
struct Colors {
    breakpoint: Color,
    step_cursor: Color,
    cursor: Color,
    address: Color,
    _bytes: Color,
}
*/

struct DisassemblyView {
    location: u64,
    _cursor: u64,
    breakpoint_radius: f32,
    breakpoint_spacing: f32,
    address_size: u8,
    reset_to_center: bool,
    lines: Vec<Line>,
    breakpoints: Vec<Breakpoint>,
}

impl DisassemblyView {
    fn set_disassembly(&mut self, reader: &mut Reader) {
        self.lines.clear();

        for entry in reader.find_array("disassembly") {
            let address = entry.find_u64("address").ok().unwrap();
            let line = entry.find_string("line").ok().unwrap();
            let mut regs_read = String::new();
            let mut regs_write = String::new();

            entry.find_string("registers_read").map(|regs| {
                regs_read = regs.to_owned();
            }).ok();

            entry.find_string("registers_write").map(|regs| {
                regs_write = regs.to_owned();
            }).ok();

            self.lines.push(Line {
                opcode: line.to_owned(),
                regs_read: regs_read,
                regs_write: regs_write,
                address: address,
            });
        }
    }

    ///
    /// Calculate how many visible lines we have
    ///
    fn get_visible_lines_count(ui: &Ui) -> usize {
        let (_, height) = ui.get_window_size();
        let text_height = ui.get_text_line_height_with_spacing();
        // - 1.0 for title text. Would be better to get the cursor pos here instead
        let visible_lines = (height / text_height) - 1.0;
        // + 0.5 to round up
        (visible_lines + 0.5) as usize
    }

    fn request_disassembly(&mut self, ui: &mut Ui, location: u64, writer: &mut Writer) {
        let visible_lines = Self::get_visible_lines_count(ui) as u64;

        self.location = location;

        // check if we have the the location within all lines, then we don't need to request more
        for line in &self.lines {
            if line.address == location {
                return;
            }
        }

        self.reset_to_center = true;
        writer.event_begin(EVENT_GET_DISASSEMBLY as u16);
        writer.write_u64("address_start", location - (visible_lines * 4));
        writer.write_u32("instruction_count", (visible_lines * 4) as u32);
        writer.event_end();
        println!("requsted {}", visible_lines * 10);
    }

    fn color_text_reg_selection(ui: &Ui, regs_use: &Vec<&str>, line: &Line, text_height: f32) {
        let (cx, cy) = ui.get_cursor_screen_pos();
        let mut color_index = 0;
        // TODO: Allocs memory, fix
        let line_text = format!("0x{:x} {}", line.address, line.opcode);

        let colors = [
            0x00b27474,
            0x00b28050,
            0x00a9b250,
            0x0060b250,

            0x004fb292,
            0x004f71b2,
            0x008850b2,
            0x00b25091,
        ];

        //let mut color_index = 0;

        //let font_size = 14.0;
        // TODO: Offset here is hardcoded with given hex size, this should be fixed
        //let start_offset = font_size * 11.0; // 0x00000000 "

        for reg in regs_use {
            let color = colors[color_index & 7];
            line_text.find(reg).map(|offset| {
                let (tx, _) = ui.calc_text_size(&line_text, offset);
                ui.fill_rect(cx + tx, cy, 22.0, text_height, Color::from_au32(200, color));
            });

            color_index += 1;
        }

        ui.text(&line_text);
    }

    fn toggle_breakpoint(&mut self, writer: &mut Writer) {
        // TODO: Should really be cursor_pos
        let address = self.location;

        for i in (0..self.breakpoints.len()).rev() {
            if self.breakpoints[i].address == address {
                writer.event_begin(EVENT_DELETE_BREAKPOINT as u16);
                writer.write_u64("address", address);
                writer.event_end();
                self.breakpoints.swap_remove(i);
                return;
            }
        }

        writer.event_begin(EVENT_SET_BREAKPOINT as u16);
        writer.write_u64("address", address);
        writer.event_end();

        // TODO: We shouldn't really add the breakpoint here but wait for reply
        // from the backend that we actually managed to set the breakpoint.
        // +bonus would be a "progress" icon here instead that it's being set.

        self.breakpoints.push(Breakpoint { address: self.location } );
    }

    fn has_breakpoint(&self, address: u64) -> bool {
        self.breakpoints.iter().find(|bp| bp.address == address).is_some()
    }

    fn render_ui(&mut self, ui: &mut Ui) {
        if self.lines.len() == 0 {
            return;
        }

        let (size_x, size_h) = ui.get_window_size();
        let text_height = ui.get_text_line_height_with_spacing();
        let mut regs = String::new();
        let mut regs_pc_use = Vec::new();
        //let font_size = ui.get_font_size();

        // find registerss for pc

        for line in &self.lines {
            if line.address == self.location {
                if line.regs_read.len() > 1 || line.regs_write.len() > 1 {
                    if line.regs_read.len() > 0 {
                        regs.push_str(&line.regs_read);
                    }
                    if line.regs_write.len() > 0 {
                        regs.push(' ');
                        regs.push_str(&line.regs_write);
                    }

                    let t = regs.trim_left();
                    regs_pc_use = t.split(' ').collect();
                    break;
                }
            }
        }

        for line in &self.lines {
            let (cx, cy) = ui.get_cursor_screen_pos();
            let bp_radius = self.breakpoint_radius;

            if line.address == self.location {
                if (cy < 0.0 || cy > (size_h - text_height)) || self.reset_to_center {
                    ui.set_scroll_here(0.5);
                    self.reset_to_center = false;
                }

                ui.fill_rect(cx, cy, size_x, text_height, Color::from_argb(200, 0, 0, 127));
            }

            if regs_pc_use.len() > 0 {
                Self::color_text_reg_selection(ui, &regs_pc_use, &line, text_height);
            } else {
                ui.text_fmt(format_args!("   0x{:x} {}", line.address, line.opcode));
            }

            if self.has_breakpoint(line.address) {
                    ui.fill_circle(&Vec2{ x: cx + self.breakpoint_spacing + bp_radius, y: cy + bp_radius + 2.0},
                                   bp_radius, Color::from_argb(255,0,0,140), 12, false);
            }
        }

        /*
        let arrow: [Vec2; 3] = [
            Vec2::new(112.0, 122.0),
            Vec2::new(212.0, 222.0),
            Vec2::new(12.0, 322.0),
        ];
        */

        //ui.fill_convex_poly(&arrow, Color::from_argb(255,0,0,200), true);
        //ui.fill_circle(&Vec2{ x: 112.0, y: 112.0}, 12.0, Color::from_argb(255,0,0,200), 12, false);
        //ui.fill_circle(&Vec2{ x: 112.0, y: 112.0}, 12.0, Color::from_argb(255,0,0,200), 12, false);
    }
}

impl View for DisassemblyView {
    fn new(_: &Ui, _: &Service) -> Self {
        DisassemblyView {
            location: u64::max_value(),
            _cursor: u64::max_value(),
            breakpoint_radius: 10.0,
            breakpoint_spacing: 6.0,
            address_size: 4,
            lines: Vec::new(),
            breakpoints: Vec::new(),
            reset_to_center: false,
        }
    }

    fn update(&mut self, ui: &mut Ui, reader: &mut Reader, writer: &mut Writer) {
        for event in reader.get_events() {
            match event {
                EVENT_SET_EXCEPTION_LOCATION => {
                    let location = reader.find_u64("address").ok().unwrap();

                    reader.find_u8("address_size").ok().map(|adress_size| {
                        self.address_size = adress_size;
                    });

                    if self.location != location {
                        self.request_disassembly(ui, location, writer);
                    }
                }

                EVENT_SET_DISASSEMBLY => {
                    self.set_disassembly(reader);
                }

                _ => (),
            }
        }

        if ui.is_key_down(Key::F9) {
            self.toggle_breakpoint(writer);
        }

        self.render_ui(ui);
    }
}

#[no_mangle]
pub fn init_plugin(plugin_handler: &mut PluginHandler) {
    define_view_plugin!(PLUGIN, b"Disassembly2 View", DisassemblyView);
    plugin_handler.register_view(&PLUGIN);
}
<|endoftext|>


<commit_before>use arc = std::arc;
use arc::ARC;
use au = gfx::geometry;
use font::{RunMetrics, Font};
use font_cache::FontCache;
use geom::point::Point2D;
use geom::size::Size2D;
use gfx::geometry::au;
use glyph::GlyphStore;
use layout::context::LayoutContext;
use libc::{c_void};
use servo_util::color;
use shaper::shape_textrun;
use std::arc;

pub struct TextRun {
    text: ~str,
    font: @Font,
    priv glyphs: GlyphStore,
}

// This is a hack until TextRuns are normally sendable, or
// we instead use ARC<TextRun> everywhere.
pub struct SendableTextRun {
    text: ~str,
    font_descriptor: (),
    priv glyphs: GlyphStore,
}

pub fn serialize(_cache: @FontCache, run: &TextRun) -> ~SendableTextRun {
    ~SendableTextRun {
        text: copy run.text,
        // TODO: actually serialize a font descriptor thingy
        font_descriptor: (),
        glyphs: copy run.glyphs,
    }
}

pub fn deserialize(cache: @FontCache, run: &SendableTextRun) -> @TextRun {
    @TextRun {
        text: copy run.text,
        // TODO: actually deserialize a font descriptor thingy
        font: cache.get_test_font(),
        glyphs: copy run.glyphs
    }
}

trait TextRunMethods {
    pure fn glyphs(&self) -> &self/GlyphStore;
    pure fn iter_indivisible_pieces_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool);
    // TODO: needs to take box style as argument, or move to TextBox.
    // see Gecko's IsTrimmableSpace methods for details.
    pure fn range_is_trimmable_whitespace(&self, offset: uint, length: uint) -> bool;

    fn metrics_for_range(offset: uint, length: uint) -> RunMetrics;
    fn min_width_for_range(offset: uint, length: uint) -> au;
    fn iter_natural_lines_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool);
}

impl TextRun : TextRunMethods {
    pure fn glyphs(&self) -> &self/GlyphStore { &self.glyphs }

    pure fn range_is_trimmable_whitespace(&self, offset: uint, length: uint) -> bool {
        let mut i = offset;
        while i < offset + length {
            let {ch, next} = str::char_range_at(self.text, i);
            match ch {
                ' ' | '\t' | '\r'  => {},
                _ => { return false; }
            }
            i = next;
        }
        return true;
    }

    fn metrics_for_range(offset: uint, length: uint) -> RunMetrics {
        self.font.measure_text(&self, offset, length)
    }

    fn min_width_for_range(offset: uint, length: uint) -> au {
        assert length > 0;
        assert offset < self.text.len();
        assert offset + length <= self.text.len();

        let mut max_piece_width = au(0);
        for self.iter_indivisible_pieces_for_range(offset, length) |piece_offset, piece_len| {
            let metrics = self.font.measure_text(&self, piece_offset, piece_len);
            if metrics.advance_width > max_piece_width {
                max_piece_width = metrics.advance_width;
            }
        };

        return max_piece_width;
    }

    fn iter_natural_lines_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool) {
        assert length > 0;
        assert offset < self.text.len();
        assert offset + length <= self.text.len();

        let mut clump_start = offset;
        let mut clump_end = offset;
        let mut in_clump = false;

        // clump non-linebreaks of nonzero length
        for uint::range(offset, offset + length) |i| {
            match (self.glyphs.char_is_newline(i), in_clump) {
                (false, true)  => { clump_end = i; }
                (false, false) => { in_clump = true; clump_start = i; clump_end = i; }
                (true, false) => { /* chomp whitespace */ }
                (true, true)  => {
                    in_clump = false;
                    // don't include the linebreak 'glyph'
                    // (we assume there's one GlyphEntry for a newline, and no actual glyphs)
                    if !f(clump_start, clump_end - clump_start + 1) { break }
                }
            }
        }

        // flush any remaining chars as a line
        if in_clump {
            clump_end = offset + length - 1;
            f(clump_start, clump_end - clump_start + 1);
        }
    }

    pure fn iter_indivisible_pieces_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool) {
        assert length > 0;
        assert offset < self.text.len();
        assert offset + length <= self.text.len();

        //TODO: need a more sophisticated model of words and possible breaks
        let text = str::view(self.text, offset, length);

        let mut clump_start = offset;

        loop {
            // clump contiguous non-whitespace
            match str::find_from(text, clump_start, |c| !char::is_whitespace(c)) {
                Some(clump_end) => {
                    if !f(clump_start, clump_end - clump_start) { break }
                    clump_start = clump_end;
                    // reached end
                    if clump_start == offset + length { break }
                },
                None => {
                    // nothing left, flush last piece containing only whitespace
                    if clump_start < offset + length {
                        let clump_end = offset + length - 1;
                        f(clump_start, clump_end - clump_start + 1);
                    }
                    break
                }
            };

            // clump contiguous whitespace
            match str::find_from(text, clump_start, |c| char::is_whitespace(c)) {
                Some(clump_end) => {
                    if !f(clump_start, clump_end - clump_start) { break }
                    clump_start = clump_end;
                    // reached end
                    if clump_start == offset + length { break }
                }
                None => {
                    // nothing left, flush last piece containing only non-whitespaces
                    if clump_start < offset + length {
                        let clump_end = offset + length - 1;
                        f(clump_start, clump_end - clump_start + 1);
                    }
                    break
                }
            }
        }
    }
}

fn TextRun(font: @Font, text: ~str) -> TextRun {
    let glyph_store = GlyphStore(text.len());
    let run = TextRun {
        text: text,
        font: font,
        glyphs: glyph_store,
    };

    shape_textrun(&run);
    return run;
}

// this test can't run until LayoutContext is removed as an argument
// to min_width_for_range.
/*
#[test]
fn test_calc_min_break_width() {

    fn test_min_width_for_run(text: ~str, width: au) {
        let flib = FontCache();
        let font = flib.get_test_font();
        let run = TextRun(font, text);
        run.min_width_for_range(0, text.len())
    }

    test_min_width_for_run(~"firecracker", au::from_px(84));
    test_min_width_for_run(~"firecracker yumyum", au::from_px(84));
    test_min_width_for_run(~"yumyum firecracker", au::from_px(84));
    test_min_width_for_run(~"yumyum firecracker yumyum", au::from_px(84));
}
*/

#[test]
#[ignore]
fn test_iter_indivisible_pieces() {
    fn test_pieces(text: ~str, res: ~[~str]) {
        let flib = FontCache();
        let font = flib.get_test_font();
        let run = TextRun(font, copy text);
        let mut slices : ~[~str] = ~[];
        for run.iter_indivisible_pieces_for_range(0, text.len()) |offset, length| {
            slices.push(str::slice(text, offset, length));
        }
        assert slices == res;
    }

    test_pieces(~"firecracker yumyum woopwoop", ~[~"firecracker", ~" ", ~"yumyum", ~" ", ~"woopwoop"]);
    test_pieces(~"firecracker yumyum     ", ~[~"firecracker", ~" ", ~"yumyum", ~"     "]);
    test_pieces(~"     firecracker yumyum", ~[~"     ", ~"firecracker", ~" ", ~"yumyum"]);
    test_pieces(~"  ", ~[~"  "]);
    test_pieces(~"", ~[]);
}

<commit_msg>Rewrite iter_indivisible_pieces_for_range in idiomatic offset+length parlance.<commit_after>use arc = std::arc;
use arc::ARC;
use au = gfx::geometry;
use font::{RunMetrics, Font};
use font_cache::FontCache;
use geom::point::Point2D;
use geom::size::Size2D;
use gfx::geometry::au;
use glyph::GlyphStore;
use layout::context::LayoutContext;
use libc::{c_void};
use servo_util::color;
use shaper::shape_textrun;
use std::arc;

pub struct TextRun {
    text: ~str,
    font: @Font,
    priv glyphs: GlyphStore,
}

// This is a hack until TextRuns are normally sendable, or
// we instead use ARC<TextRun> everywhere.
pub struct SendableTextRun {
    text: ~str,
    font_descriptor: (),
    priv glyphs: GlyphStore,
}

pub fn serialize(_cache: @FontCache, run: &TextRun) -> ~SendableTextRun {
    ~SendableTextRun {
        text: copy run.text,
        // TODO: actually serialize a font descriptor thingy
        font_descriptor: (),
        glyphs: copy run.glyphs,
    }
}

pub fn deserialize(cache: @FontCache, run: &SendableTextRun) -> @TextRun {
    @TextRun {
        text: copy run.text,
        // TODO: actually deserialize a font descriptor thingy
        font: cache.get_test_font(),
        glyphs: copy run.glyphs
    }
}

trait TextRunMethods {
    pure fn glyphs(&self) -> &self/GlyphStore;
    pure fn iter_indivisible_pieces_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool);
    // TODO: needs to take box style as argument, or move to TextBox.
    // see Gecko's IsTrimmableSpace methods for details.
    pure fn range_is_trimmable_whitespace(&self, offset: uint, length: uint) -> bool;

    fn metrics_for_range(offset: uint, length: uint) -> RunMetrics;
    fn min_width_for_range(offset: uint, length: uint) -> au;
    fn iter_natural_lines_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool);
}

impl TextRun : TextRunMethods {
    pure fn glyphs(&self) -> &self/GlyphStore { &self.glyphs }

    pure fn range_is_trimmable_whitespace(&self, offset: uint, length: uint) -> bool {
        let mut i = offset;
        while i < offset + length {
            let {ch, next} = str::char_range_at(self.text, i);
            match ch {
                ' ' | '\t' | '\r'  => {},
                _ => { return false; }
            }
            i = next;
        }
        return true;
    }

    fn metrics_for_range(offset: uint, length: uint) -> RunMetrics {
        self.font.measure_text(&self, offset, length)
    }

    fn min_width_for_range(offset: uint, length: uint) -> au {
        assert length > 0;
        assert offset < self.text.len();
        assert offset + length <= self.text.len();

        let mut max_piece_width = au(0);
        for self.iter_indivisible_pieces_for_range(offset, length) |piece_offset, piece_len| {
            let metrics = self.font.measure_text(&self, piece_offset, piece_len);
            if metrics.advance_width > max_piece_width {
                max_piece_width = metrics.advance_width;
            }
        };

        return max_piece_width;
    }

    fn iter_natural_lines_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool) {
        assert length > 0;
        assert offset < self.text.len();
        assert offset + length <= self.text.len();

        let mut clump_start = offset;
        let mut clump_end = offset;
        let mut in_clump = false;

        // clump non-linebreaks of nonzero length
        for uint::range(offset, offset + length) |i| {
            match (self.glyphs.char_is_newline(i), in_clump) {
                (false, true)  => { clump_end = i; }
                (false, false) => { in_clump = true; clump_start = i; clump_end = i; }
                (true, false) => { /* chomp whitespace */ }
                (true, true)  => {
                    in_clump = false;
                    // don't include the linebreak 'glyph'
                    // (we assume there's one GlyphEntry for a newline, and no actual glyphs)
                    if !f(clump_start, clump_end - clump_start + 1) { break }
                }
            }
        }

        // flush any remaining chars as a line
        if in_clump {
            clump_end = offset + length - 1;
            f(clump_start, clump_end - clump_start + 1);
        }
    }

    pure fn iter_indivisible_pieces_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool) {
        assert length > 0;
        assert offset < self.text.len();
        assert offset + length <= self.text.len();

        let mut clump_offset = offset;
        let mut clump_length;

        loop {
            // find next non-whitespace byte index, then clump all whitespace before it.
            match str::find_from(self.text, clump_offset, |c| !char::is_whitespace(c)) {
                Some(nonws_char_offset) => {
                    clump_length = nonws_char_offset - clump_offset;
                    if !f(clump_offset, clump_length) { break }
                    clump_offset += clump_length;
                    // reached end
                    if clump_offset == offset + length { break }
                },
                None => {
                    // nothing left, flush last piece containing only whitespace
                    if clump_offset < offset + length {
                        let clump_length = (offset + length) - clump_offset;
                        f(clump_offset, clump_length);
                    }
                    break
                }
            };

            // find next whitespace byte index, then clump all non-whitespace before it.
            match str::find_from(self.text, clump_offset, |c| char::is_whitespace(c)) {
                Some(ws_char_offset) => {
                    clump_length = ws_char_offset - clump_offset;
                    if !f(clump_offset, clump_length) { break }
                    clump_offset += clump_length;
                    // reached end
                    if clump_offset == offset + length { break }
                }
                None => {
                    // nothing left, flush last piece containing only non-whitespaces
                    if clump_offset < offset + length {
                        let clump_length = (offset + length) - clump_offset;
                        f(clump_offset, clump_length);
                    }
                    break
                }
            }
        }
    }
}

fn TextRun(font: @Font, text: ~str) -> TextRun {
    let glyph_store = GlyphStore(text.len());
    let run = TextRun {
        text: text,
        font: font,
        glyphs: glyph_store,
    };

    shape_textrun(&run);
    return run;
}

// this test can't run until LayoutContext is removed as an argument
// to min_width_for_range.
/*
#[test]
fn test_calc_min_break_width() {

    fn test_min_width_for_run(text: ~str, width: au) {
        let flib = FontCache();
        let font = flib.get_test_font();
        let run = TextRun(font, text);
        run.min_width_for_range(0, text.len())
    }

    test_min_width_for_run(~"firecracker", au::from_px(84));
    test_min_width_for_run(~"firecracker yumyum", au::from_px(84));
    test_min_width_for_run(~"yumyum firecracker", au::from_px(84));
    test_min_width_for_run(~"yumyum firecracker yumyum", au::from_px(84));
}
*/

#[test]
#[ignore]
fn test_iter_indivisible_pieces() {
    fn test_pieces(text: ~str, res: ~[~str]) {
        let flib = FontCache();
        let font = flib.get_test_font();
        let run = TextRun(font, copy text);
        let mut slices : ~[~str] = ~[];
        for run.iter_indivisible_pieces_for_range(0, text.len()) |offset, length| {
            slices.push(str::slice(text, offset, length));
        }
        assert slices == res;
    }

    test_pieces(~"firecracker yumyum woopwoop", ~[~"firecracker", ~" ", ~"yumyum", ~" ", ~"woopwoop"]);
    test_pieces(~"firecracker yumyum     ", ~[~"firecracker", ~" ", ~"yumyum", ~"     "]);
    test_pieces(~"     firecracker yumyum", ~[~"     ", ~"firecracker", ~" ", ~"yumyum"]);
    test_pieces(~"  ", ~[~"  "]);
    test_pieces(~"", ~[]);
}

<|endoftext|>