<commit_before>//! Wrapper for `std::io::Read`.

use std::fmt;
use std::mem;
use std::io;

macro_rules! impl_read_little_endian_integer {
    ($ty:ident, $name:ident, $size:expr) => {
/// Reads a little-endian value and returns it.
        fn $name(&mut self) -> io::Result<$ty> {
            assert_eq!($size, mem::size_of::<$ty>());

            let mut data: $ty = 0;
            let mut slice = unsafe {
                ::std::slice::from_raw_parts_mut(&mut data as *mut $ty as *mut u8, $size)
            };
            self.read_exact(slice)?;
            Ok($ty::from_le(data))
        }
    }
}

macro_rules! impl_read_little_endian_integer_array {
    ($ty:ident, $name:ident, $size:expr) => {
/// Reads a little-endian values into the given buffer.
        fn $name(&mut self, buf: &mut [$ty]) -> io::Result<()> {
            assert_eq!($size, mem::size_of::<$ty>());

            {
                let mut slice = unsafe {
                    ::std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, $size * buf.len())
                };
                self.read_exact(slice)?;
            }
            for elem in buf {
                *elem = $ty::from_le(*elem);
            }
            Ok(())
        }
    }
}

/// The `ReadLittleEndian` trait allows for reading little-endian primitive type values from a
/// source.
pub trait ReadLittleEndian: io::Read {
    impl_read_little_endian_integer!(u8, read_u8, 1);
    impl_read_little_endian_integer!(u32, read_u32, 4);
    impl_read_little_endian_integer!(u64, read_u64, 8);
    impl_read_little_endian_integer!(i16, read_i16, 2);
    impl_read_little_endian_integer!(i32, read_i32, 4);
    impl_read_little_endian_integer!(i64, read_i64, 8);

    /// Reads a little-endian value and returns it.
    fn read_f32(&mut self) -> io::Result<f32> {
        let integer = self.read_u32()?;
        let val = unsafe { mem::transmute(integer) };
        Ok(val)
    }

    /// Reads a little-endian value and returns it.
    fn read_f64(&mut self) -> io::Result<f64> {
        let integer = self.read_u64()?;
        let val = unsafe { mem::transmute(integer) };
        Ok(val)
    }

    impl_read_little_endian_integer_array!(u8, read_u8_arr, 1);
    impl_read_little_endian_integer_array!(u32, read_u32_arr, 4);
    impl_read_little_endian_integer_array!(u64, read_u64_arr, 8);
    impl_read_little_endian_integer_array!(i16, read_i16_arr, 2);
    impl_read_little_endian_integer_array!(i32, read_i32_arr, 4);
    impl_read_little_endian_integer_array!(i64, read_i64_arr, 8);

    /// Reads a little-endian values into the given buffer.
    fn read_f32_arr(&mut self, buf: &mut [f32]) -> io::Result<()> {
        self.read_u32_arr(unsafe { mem::transmute(buf) })
    }

    /// Reads a little-endian values into the given buffer.
    fn read_f64_arr(&mut self, buf: &mut [f64]) -> io::Result<()> {
        self.read_u64_arr(unsafe { mem::transmute(buf) })
    }
}

impl<R: io::Read> ReadLittleEndian for R {}

/// Source stream for `BinaryParser`.
pub trait ParserSource: fmt::Debug + io::Read {
    /// Returns the current position from the start of the stream.
    fn position(&self) -> u64;

    /// Skips to the given position.
    ///
    /// # Panics
    /// Panics if a byte at the given position has been already read.
    fn skip_to(&mut self, dest_pos: u64) -> io::Result<()>;
}

/// Reader with position info.
pub struct BasicSource<R> {
    /// Source reader.
    source: R,
    /// Current position from the start of the stream..
    position: u64,
}

impl<R: io::Read> BasicSource<R> {
    /// Creates a new `BasicSource`.
    pub fn new(source: R) -> Self {
        BasicSource {
            source: source,
            position: 0,
        }
    }
}

impl<R: io::Read> io::Read for BasicSource<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let read_len = self.source.read(buf)?;
        self.position += read_len as u64;
        Ok(read_len)
    }

    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
        self.source.read_exact(buf)?;
        self.position += buf.len() as u64;
        Ok(())
    }
}

impl<R> fmt::Debug for BasicSource<R> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("BasicSource")
            .field("position", &self.position)
            .finish()
    }
}

impl<R: io::Read> ParserSource for BasicSource<R> {
    fn position(&self) -> u64 {
        self.position
    }

    fn skip_to(&mut self, dest_pos: u64) -> io::Result<()> {
        use std::io::Read;

        assert!(dest_pos >= self.position(),
                "Destination position should be after current position: dest_pos={}, position={}",
                dest_pos,
                self.position());
        const TEMP_BUF_LEN: usize = 256;
        let mut temp_buf = [0u8; TEMP_BUF_LEN];
        let mut rest_len = dest_pos - self.position();
        while rest_len > TEMP_BUF_LEN as u64 {
            self.read_exact(&mut temp_buf)?;
            rest_len -= TEMP_BUF_LEN as u64;
        }
        self.read_exact(&mut temp_buf[0..rest_len as usize])?;

        assert_eq!(self.position(), dest_pos);
        Ok(())
    }
}

/// Reader with position info and seek feature.
///
/// This wrapper doesn't manage offset, i.e. the start of the source stream should be the start of
/// the FBX data.
pub struct SeekableSource<R> {
    /// Source reader.
    source: R,
    /// Current position from the start of the stream..
    position: u64,
}

impl<R: io::Read + io::Seek> SeekableSource<R> {
    /// Creates a new `SeekableSource`.
    pub fn new(source: R) -> Self {
        SeekableSource {
            source: source,
            position: 0,
        }
    }
}

impl<R: io::Read> io::Read for SeekableSource<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let read_len = self.source.read(buf)?;
        self.position += read_len as u64;
        Ok(read_len)
    }

    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
        self.source.read_exact(buf)?;
        self.position += buf.len() as u64;
        Ok(())
    }
}

impl<R: io::Read + io::Seek> io::Seek for SeekableSource<R> {
    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
        self.position = self.source.seek(pos)?;
        Ok(self.position)
    }
}

impl<R> fmt::Debug for SeekableSource<R> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("SeekableSource")
            .field("position", &self.position)
            .finish()
    }
}

impl<R: io::Read + io::Seek> ParserSource for SeekableSource<R> {
    fn position(&self) -> u64 {
        self.position
    }

    fn skip_to(&mut self, dest_pos: u64) -> io::Result<()> {
        use std::io::{Seek, SeekFrom};

        assert!(dest_pos >= self.position(),
                "Destination position should be after current position: dest_pos={}, position={}",
                dest_pos,
                self.position());
        self.seek(SeekFrom::Start(dest_pos))?;

        assert_eq!(self.position(), dest_pos);
        Ok(())
    }
}

/// Reader which can read and seek limited area of a stream.
pub struct LimitedSeekReader<R> {
    /// Source stream.
    source: R,
    /// Current position
    current: u64,
    /// Start position.
    begin: u64,
    /// End position.
    end: u64,
}

impl<R: io::Read> LimitedSeekReader<R> {
    /// Creates a new `LimitedSeekReader`.
    pub fn new(source: R, current: u64, begin: u64, end: u64) -> Self {
        assert!(begin <= end);
        assert!(begin <= current);
        assert!(current <= end);

        LimitedSeekReader {
            source: source,
            current: current,
            begin: begin,
            end: end,
        }
    }
}

impl<R> LimitedSeekReader<R> {
    /// Returns length of region allowed to be read in bytes.
    pub fn len(&self) -> u64 {
        assert!(self.begin <= self.end);
        self.end - self.begin
    }

    /// Returns `true` if the reader can read no data.
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns rest length of region allowed to be read in bytes.
    fn rest_len(&self) -> u64 {
        assert!(self.current <= self.end);
        self.end - self.current
    }

    /// Returns the current position relative to the start of the limited region.
    fn rel_pos(&self) -> u64 {
        assert!(self.begin <= self.current);
        self.current - self.begin
    }
}

impl<R: io::Read> io::Read for LimitedSeekReader<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let limit = self.rest_len();
        let size = self.source.by_ref().take(limit).read(buf)?;
        self.current += size as u64;
        Ok(size)
    }
}

impl<R: io::Seek> io::Seek for LimitedSeekReader<R> {
    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
        match pos {
            io::SeekFrom::Start(val) => {
                let offset = ::std::cmp::min(self.len(), val);
                let target = self.begin + offset;
                assert!(target >= self.begin);
                assert!(target <= self.end);
                self.current = self.source.seek(io::SeekFrom::Start(target))?;
            },
            io::SeekFrom::Current(val) => {
                let offset = if val < 0 {
                    ::std::cmp::max(-(self.rel_pos() as i64), val)
                } else {
                    ::std::cmp::min(self.rest_len(), val as u64) as i64
                };
                let target = self.current as i64 + offset;
                assert!(target as u64 >= self.begin);
                assert!(target as u64 <= self.end);
                self.current = self.source.seek(io::SeekFrom::Current(offset))?;
            },
            io::SeekFrom::End(val) => {
                let offset = ::std::cmp::max(-(self.len() as i64), ::std::cmp::min(0, val));
                let target = (self.end as i64 + offset) as u64;
                assert!(target >= self.begin);
                assert!(target <= self.end);
                self.current = self.source.seek(io::SeekFrom::Start(target))?;
            },
        }
        Ok(self.rel_pos())
    }
}

impl<R> fmt::Debug for LimitedSeekReader<R> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("LimitedSeekReader")
            .field("current", &self.current)
            .field("begin", &self.begin)
            .field("end", &self.end)
            .finish()
    }
}

#[cfg(test)]
mod tests {
    use std::io::{Cursor, Seek, SeekFrom};
    use super::{ParserSource, BasicSource, SeekableSource};

    fn do_test_skip_to(buf_size: usize, skip_dest: u64) {
        do_test_basic_skip_to(buf_size, skip_dest);
        do_test_seekable_skip_to(buf_size, skip_dest);
    }

    fn do_test_basic_skip_to(buf_size: usize, skip_dest: u64) {
        let mut short_buf = Cursor::new(vec![0; buf_size]);
        let short_count = {
            let mut reader = BasicSource::new(&mut short_buf);
            reader.skip_to(skip_dest).expect("Failed to skip");
            reader.position()
        };
        assert_eq!(short_count, skip_dest);
        assert_eq!(short_count,
                   short_buf.seek(SeekFrom::Current(0)).expect("Failed to seek"));
    }

    fn do_test_seekable_skip_to(buf_size: usize, skip_dest: u64) {
        let mut short_buf = Cursor::new(vec![0; buf_size]);
        let mut reader = SeekableSource::new(&mut short_buf);
        reader.skip_to(skip_dest).expect("Failed to skip");
        assert_eq!(reader.position(), skip_dest);
        reader.seek(SeekFrom::End(0)).expect("Failed to seek");;
        assert_eq!(reader.position(), buf_size as u64);
        reader.seek(SeekFrom::Start(0)).expect("Failed to seek");
        assert_eq!(reader.position(), 0);
    }

    #[test]
    fn test_skip_to() {
        do_test_skip_to(0, 0);
        do_test_skip_to(15, 0);
        do_test_skip_to(30, 23);
        do_test_skip_to(512, 401);
        do_test_skip_to(64, 64);
    }
}
<commit_msg>Implement `BufRead` for parser sources and streams<commit_after>//! Wrapper for `std::io::Read`.

use std::fmt;
use std::mem;
use std::io;

macro_rules! impl_read_little_endian_integer {
    ($ty:ident, $name:ident, $size:expr) => {
/// Reads a little-endian value and returns it.
        fn $name(&mut self) -> io::Result<$ty> {
            assert_eq!($size, mem::size_of::<$ty>());

            let mut data: $ty = 0;
            let mut slice = unsafe {
                ::std::slice::from_raw_parts_mut(&mut data as *mut $ty as *mut u8, $size)
            };
            self.read_exact(slice)?;
            Ok($ty::from_le(data))
        }
    }
}

macro_rules! impl_read_little_endian_integer_array {
    ($ty:ident, $name:ident, $size:expr) => {
/// Reads a little-endian values into the given buffer.
        fn $name(&mut self, buf: &mut [$ty]) -> io::Result<()> {
            assert_eq!($size, mem::size_of::<$ty>());

            {
                let mut slice = unsafe {
                    ::std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, $size * buf.len())
                };
                self.read_exact(slice)?;
            }
            for elem in buf {
                *elem = $ty::from_le(*elem);
            }
            Ok(())
        }
    }
}

/// The `ReadLittleEndian` trait allows for reading little-endian primitive type values from a
/// source.
pub trait ReadLittleEndian: io::Read {
    impl_read_little_endian_integer!(u8, read_u8, 1);
    impl_read_little_endian_integer!(u32, read_u32, 4);
    impl_read_little_endian_integer!(u64, read_u64, 8);
    impl_read_little_endian_integer!(i16, read_i16, 2);
    impl_read_little_endian_integer!(i32, read_i32, 4);
    impl_read_little_endian_integer!(i64, read_i64, 8);

    /// Reads a little-endian value and returns it.
    fn read_f32(&mut self) -> io::Result<f32> {
        let integer = self.read_u32()?;
        let val = unsafe { mem::transmute(integer) };
        Ok(val)
    }

    /// Reads a little-endian value and returns it.
    fn read_f64(&mut self) -> io::Result<f64> {
        let integer = self.read_u64()?;
        let val = unsafe { mem::transmute(integer) };
        Ok(val)
    }

    impl_read_little_endian_integer_array!(u8, read_u8_arr, 1);
    impl_read_little_endian_integer_array!(u32, read_u32_arr, 4);
    impl_read_little_endian_integer_array!(u64, read_u64_arr, 8);
    impl_read_little_endian_integer_array!(i16, read_i16_arr, 2);
    impl_read_little_endian_integer_array!(i32, read_i32_arr, 4);
    impl_read_little_endian_integer_array!(i64, read_i64_arr, 8);

    /// Reads a little-endian values into the given buffer.
    fn read_f32_arr(&mut self, buf: &mut [f32]) -> io::Result<()> {
        self.read_u32_arr(unsafe { mem::transmute(buf) })
    }

    /// Reads a little-endian values into the given buffer.
    fn read_f64_arr(&mut self, buf: &mut [f64]) -> io::Result<()> {
        self.read_u64_arr(unsafe { mem::transmute(buf) })
    }
}

impl<R: io::Read> ReadLittleEndian for R {}

/// Source stream for `BinaryParser`.
pub trait ParserSource: fmt::Debug + io::Read {
    /// Returns the current position from the start of the stream.
    fn position(&self) -> u64;

    /// Skips to the given position.
    ///
    /// # Panics
    /// Panics if a byte at the given position has been already read.
    fn skip_to(&mut self, dest_pos: u64) -> io::Result<()>;
}

/// Reader with position info.
pub struct BasicSource<R> {
    /// Source reader.
    source: R,
    /// Current position from the start of the stream..
    position: u64,
}

impl<R: io::Read> BasicSource<R> {
    /// Creates a new `BasicSource`.
    pub fn new(source: R) -> Self {
        BasicSource {
            source: source,
            position: 0,
        }
    }
}

impl<R: io::Read> io::Read for BasicSource<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let read_len = self.source.read(buf)?;
        self.position += read_len as u64;
        Ok(read_len)
    }

    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
        self.source.read_exact(buf)?;
        self.position += buf.len() as u64;
        Ok(())
    }
}

impl<R: io::BufRead> io::BufRead for BasicSource<R> {
    fn fill_buf(&mut self) -> io::Result<&[u8]> {
        self.source.fill_buf()
    }

    fn consume(&mut self, amt: usize) {
        self.position += amt as u64;
        self.source.consume(amt);
    }
}

impl<R> fmt::Debug for BasicSource<R> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("BasicSource")
            .field("position", &self.position)
            .finish()
    }
}

impl<R: io::Read> ParserSource for BasicSource<R> {
    fn position(&self) -> u64 {
        self.position
    }

    fn skip_to(&mut self, dest_pos: u64) -> io::Result<()> {
        use std::io::Read;

        assert!(dest_pos >= self.position(),
                "Destination position should be after current position: dest_pos={}, position={}",
                dest_pos,
                self.position());
        const TEMP_BUF_LEN: usize = 256;
        let mut temp_buf = [0u8; TEMP_BUF_LEN];
        let mut rest_len = dest_pos - self.position();
        while rest_len > TEMP_BUF_LEN as u64 {
            self.read_exact(&mut temp_buf)?;
            rest_len -= TEMP_BUF_LEN as u64;
        }
        self.read_exact(&mut temp_buf[0..rest_len as usize])?;

        assert_eq!(self.position(), dest_pos);
        Ok(())
    }
}

/// Reader with position info and seek feature.
///
/// This wrapper doesn't manage offset, i.e. the start of the source stream should be the start of
/// the FBX data.
pub struct SeekableSource<R> {
    /// Source reader.
    source: R,
    /// Current position from the start of the stream..
    position: u64,
}

impl<R: io::Read + io::Seek> SeekableSource<R> {
    /// Creates a new `SeekableSource`.
    pub fn new(source: R) -> Self {
        SeekableSource {
            source: source,
            position: 0,
        }
    }
}

impl<R: io::Read> io::Read for SeekableSource<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let read_len = self.source.read(buf)?;
        self.position += read_len as u64;
        Ok(read_len)
    }

    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
        self.source.read_exact(buf)?;
        self.position += buf.len() as u64;
        Ok(())
    }
}

impl<R: io::Read + io::Seek> io::Seek for SeekableSource<R> {
    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
        self.position = self.source.seek(pos)?;
        Ok(self.position)
    }
}

impl<R> fmt::Debug for SeekableSource<R> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("SeekableSource")
            .field("position", &self.position)
            .finish()
    }
}

impl<R: io::Read + io::Seek> ParserSource for SeekableSource<R> {
    fn position(&self) -> u64 {
        self.position
    }

    fn skip_to(&mut self, dest_pos: u64) -> io::Result<()> {
        use std::io::{Seek, SeekFrom};

        assert!(dest_pos >= self.position(),
                "Destination position should be after current position: dest_pos={}, position={}",
                dest_pos,
                self.position());
        self.seek(SeekFrom::Start(dest_pos))?;

        assert_eq!(self.position(), dest_pos);
        Ok(())
    }
}

impl<R: io::BufRead> io::BufRead for SeekableSource<R> {
    fn fill_buf(&mut self) -> io::Result<&[u8]> {
        self.source.fill_buf()
    }

    fn consume(&mut self, amt: usize) {
        self.position += amt as u64;
        self.source.consume(amt);
    }
}

/// Reader which can read and seek limited area of a stream.
pub struct LimitedSeekReader<R> {
    /// Source stream.
    source: R,
    /// Current position
    current: u64,
    /// Start position.
    begin: u64,
    /// End position.
    end: u64,
}

impl<R: io::Read> LimitedSeekReader<R> {
    /// Creates a new `LimitedSeekReader`.
    pub fn new(source: R, current: u64, begin: u64, end: u64) -> Self {
        assert!(begin <= end);
        assert!(begin <= current);
        assert!(current <= end);

        LimitedSeekReader {
            source: source,
            current: current,
            begin: begin,
            end: end,
        }
    }
}

impl<R> LimitedSeekReader<R> {
    /// Returns length of region allowed to be read in bytes.
    pub fn len(&self) -> u64 {
        assert!(self.begin <= self.end);
        self.end - self.begin
    }

    /// Returns `true` if the reader can read no data.
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns rest length of region allowed to be read in bytes.
    fn rest_len(&self) -> u64 {
        assert!(self.current <= self.end);
        self.end - self.current
    }

    /// Returns the current position relative to the start of the limited region.
    fn rel_pos(&self) -> u64 {
        assert!(self.begin <= self.current);
        self.current - self.begin
    }
}

impl<R: io::Read> io::Read for LimitedSeekReader<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let limit = self.rest_len();
        let size = self.source.by_ref().take(limit).read(buf)?;
        self.current += size as u64;
        Ok(size)
    }
}

impl<R: io::BufRead> io::BufRead for LimitedSeekReader<R> {
    fn fill_buf(&mut self) -> io::Result<&[u8]> {
        self.source.fill_buf()
    }

    fn consume(&mut self, amt: usize) {
        self.current += amt as u64;
        self.source.consume(amt);
    }
}

impl<R: io::Seek> io::Seek for LimitedSeekReader<R> {
    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
        match pos {
            io::SeekFrom::Start(val) => {
                let offset = ::std::cmp::min(self.len(), val);
                let target = self.begin + offset;
                assert!(target >= self.begin);
                assert!(target <= self.end);
                self.current = self.source.seek(io::SeekFrom::Start(target))?;
            },
            io::SeekFrom::Current(val) => {
                let offset = if val < 0 {
                    ::std::cmp::max(-(self.rel_pos() as i64), val)
                } else {
                    ::std::cmp::min(self.rest_len(), val as u64) as i64
                };
                let target = self.current as i64 + offset;
                assert!(target as u64 >= self.begin);
                assert!(target as u64 <= self.end);
                self.current = self.source.seek(io::SeekFrom::Current(offset))?;
            },
            io::SeekFrom::End(val) => {
                let offset = ::std::cmp::max(-(self.len() as i64), ::std::cmp::min(0, val));
                let target = (self.end as i64 + offset) as u64;
                assert!(target >= self.begin);
                assert!(target <= self.end);
                self.current = self.source.seek(io::SeekFrom::Start(target))?;
            },
        }
        Ok(self.rel_pos())
    }
}

impl<R> fmt::Debug for LimitedSeekReader<R> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("LimitedSeekReader")
            .field("current", &self.current)
            .field("begin", &self.begin)
            .field("end", &self.end)
            .finish()
    }
}

#[cfg(test)]
mod tests {
    use std::io::{Cursor, Seek, SeekFrom};
    use super::{ParserSource, BasicSource, SeekableSource};

    fn do_test_skip_to(buf_size: usize, skip_dest: u64) {
        do_test_basic_skip_to(buf_size, skip_dest);
        do_test_seekable_skip_to(buf_size, skip_dest);
    }

    fn do_test_basic_skip_to(buf_size: usize, skip_dest: u64) {
        let mut short_buf = Cursor::new(vec![0; buf_size]);
        let short_count = {
            let mut reader = BasicSource::new(&mut short_buf);
            reader.skip_to(skip_dest).expect("Failed to skip");
            reader.position()
        };
        assert_eq!(short_count, skip_dest);
        assert_eq!(short_count,
                   short_buf.seek(SeekFrom::Current(0)).expect("Failed to seek"));
    }

    fn do_test_seekable_skip_to(buf_size: usize, skip_dest: u64) {
        let mut short_buf = Cursor::new(vec![0; buf_size]);
        let mut reader = SeekableSource::new(&mut short_buf);
        reader.skip_to(skip_dest).expect("Failed to skip");
        assert_eq!(reader.position(), skip_dest);
        reader.seek(SeekFrom::End(0)).expect("Failed to seek");;
        assert_eq!(reader.position(), buf_size as u64);
        reader.seek(SeekFrom::Start(0)).expect("Failed to seek");
        assert_eq!(reader.position(), 0);
    }

    #[test]
    fn test_skip_to() {
        do_test_skip_to(0, 0);
        do_test_skip_to(15, 0);
        do_test_skip_to(30, 23);
        do_test_skip_to(512, 401);
        do_test_skip_to(64, 64);
    }
}
<|endoftext|>


<commit_before>use super::{directory_stack::DirectoryStack, variables::Variables};
use glob::glob;
use liner::{Completer, FilenameCompleter};

/// Performs escaping to an inner `FilenameCompleter` to enable a handful of special cases
/// needed by the shell, such as expanding '~' to a home directory, or adding a backslash
/// when a special character is contained within an expanded filename.
pub(crate) struct IonFileCompleter {
    /// The completer that this completer is  handling.
    inner: FilenameCompleter,
    /// A pointer to the directory stack in the shell.
    dir_stack: *const DirectoryStack,
    /// A pointer to the variables map in the shell.
    vars: *const Variables,
}

impl IonFileCompleter {
    pub(crate) fn new(
        path: Option<&str>,
        dir_stack: *const DirectoryStack,
        vars: *const Variables,
    ) -> IonFileCompleter {
        IonFileCompleter {
            inner: FilenameCompleter::new(path),
            dir_stack,
            vars,
        }
    }
}

impl Completer for IonFileCompleter {
    /// When the tab key is pressed, **Liner** will use this method to perform completions of
    /// filenames. As our `IonFileCompleter` is a wrapper around **Liner**'s
    /// `FilenameCompleter`,
    /// the purpose of our custom `Completer` is to expand possible `~` characters in the
    /// `start`
    /// value that we receive from the prompt, grab completions from the inner
    /// `FilenameCompleter`,
    /// and then escape the resulting filenames, as well as remove the expanded form of the `~`
    /// character and re-add the `~` character in it's place.
    fn completions(&self, start: &str) -> Vec<String> {
        // Only if the first character is a tilde character will we perform expansions
        if start.starts_with('~') {
            // Dereferencing the raw pointers here should be entirely safe, theoretically,
            // because no changes will occur to either of the underlying references in the
            // duration between creation of the completers and execution of their
            // completions.
            if let Some(expanded) = unsafe { (*self.vars).tilde_expansion(start, &*self.dir_stack) }
            {
                // Now we obtain completions for the `expanded` form of the `start` value.
                let completions = filename_completion(&expanded, |x| self.inner.completions(x));
                let mut iterator = completions.iter();

                // And then we will need to take those completions and remove the expanded form
                // of the tilde pattern and replace it with that pattern yet again.
                let mut completions = Vec::new();

                // We can do that by obtaining the index position where the tilde character
                // ends. We don't search with `~` because we also want to
                // handle other tilde variants.
                let t_index = start.find('/').unwrap_or(1);
                // `tilde` is the tilde pattern, and `search` is the pattern that follows.
                let (tilde, search) = start.split_at(t_index as usize);

                if search.len() < 2 {
                    // If the length of the search pattern is less than 2, the search pattern is
                    // empty, and thus the completions actually contain files and directories in
                    // the home directory.

                    // The tilde pattern will actually be our `start` command in itself,
                    // and the completed form will be all of the characters beyond the length of
                    // the expanded form of the tilde pattern.
                    for completion in iterator {
                        completions.push([start, &completion[expanded.len()..]].concat());
                    }
                } else {
                    // To save processing time, we should get obtain the index position where our
                    // search pattern begins, and re-use that index to slice the completions so
                    // that we may re-add the tilde character with the completion that follows.
                    if let Some(completion) = iterator.next() {
                        if let Some(e_index) = expanded.rfind(search) {
                            completions.push(escape(&[tilde, &completion[e_index..]].concat()));
                            for completion in iterator {
                                let expanded = &completion[e_index..];
                                completions.push(escape(&[tilde, expanded].concat()));
                            }
                        }
                    }
                }

                return completions;
            }
        }

        filename_completion(&start, |x| self.inner.completions(x))
    }
}

fn filename_completion<LC>(start: &str, liner_complete: LC) -> Vec<String>
where
    LC: Fn(&str) -> Vec<String>,
{
    let unescaped_start = unescape(start);

    let split_start = unescaped_start.split("/");
    let mut string = String::with_capacity(5);

    // When 'start' is an absolute path, "/..." gets split to ["", "..."]
    // So we skip the first element and add "/" to the start of the string
    let mut start_for_glob = if unescaped_start.starts_with("/") {
        string.push('/');
        split_start.skip(1).fold(string, |mut state, element| {
            state.push_str(element);
            state.push_str("*/");
            state
        })
    } else {
        split_start.fold(string, |mut state, element| {
            state.push_str(element);
            state.push_str("*/");
            state
        })
    };
    start_for_glob.pop(); // pop out the last '/' character

    let mut inner_glob: Vec<String> = match glob(&start_for_glob) {
        Ok(completions) => completions
            .filter_map(Result::ok)
            .map(|x| x.to_string_lossy().into_owned())
            .collect(),
        _ => vec![],
    };
    if inner_glob.len() == 0 {
        inner_glob.push(escape(&start.to_string()));
    }

    let mut completions = vec![];

    // Use Liner::Completer as well, to preserve the previous behaviour
    // around single-directory completions
    for path in inner_glob {
        let liner_completions: Vec<String> = liner_complete(&path)
            .iter()
            .map(|x| escape(x.as_str()))
            .collect();
        for c in liner_completions {
            completions.push(c)
        }
    }
    completions
}

/// Escapes filenames from the completer so that special characters will be properly escaped.
///
/// NOTE: Perhaps we should submit a PR to Liner to add a &'static [u8] field to
/// `FilenameCompleter` so that we don't have to perform the escaping ourselves?
fn escape(input: &str) -> String {
    let mut output = Vec::with_capacity(input.len());
    for character in input.bytes() {
        match character {
            b'(' | b')' | b'[' | b']' | b'&' | b'$' | b'@' | b'{' | b'}' | b'<' | b'>' | b';'
            | b'"' | b'\'' | b'#' | b'^' | b'*' => output.push(b'\\'),
            _ => (),
        }
        output.push(character);
    }
    unsafe { String::from_utf8_unchecked(output) }
}

/// Unescapes filenames to be passed into the completer
fn unescape(input: &str) -> String {
    let mut output = Vec::with_capacity(input.len());
    let mut bytes = input.bytes();
    while let Some(b) = bytes.next() {
        match b {
            b'\\' => if let Some(next) = bytes.next() {
                output.push(next);
            } else {
                output.push(b'\\')
            },
            _ => output.push(b),
        }
    }
    unsafe { String::from_utf8_unchecked(output) }
}

/// A completer that combines suggestions from multiple completers.
#[derive(Clone, Eq, PartialEq)]
pub(crate) struct MultiCompleter<A, B>
where
    A: Completer,
    B: Completer,
{
    a: Vec<A>,
    b: B,
}

impl<A, B> MultiCompleter<A, B>
where
    A: Completer,
    B: Completer,
{
    pub(crate) fn new(a: Vec<A>, b: B) -> MultiCompleter<A, B> { MultiCompleter { a, b } }
}

impl<A, B> Completer for MultiCompleter<A, B>
where
    A: Completer,
    B: Completer,
{
    fn completions(&self, start: &str) -> Vec<String> {
        let mut completions = self.b.completions(start);
        for x in &self.a {
            completions.extend_from_slice(&x.completions(start));
        }
        completions
    }
}

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

    #[test]
    fn filename_completion() {
        let current_dir = env::current_dir().expect("Unable to get current directory");

        let completer = IonFileCompleter::new(
            current_dir.to_str(),
            &DirectoryStack::new(),
            &Variables::default(),
        );
        assert_eq!(completer.completions("testing"), vec!["testing/"]);
        assert_eq!(
            completer.completions("testing/file"),
            vec!["testing/file_with_text"]
        );

        assert_eq!(completer.completions("~"), vec!["~/"]);

        assert_eq!(
            completer.completions("tes/fil"),
            vec!["testing/file_with_text"]
        );
    }
}
<commit_msg>Refactor filename completion as an iterator to set up for when liner uses iterators<commit_after>use super::{directory_stack::DirectoryStack, variables::Variables};
use glob::glob;
use liner::{Completer, FilenameCompleter};

/// Performs escaping to an inner `FilenameCompleter` to enable a handful of special cases
/// needed by the shell, such as expanding '~' to a home directory, or adding a backslash
/// when a special character is contained within an expanded filename.
pub(crate) struct IonFileCompleter {
    /// The completer that this completer is  handling.
    inner: FilenameCompleter,
    /// A pointer to the directory stack in the shell.
    dir_stack: *const DirectoryStack,
    /// A pointer to the variables map in the shell.
    vars: *const Variables,
}

impl IonFileCompleter {
    pub(crate) fn new(
        path: Option<&str>,
        dir_stack: *const DirectoryStack,
        vars: *const Variables,
    ) -> IonFileCompleter {
        IonFileCompleter {
            inner: FilenameCompleter::new(path),
            dir_stack,
            vars,
        }
    }
}

impl Completer for IonFileCompleter {
    /// When the tab key is pressed, **Liner** will use this method to perform completions of
    /// filenames. As our `IonFileCompleter` is a wrapper around **Liner**'s
    /// `FilenameCompleter`,
    /// the purpose of our custom `Completer` is to expand possible `~` characters in the
    /// `start`
    /// value that we receive from the prompt, grab completions from the inner
    /// `FilenameCompleter`,
    /// and then escape the resulting filenames, as well as remove the expanded form of the `~`
    /// character and re-add the `~` character in it's place.
    fn completions(&self, start: &str) -> Vec<String> {
        // Only if the first character is a tilde character will we perform expansions
        if start.starts_with('~') {
            // Dereferencing the raw pointers here should be entirely safe, theoretically,
            // because no changes will occur to either of the underlying references in the
            // duration between creation of the completers and execution of their
            // completions.
            if let Some(expanded) = unsafe { (*self.vars).tilde_expansion(start, &*self.dir_stack) }
            {
                // Now we obtain completions for the `expanded` form of the `start` value.
                let mut iterator = FilenameCompletion::new(&expanded, |x| self.inner.completions(x));

                // And then we will need to take those completions and remove the expanded form
                // of the tilde pattern and replace it with that pattern yet again.
                let mut completions = Vec::new();

                // We can do that by obtaining the index position where the tilde character
                // ends. We don't search with `~` because we also want to
                // handle other tilde variants.
                let t_index = start.find('/').unwrap_or(1);
                // `tilde` is the tilde pattern, and `search` is the pattern that follows.
                let (tilde, search) = start.split_at(t_index as usize);

                if search.len() < 2 {
                    // If the length of the search pattern is less than 2, the search pattern is
                    // empty, and thus the completions actually contain files and directories in
                    // the home directory.

                    // The tilde pattern will actually be our `start` command in itself,
                    // and the completed form will be all of the characters beyond the length of
                    // the expanded form of the tilde pattern.
                    for completion in iterator {
                        completions.push([start, &completion[expanded.len()..]].concat());
                    }
                } else {
                    // To save processing time, we should get obtain the index position where our
                    // search pattern begins, and re-use that index to slice the completions so
                    // that we may re-add the tilde character with the completion that follows.
                    if let Some(completion) = iterator.next() {
                        if let Some(e_index) = expanded.rfind(search) {
                            completions.push(escape(&[tilde, &completion[e_index..]].concat()));
                            for completion in iterator {
                                let expanded = &completion[e_index..];
                                completions.push(escape(&[tilde, expanded].concat()));
                            }
                        }
                    }
                }

                return completions;
            }
        }

        FilenameCompletion::new(&start, |x| self.inner.completions(x)).collect()
    }
}

pub struct FilenameCompletion<'a> {
    iter: Box<Iterator<Item = String> + 'a>,
}

impl<'a> FilenameCompletion<'a> {
    pub fn new<LC: Fn(&str) -> Vec<String> + 'a>(start: &'a str, liner_complete: LC) -> Self {
        let unescaped_start = unescape(start);

        let split_start = unescaped_start.split("/");
        let mut string = String::with_capacity(5);

        // When 'start' is an absolute path, "/..." gets split to ["", "..."]
        // So we skip the first element and add "/" to the start of the string
        let mut start_for_glob = if unescaped_start.starts_with("/") {
            string.push('/');
            split_start.skip(1).fold(string, |mut state, element| {
                state.push_str(element);
                state.push_str("*/");
                state
            })
        } else {
            split_start.fold(string, |mut state, element| {
                state.push_str(element);
                state.push_str("*/");
                state
            })
        };
        start_for_glob.pop(); // pop out the last '/' character

        let iter_inner_glob: Box<Iterator<Item = String>> = match glob(&start_for_glob) {
            Ok(completions) => {
                let mut iter = completions
                    .filter_map(Result::ok)
                    .map(|x| x.to_string_lossy().into_owned())
                    .peekable();
                if iter.peek().is_some() {
                    Box::new(iter)
                } else {
                    Box::new(Some(escape(start)).into_iter())
                }
            }
            _ => Box::new(Some(escape(start)).into_iter()),
        };

        // Use Liner::Completer as well, to preserve the previous behaviour
        // around single-directory completions
        let iter = iter_inner_glob.flat_map(move |path| {
            liner_complete(&path)
                .into_iter()
                .map(|x| escape(x.as_str()))
        });

        FilenameCompletion {
            iter: Box::new(iter),
        }
    }
}

impl<'a> Iterator for FilenameCompletion<'a> {
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next()
    }
}

/// Escapes filenames from the completer so that special characters will be properly escaped.
///
/// NOTE: Perhaps we should submit a PR to Liner to add a &'static [u8] field to
/// `FilenameCompleter` so that we don't have to perform the escaping ourselves?
fn escape(input: &str) -> String {
    let mut output = Vec::with_capacity(input.len());
    for character in input.bytes() {
        match character {
            b'(' | b')' | b'[' | b']' | b'&' | b'$' | b'@' | b'{' | b'}' | b'<' | b'>' | b';'
            | b'"' | b'\'' | b'#' | b'^' | b'*' => output.push(b'\\'),
            _ => (),
        }
        output.push(character);
    }
    unsafe { String::from_utf8_unchecked(output) }
}

/// Unescapes filenames to be passed into the completer
fn unescape(input: &str) -> String {
    let mut output = Vec::with_capacity(input.len());
    let mut bytes = input.bytes();
    while let Some(b) = bytes.next() {
        match b {
            b'\\' => if let Some(next) = bytes.next() {
                output.push(next);
            } else {
                output.push(b'\\')
            },
            _ => output.push(b),
        }
    }
    unsafe { String::from_utf8_unchecked(output) }
}

/// A completer that combines suggestions from multiple completers.
#[derive(Clone, Eq, PartialEq)]
pub(crate) struct MultiCompleter<A, B>
where
    A: Completer,
    B: Completer,
{
    a: Vec<A>,
    b: B,
}

impl<A, B> MultiCompleter<A, B>
where
    A: Completer,
    B: Completer,
{
    pub(crate) fn new(a: Vec<A>, b: B) -> MultiCompleter<A, B> { MultiCompleter { a, b } }
}

impl<A, B> Completer for MultiCompleter<A, B>
where
    A: Completer,
    B: Completer,
{
    fn completions(&self, start: &str) -> Vec<String> {
        let mut completions = self.b.completions(start);
        for x in &self.a {
            completions.extend_from_slice(&x.completions(start));
        }
        completions
    }
}

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

    #[test]
    fn filename_completion() {
        let current_dir = env::current_dir().expect("Unable to get current directory");

        let completer = IonFileCompleter::new(
            current_dir.to_str(),
            &DirectoryStack::new(),
            &Variables::default(),
        );
        assert_eq!(completer.completions("testing"), vec!["testing/"]);
        assert_eq!(
            completer.completions("testing/file"),
            vec!["testing/file_with_text"]
        );

        assert_eq!(completer.completions("~"), vec!["~/"]);

        assert_eq!(
            completer.completions("tes/fil"),
            vec!["testing/file_with_text"]
        );
    }
}
<|endoftext|>


<commit_before>extern crate git2;

use git2::{Repository, StatusOptions};
use std::fs::File;
use std::path::Path;
use std::io::Write;

fn main() {
  let git = git_commit().map(|x| format!("-g{}", x)).unwrap_or_default();
  let clean = match git_clean() {
    Some(false) => "-dirty",
    _ => ""
  };
  let version = std::env::var("CARGO_PKG_VERSION").unwrap();
  let version_string = format!("{}{}{}", version, git, clean);
  let out_dir = std::env::var("OUT_DIR").unwrap();
  let p = Path::new(&out_dir);
  let mut f = File::create(p.join("version")).unwrap();
  f.write_all(version_string.as_bytes()).unwrap();
}

fn git_commit() -> Option<String> {
  let repo = Repository::open(".").ok()?;
  let revparse = repo.revparse_single("HEAD").ok()?;
  revparse.short_id().ok()?.as_str().map(ToOwned::to_owned)
}

fn git_clean() -> Option<bool> {
  let repo = Repository::open(".").ok()?;
  let mut options = StatusOptions::new();
  options
    .include_ignored(false)
    .include_untracked(false);
  let statuses = repo.statuses(Some(&mut options)).ok()?;
  Some(statuses.is_empty())
}
<commit_msg>feat(build): include branch<commit_after>extern crate git2;

use git2::{Repository, StatusOptions};
use std::fs::File;
use std::path::Path;
use std::io::Write;

fn main() {
  let repo = Repo(Repository::open(".").ok());
  let git = repo.commit().map(|x| format!("-g{}", x)).unwrap_or_default();
  let branch = repo.branch().map(|x| format!("-{}", x)).unwrap_or_default();
  let clean = match repo.clean() {
    Some(false) => "-dirty",
    _ => ""
  };
  let version = std::env::var("CARGO_PKG_VERSION").unwrap();
  let version_string = format!("{}{}{}{}", version, git, branch, clean);
  let out_dir = std::env::var("OUT_DIR").unwrap();
  let p = Path::new(&out_dir);
  let mut f = File::create(p.join("version")).unwrap();
  f.write_all(version_string.as_bytes()).unwrap();
}

struct Repo(Option<Repository>);

impl Repo {
  fn commit(&self) -> Option<String> {
    let repo = self.0.as_ref()?;
    let revparse = repo.revparse_single("HEAD").ok()?;
    revparse.short_id().ok()?.as_str().map(ToOwned::to_owned)
  }

  fn clean(&self) -> Option<bool> {
    let repo = self.0.as_ref()?;
    let mut options = StatusOptions::new();
    options
      .include_ignored(false)
      .include_untracked(false);
    let statuses = repo.statuses(Some(&mut options)).ok()?;
    Some(statuses.is_empty())
  }

  fn branch(&self) -> Option<String> {
    let repo = self.0.as_ref()?;
    let head = repo.head().ok()?;
    head.name().and_then(|x| {
      if x.starts_with("refs/heads/") {
        Some(x[11..].to_string())
      } else if x.len() >= 7 {
        Some(x[..7].to_string())
      } else {
        None
      }
    })
  }
}
<|endoftext|>


<commit_before>extern crate gcc;

use std::env;

fn main() {
    gcc::Config::new()
                .file("src/predicates.c")
				.opt_level(2)
                .compile("libpredicates.a");
}
<commit_msg>Remove unused "use"<commit_after>extern crate gcc;

fn main() {
    gcc::Config::new()
                .file("src/predicates.c")
				.opt_level(2)
                .compile("libpredicates.a");
}
<|endoftext|>


<commit_before>use std::process::Command;

fn main() {
	let output = Command::new("python")
			.arg("scripts/generate_wrapper.py")
			.output()
			.expect("failed to execute process");
	println!("{:?}", output.stdout)
}
<commit_msg>fix script name, add path<commit_after>use std::process::Command;

fn main() {
	let output = Command::new("python")
			.arg(format!("{}/scripts/generate_wrappers.py", env!("CARGO_MANIFEST_DIR")))
			.spawn()
			.expect("failed to execute process");
	println!("{:?}", output)
}
<|endoftext|>


<commit_before>fn main() {
    manage_docs();
}

#[cfg(any(feature = "embed-lgpl-docs", feature = "purge-lgpl-docs"))]
fn manage_docs() {
    extern crate lgpl_docs;
    const PATH: &'static str = "src";
    const IGNORES: &'static [&'static str] = &["lib.rs"];
    lgpl_docs::purge(PATH, IGNORES);
    if cfg!(feature = "embed-lgpl-docs") {
        lgpl_docs::embed(lgpl_docs::Library::Cairo, PATH, IGNORES);
    }
}

#[cfg(not(any(feature = "embed-lgpl-docs", feature = "purge-lgpl-docs")))]
fn manage_docs() {}
<commit_msg>Don't include LGPL docs in the docs if both embed-lgpl-docs and purge-lgpl-docs features are selected<commit_after>fn main() {
    manage_docs();
}

#[cfg(all(
    any(feature = "embed-lgpl-docs", feature = "purge-lgpl-docs"),
    not(all(feature = "embed-lgpl-docs", feature = "purge-lgpl-docs"))
))]
fn manage_docs() {
    extern crate lgpl_docs;
    const PATH: &'static str = "src";
    const IGNORES: &'static [&'static str] = &["lib.rs"];
    lgpl_docs::purge(PATH, IGNORES);
    if cfg!(feature = "embed-lgpl-docs") {
        lgpl_docs::embed(lgpl_docs::Library::Cairo, PATH, IGNORES);
    }
}

#[cfg(any(
    all(feature = "embed-lgpl-docs", feature = "purge-lgpl-docs"),
    not(any(feature = "embed-lgpl-docs", feature = "purge-lgpl-docs"))
))]
fn manage_docs() {}
<|endoftext|>


<commit_before>
use bwapi_sys as sys;
use iterator::{BwIterator, FromRaw};

pub struct Region(*mut sys::Region);

use std::os::raw::c_void as void;

impl FromRaw for Region {
    unsafe fn from_raw(raw: *mut void) -> Region {
        assert!(!raw.is_null());
        Region(raw as *mut sys::Region)
    }
}

impl Region {
    pub fn id(&self) -> i32 {
        unsafe {
            sys::Region_getID(self.0)
        }
    }

    pub fn group_id(&self) -> i32 {
        unsafe {
            sys::Region_getRegionGroupID(self.0)
        }
    }

    pub fn center(&self) -> sys::Position {
        unsafe {
            sys::Region_getCenter(self.0)
        }
    }

    pub fn defense_priority(&self) -> i32 {
        unsafe {
            sys::Region_getDefensePriority(self.0)
        }
    }

    pub fn neighbors(&self) -> Box<Iterator<Item=Region>> {
        unsafe {
            let iter = sys::Region_getNeighbors(self.0) as *mut sys::Iterator;
            Box::new(BwIterator::from(iter))
        }
    }
}
<commit_msg>Fixes Region::center<commit_after>
use bwapi_sys as sys;
use iterator::{BwIterator, FromRaw};
use position::Position;

pub struct Region(*mut sys::Region);

use std::os::raw::c_void as void;

impl FromRaw for Region {
    unsafe fn from_raw(raw: *mut void) -> Region {
        assert!(!raw.is_null());
        Region(raw as *mut sys::Region)
    }
}

impl Region {
    pub fn id(&self) -> i32 {
        unsafe {
            sys::Region_getID(self.0)
        }
    }

    pub fn group_id(&self) -> i32 {
        unsafe {
            sys::Region_getRegionGroupID(self.0)
        }
    }

    pub fn center(&self) -> Position {
        Position::from( unsafe { sys::Region_getCenter(self.0) } )
    }

    pub fn defense_priority(&self) -> i32 {
        unsafe {
            sys::Region_getDefensePriority(self.0)
        }
    }

    pub fn neighbors(&self) -> Box<Iterator<Item=Region>> {
        unsafe {
            let iter = sys::Region_getNeighbors(self.0) as *mut sys::Iterator;
            Box::new(BwIterator::from(iter))
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

#![allow(unused_imports)]

use std::collections::{BTreeSet, BTreeMap, LinkedList};
use std::cmp::{Ordering, Ord, PartialOrd};
use std::fmt::{Display, Formatter};

use symbols;
use num::bigint::{BigInt, ToBigInt, ParseBigIntError};
use ordered_float::OrderedFloat;

/// Value represents one of the allowed values in an EDN string.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum Value {
    Nil,
    Boolean(bool),
    Integer(i64),
    BigInteger(BigInt),
    // https://users.rust-lang.org/t/hashmap-key-cant-be-float-number-type-why/7892
    Float(OrderedFloat<f64>),
    Text(String),
    PlainSymbol(symbols::PlainSymbol),
    NamespacedSymbol(symbols::NamespacedSymbol),
    Keyword(symbols::Keyword),
    NamespacedKeyword(symbols::NamespacedKeyword),
    Vector(Vec<Value>),
    List(LinkedList<Value>),
    // We're using BTree{Set, Map} rather than Hash{Set, Map} because the BTree variants
    // implement Hash. The Hash variants don't in order to preserve O(n) hashing
    // time, which is hard given recursive data structures.
    // See https://internals.rust-lang.org/t/implementing-hash-for-hashset-hashmap/3817/1
    Set(BTreeSet<Value>),
    Map(BTreeMap<Value, Value>),
}

use self::Value::*;

impl Display for Value {
    fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
        match *self {
            Keyword(ref v) => v.fmt(f),
            NamespacedKeyword(ref v) => v.fmt(f),
            PlainSymbol(ref v) => v.fmt(f),
            NamespacedSymbol(ref v) => v.fmt(f),

            Integer(v) => write!(f, "{}", v),
            BigInteger(ref v) => write!(f, "{}N", v),
            Float(OrderedFloat(v)) => write!(f, "{}", v),       // TODO: make sure float syntax is correct.
                                                                // TODO: NaN.
            Text(ref v) => write!(f, "{}", v),                  // TODO: EDN escaping.
            Vector(ref v) => {
                try!(write!(f, "["));
                for x in v {
                    try!(write!(f, " {}", x));
                }
                write!(f, " ]")
            }
            _ =>
                write!(f, "{}",
                       match *self {
                           Nil => "null",
                           Boolean(b) => if b { "true" } else { "false" },
                           _ => unimplemented!(),
                       }),
        }
    }
}

#[test]
fn test_value_from() {
    assert_eq!(Value::from(42f64), Value::Float(OrderedFloat::from(42f64)));
    assert_eq!(Value::from_bigint("42").unwrap(), Value::BigInteger(42.to_bigint().unwrap()));
}

#[test]
fn test_print_edn() {
    assert_eq!("[ 1 2 [ 3.1 ] [ ] :five :six/seven eight nine/ten true ]",
               Value::Vector(vec!(Value::Integer(1),
               Value::Integer(2),
               Value::Vector(vec!(Value::Float(OrderedFloat(3.1)))),
               Value::Vector(vec!()),
               Value::Keyword(symbols::Keyword::new("five")),
               Value::NamespacedKeyword(symbols::NamespacedKeyword::new("six", "seven")),
               Value::PlainSymbol(symbols::PlainSymbol::new("eight")),
               Value::NamespacedSymbol(symbols::NamespacedSymbol::new("nine", "ten")),
               Value::Boolean(true))).to_string());
}

/// Creates `is_$TYPE` helper functions for Value, like
/// `is_big_integer()` or `is_text()`.
macro_rules! def_is {
    ($name: ident, $pat: pat) => {
        pub fn $name(&self) -> bool {
            match *self { $pat => true, _ => false }
        }
    }
}

/// Creates `as_$TYPE` helper functions for Value, like `as_big_integer()`,
/// which returns the underlying value representing this Value wrapped
/// in an Option, like `<Option<&BigInt>`.
macro_rules! def_as {
    ($name: ident, $kind: path, $t: ty) => {
        pub fn $name(&self) -> Option<&$t> {
            match *self { $kind(ref v) => Some(v), _ => None }
        }
    }
}

impl Value {
    def_is!(is_nil, Nil);
    def_is!(is_boolean, Boolean(_));
    def_is!(is_integer, Integer(_));
    def_is!(is_big_integer, BigInteger(_));
    def_is!(is_float, Float(_));
    def_is!(is_text, Text(_));
    def_is!(is_symbol, PlainSymbol(_));
    def_is!(is_namespaced_symbol, NamespacedSymbol(_));
    def_is!(is_keyword, Keyword(_));
    def_is!(is_namespaced_keyword, NamespacedKeyword(_));
    def_is!(is_vector, Vector(_));
    def_is!(is_list, List(_));
    def_is!(is_set, Set(_));
    def_is!(is_map, Map(_));

    /// `as_nil` does not use the macro as it does not have an underlying
    /// value, and returns `Option<()>`.
    pub fn as_nil(&self) -> Option<()> {
        match *self { Nil => Some(()), _ => None }
    }

    def_as!(as_boolean, Boolean, bool);
    def_as!(as_integer, Integer, i64);
    def_as!(as_big_integer, BigInteger, BigInt);
    def_as!(as_float, Float, OrderedFloat<f64>);
    def_as!(as_text, Text, String);
    def_as!(as_symbol, PlainSymbol, symbols::PlainSymbol);
    def_as!(as_namespaced_symbol, NamespacedSymbol, symbols::NamespacedSymbol);
    def_as!(as_keyword, Keyword, symbols::Keyword);
    def_as!(as_namespaced_keyword, NamespacedKeyword, symbols::NamespacedKeyword);
    def_as!(as_vector, Vector, Vec<Value>);
    def_as!(as_list, List, LinkedList<Value>);
    def_as!(as_set, Set, BTreeSet<Value>);
    def_as!(as_map, Map, BTreeMap<Value, Value>);

    pub fn from_bigint(src: &str) -> Option<Value> {
        src.parse::<BigInt>().map(Value::BigInteger).ok()
    }
}

impl From<f64> for Value {
    fn from(src: f64) -> Value {
        Value::Float(OrderedFloat::from(src))
    }
}

impl PartialOrd for Value {
    fn partial_cmp(&self, other: &Value) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

// TODO: Check we follow the equality rules at the bottom of https://github.com/edn-format/edn
impl Ord for Value {
    fn cmp(&self, other: &Value) -> Ordering {

        let ord_order = to_ord(self).cmp(&to_ord(other));
        match *self {
            Nil             => match *other { Nil             => Ordering::Equal, _ => ord_order },
            Boolean(bs)     => match *other { Boolean(bo)     => bo.cmp(&bs), _ => ord_order },
            BigInteger(ref bs) => match *other { BigInteger(ref bo) => bo.cmp(&bs), _ => ord_order },
            Integer(is)     => match *other { Integer(io)     => io.cmp(&is), _ => ord_order },
            Float(ref fs)   => match *other { Float(ref fo)   => fo.cmp(&fs), _ => ord_order },
            Text(ref ts)    => match *other { Text(ref to)    => to.cmp(&ts), _ => ord_order },
            PlainSymbol(ref ss)  => match *other { PlainSymbol(ref so)  => so.cmp(&ss), _ => ord_order },
            NamespacedSymbol(ref ss)
                => match *other { NamespacedSymbol(ref so)    => so.cmp(&ss), _ => ord_order },
            Keyword(ref ks) => match *other { Keyword(ref ko) => ko.cmp(&ks), _ => ord_order },
            NamespacedKeyword(ref ks)
                => match *other { NamespacedKeyword(ref ko)   => ko.cmp(&ks), _ => ord_order },
            Vector(ref vs)  => match *other { Vector(ref vo)  => vo.cmp(&vs), _ => ord_order },
            List(ref ls)    => match *other { List(ref lo)    => lo.cmp(&ls), _ => ord_order },
            Set(ref ss)     => match *other { Set(ref so)     => so.cmp(&ss), _ => ord_order },
            Map(ref ms)     => match *other { Map(ref mo)     => mo.cmp(&ms), _ => ord_order },
        }
    }
}

fn to_ord(value: &Value) -> i32 {
    match *value {
        Nil => 0,
        Boolean(_) => 1,
        Integer(_) => 2,
        BigInteger(_) => 3,
        Float(_) => 4,
        Text(_) => 5,
        PlainSymbol(_) => 6,
        NamespacedSymbol(_) => 7,
        Keyword(_) => 8,
        NamespacedKeyword(_) => 9,
        Vector(_) => 10,
        List(_) => 11,
        Set(_) => 12,
        Map(_) => 13,
    }
}

pub struct Pair(Value, Value);

pub fn to_symbol<'a, T: Into<Option<&'a str>>>(namespace: T, name: &str) -> Value {
    namespace.into().map_or_else(
        || Value::PlainSymbol(symbols::PlainSymbol::new(name)),
        |ns| Value::NamespacedSymbol(symbols::NamespacedSymbol::new(ns, name)))
}

pub fn to_keyword<'a, T: Into<Option<&'a str>>>(namespace: T, name: &str) -> Value {
    namespace.into().map_or_else(
        || Value::Keyword(symbols::Keyword::new(name)),
        |ns| Value::NamespacedKeyword(symbols::NamespacedKeyword::new(ns, name)))
}<commit_msg>Add rustdoc comments for `to_symbol` and `to_keyword` functions<commit_after>// Copyright 2016 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

#![allow(unused_imports)]

use std::collections::{BTreeSet, BTreeMap, LinkedList};
use std::cmp::{Ordering, Ord, PartialOrd};
use std::fmt::{Display, Formatter};

use symbols;
use num::bigint::{BigInt, ToBigInt, ParseBigIntError};
use ordered_float::OrderedFloat;

/// Value represents one of the allowed values in an EDN string.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum Value {
    Nil,
    Boolean(bool),
    Integer(i64),
    BigInteger(BigInt),
    // https://users.rust-lang.org/t/hashmap-key-cant-be-float-number-type-why/7892
    Float(OrderedFloat<f64>),
    Text(String),
    PlainSymbol(symbols::PlainSymbol),
    NamespacedSymbol(symbols::NamespacedSymbol),
    Keyword(symbols::Keyword),
    NamespacedKeyword(symbols::NamespacedKeyword),
    Vector(Vec<Value>),
    List(LinkedList<Value>),
    // We're using BTree{Set, Map} rather than Hash{Set, Map} because the BTree variants
    // implement Hash. The Hash variants don't in order to preserve O(n) hashing
    // time, which is hard given recursive data structures.
    // See https://internals.rust-lang.org/t/implementing-hash-for-hashset-hashmap/3817/1
    Set(BTreeSet<Value>),
    Map(BTreeMap<Value, Value>),
}

use self::Value::*;

impl Display for Value {
    fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
        match *self {
            Keyword(ref v) => v.fmt(f),
            NamespacedKeyword(ref v) => v.fmt(f),
            PlainSymbol(ref v) => v.fmt(f),
            NamespacedSymbol(ref v) => v.fmt(f),

            Integer(v) => write!(f, "{}", v),
            BigInteger(ref v) => write!(f, "{}N", v),
            Float(OrderedFloat(v)) => write!(f, "{}", v),       // TODO: make sure float syntax is correct.
                                                                // TODO: NaN.
            Text(ref v) => write!(f, "{}", v),                  // TODO: EDN escaping.
            Vector(ref v) => {
                try!(write!(f, "["));
                for x in v {
                    try!(write!(f, " {}", x));
                }
                write!(f, " ]")
            }
            _ =>
                write!(f, "{}",
                       match *self {
                           Nil => "null",
                           Boolean(b) => if b { "true" } else { "false" },
                           _ => unimplemented!(),
                       }),
        }
    }
}

#[test]
fn test_value_from() {
    assert_eq!(Value::from(42f64), Value::Float(OrderedFloat::from(42f64)));
    assert_eq!(Value::from_bigint("42").unwrap(), Value::BigInteger(42.to_bigint().unwrap()));
}

#[test]
fn test_print_edn() {
    assert_eq!("[ 1 2 [ 3.1 ] [ ] :five :six/seven eight nine/ten true ]",
               Value::Vector(vec!(Value::Integer(1),
               Value::Integer(2),
               Value::Vector(vec!(Value::Float(OrderedFloat(3.1)))),
               Value::Vector(vec!()),
               Value::Keyword(symbols::Keyword::new("five")),
               Value::NamespacedKeyword(symbols::NamespacedKeyword::new("six", "seven")),
               Value::PlainSymbol(symbols::PlainSymbol::new("eight")),
               Value::NamespacedSymbol(symbols::NamespacedSymbol::new("nine", "ten")),
               Value::Boolean(true))).to_string());
}

/// Creates `is_$TYPE` helper functions for Value, like
/// `is_big_integer()` or `is_text()`.
macro_rules! def_is {
    ($name: ident, $pat: pat) => {
        pub fn $name(&self) -> bool {
            match *self { $pat => true, _ => false }
        }
    }
}

/// Creates `as_$TYPE` helper functions for Value, like `as_big_integer()`,
/// which returns the underlying value representing this Value wrapped
/// in an Option, like `<Option<&BigInt>`.
macro_rules! def_as {
    ($name: ident, $kind: path, $t: ty) => {
        pub fn $name(&self) -> Option<&$t> {
            match *self { $kind(ref v) => Some(v), _ => None }
        }
    }
}

impl Value {
    def_is!(is_nil, Nil);
    def_is!(is_boolean, Boolean(_));
    def_is!(is_integer, Integer(_));
    def_is!(is_big_integer, BigInteger(_));
    def_is!(is_float, Float(_));
    def_is!(is_text, Text(_));
    def_is!(is_symbol, PlainSymbol(_));
    def_is!(is_namespaced_symbol, NamespacedSymbol(_));
    def_is!(is_keyword, Keyword(_));
    def_is!(is_namespaced_keyword, NamespacedKeyword(_));
    def_is!(is_vector, Vector(_));
    def_is!(is_list, List(_));
    def_is!(is_set, Set(_));
    def_is!(is_map, Map(_));

    /// `as_nil` does not use the macro as it does not have an underlying
    /// value, and returns `Option<()>`.
    pub fn as_nil(&self) -> Option<()> {
        match *self { Nil => Some(()), _ => None }
    }

    def_as!(as_boolean, Boolean, bool);
    def_as!(as_integer, Integer, i64);
    def_as!(as_big_integer, BigInteger, BigInt);
    def_as!(as_float, Float, OrderedFloat<f64>);
    def_as!(as_text, Text, String);
    def_as!(as_symbol, PlainSymbol, symbols::PlainSymbol);
    def_as!(as_namespaced_symbol, NamespacedSymbol, symbols::NamespacedSymbol);
    def_as!(as_keyword, Keyword, symbols::Keyword);
    def_as!(as_namespaced_keyword, NamespacedKeyword, symbols::NamespacedKeyword);
    def_as!(as_vector, Vector, Vec<Value>);
    def_as!(as_list, List, LinkedList<Value>);
    def_as!(as_set, Set, BTreeSet<Value>);
    def_as!(as_map, Map, BTreeMap<Value, Value>);

    pub fn from_bigint(src: &str) -> Option<Value> {
        src.parse::<BigInt>().map(Value::BigInteger).ok()
    }
}

impl From<f64> for Value {
    fn from(src: f64) -> Value {
        Value::Float(OrderedFloat::from(src))
    }
}

impl PartialOrd for Value {
    fn partial_cmp(&self, other: &Value) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

// TODO: Check we follow the equality rules at the bottom of https://github.com/edn-format/edn
impl Ord for Value {
    fn cmp(&self, other: &Value) -> Ordering {

        let ord_order = to_ord(self).cmp(&to_ord(other));
        match *self {
            Nil             => match *other { Nil             => Ordering::Equal, _ => ord_order },
            Boolean(bs)     => match *other { Boolean(bo)     => bo.cmp(&bs), _ => ord_order },
            BigInteger(ref bs) => match *other { BigInteger(ref bo) => bo.cmp(&bs), _ => ord_order },
            Integer(is)     => match *other { Integer(io)     => io.cmp(&is), _ => ord_order },
            Float(ref fs)   => match *other { Float(ref fo)   => fo.cmp(&fs), _ => ord_order },
            Text(ref ts)    => match *other { Text(ref to)    => to.cmp(&ts), _ => ord_order },
            PlainSymbol(ref ss)  => match *other { PlainSymbol(ref so)  => so.cmp(&ss), _ => ord_order },
            NamespacedSymbol(ref ss)
                => match *other { NamespacedSymbol(ref so)    => so.cmp(&ss), _ => ord_order },
            Keyword(ref ks) => match *other { Keyword(ref ko) => ko.cmp(&ks), _ => ord_order },
            NamespacedKeyword(ref ks)
                => match *other { NamespacedKeyword(ref ko)   => ko.cmp(&ks), _ => ord_order },
            Vector(ref vs)  => match *other { Vector(ref vo)  => vo.cmp(&vs), _ => ord_order },
            List(ref ls)    => match *other { List(ref lo)    => lo.cmp(&ls), _ => ord_order },
            Set(ref ss)     => match *other { Set(ref so)     => so.cmp(&ss), _ => ord_order },
            Map(ref ms)     => match *other { Map(ref mo)     => mo.cmp(&ms), _ => ord_order },
        }
    }
}

fn to_ord(value: &Value) -> i32 {
    match *value {
        Nil => 0,
        Boolean(_) => 1,
        Integer(_) => 2,
        BigInteger(_) => 3,
        Float(_) => 4,
        Text(_) => 5,
        PlainSymbol(_) => 6,
        NamespacedSymbol(_) => 7,
        Keyword(_) => 8,
        NamespacedKeyword(_) => 9,
        Vector(_) => 10,
        List(_) => 11,
        Set(_) => 12,
        Map(_) => 13,
    }
}

pub struct Pair(Value, Value);

/// Converts `name` into a plain or namespaced value symbol, depending on
/// whether or not `namespace` is given.
///
/// # Examples
///
/// ```
/// # use edn::types::to_symbol;
/// # use edn::types::Value;
/// # use edn::symbols;
/// let value = to_symbol("foo", "bar");
/// assert_eq!(value, Value::NamespacedSymbol(symbols::NamespacedSymbol::new("foo", "bar")));
///
/// let value = to_symbol(None, "baz");
/// assert_eq!(value, Value::PlainSymbol(symbols::PlainSymbol::new("baz")));
/// ```
pub fn to_symbol<'a, T: Into<Option<&'a str>>>(namespace: T, name: &str) -> Value {
    namespace.into().map_or_else(
        || Value::PlainSymbol(symbols::PlainSymbol::new(name)),
        |ns| Value::NamespacedSymbol(symbols::NamespacedSymbol::new(ns, name)))
}

/// Converts `name` into a plain or namespaced value keyword, depending on
/// whether or not `namespace` is given.
///
/// # Examples
///
/// ```
/// # use edn::types::to_keyword;
/// # use edn::types::Value;
/// # use edn::symbols;
/// let value = to_keyword("foo", "bar");
/// assert_eq!(value, Value::NamespacedKeyword(symbols::NamespacedKeyword::new("foo", "bar")));
///
/// let value = to_keyword(None, "baz");
/// assert_eq!(value, Value::Keyword(symbols::Keyword::new("baz")));
/// ```
pub fn to_keyword<'a, T: Into<Option<&'a str>>>(namespace: T, name: &str) -> Value {
    namespace.into().map_or_else(
        || Value::Keyword(symbols::Keyword::new(name)),
        |ns| Value::NamespacedKeyword(symbols::NamespacedKeyword::new(ns, name)))
}<|endoftext|>


<commit_before>use regex::Regex;

use crate::data_types::SolrTermSummary;

use std::collections::HashMap;

use crate::web::config::ServerConfig;
use crate::api::search_types::*;
use crate::api::search_utils::{do_solr_request, clean_words, get_query_part};

#[derive(Serialize, Deserialize, Debug)]
pub struct TermSearchMatch {
    pub id: String,
    pub cv_name: String,
    pub name: String,
    pub definition: String,
    pub hl: SolrMatchHighlight,
}

#[derive(Serialize, Deserialize, Debug)]
struct TermSearchRes {
    pub id: String,
    pub cv_name: String,
    pub name: String,
    pub definition: String,
}

#[derive(Deserialize, Debug)]
struct SolrTermSearchResponse {
    pub docs: Vec<TermSearchRes>,
}

#[derive(Deserialize, Debug)]
struct SolrTermSearchResponseContainer {
    pub highlighting: HashMap<SolrMatchId, SolrMatchHighlight>,
    pub response: SolrTermSearchResponse,
}

#[derive(Deserialize, Debug)]
struct SolrTermResponse {
    pub docs: Vec<SolrTermSummary>,
}

#[derive(Deserialize, Debug)]
struct SolrTermResponseContainer {
    pub response: SolrTermResponse,
}

pub fn search_terms(config: &ServerConfig, q: &str)
                    -> Result<Vec<TermSearchMatch>, String>
{
    let cv_name = &config.cv_name_for_terms_search;

    if let Some(mut url) = make_terms_url(config, cv_name, q) {
        url += "&hl=on&hl.fl=name,definition&fl=id,name,cv_name,definition";
        let res = do_solr_request(&url)?;

        match serde_json::from_reader(res) {
            Ok(container) => {
                let response_container: SolrTermSearchResponseContainer = container;
                let mut hl_by_id = response_container.highlighting;
                let matches: Vec<TermSearchMatch> = response_container.response.docs
                    .iter().map(|doc| TermSearchMatch {
                        id: String::from(&doc.id),
                        cv_name: String::from(doc.cv_name.as_str()),
                        name: String::from(doc.name.as_str()),
                        definition: doc.definition.clone(),
                        hl: hl_by_id.remove(doc.id.as_str())
                            .unwrap_or_else(HashMap::new),
                    }).collect();
                Ok(matches)
            },
            Err(err) => {
                Err(format!("Error parsing response from Solr: {:?}", err))
            }
        }
    } else {
        Ok(vec![])
    }
}

pub fn make_terms_url(config: &ServerConfig, cv_name: &str, q: &str) -> Option<String> {
    let mut terms_url =
        config.solr_url.to_owned() + "/terms/select?wt=json&q=";

    let termid_re_string = r"^(?P<prefix>[\w_]+):(?P<accession>\d+)$";
    let termid_re = Regex::new(termid_re_string).unwrap();

    let parent_re_string = r"^\[".to_owned() + termid_re_string + r"\]$";
    let parent_re = Regex::new(&parent_re_string).unwrap();

    let maybe_captures = parent_re.captures(cv_name);

    if let Some(captures) = maybe_captures {
        let prefix = captures.name("prefix").unwrap().as_str();
        let accession = captures.name("accession").unwrap().as_str();
        terms_url += &format!("(interesting_isa_parents:{}\\:{} OR id:{}\\:{})",
                              prefix, accession, prefix, accession);
    } else {
        terms_url += &format!("cv_name:+{}^=1", cv_name);
    }

    if let Some(captures) = termid_re.captures(q) {
        let prefix = captures.name("prefix").unwrap().as_str();
        let accession = captures.name("accession").unwrap().as_str();
        terms_url = format!(r"{} AND (id:{}\:{} OR secondary_identifiers:{}\:{})",
                            terms_url, prefix, accession, prefix, accession);
    } else {
        let clean_words = clean_words(q);

        if clean_words.is_empty() {
            return None;
        }

        terms_url += "annotation_count:[1 TO *] AND (name:(";

        let query_part = get_query_part(&clean_words);

        terms_url += &format!("{}) OR close_synonym_words:({})^{} OR distant_synonym_words:({})^{} OR definition:({})^{})",
                              query_part, query_part, config.close_synonym_boost,
                              query_part, config.distant_synonym_boost,
                              query_part, config.term_definition_boost);
    }
    Some(terms_url)
}

pub fn term_complete(config: &ServerConfig, cv_name: &str, q: &str)
                     -> Result<Vec<SolrTermSummary>, String>
{
    if let Some(terms_url) = make_terms_url(config, cv_name, q) {
        match reqwest::blocking::get(&terms_url) {
            Ok(res) => {
                if res.status().is_success() {
                    match serde_json::from_reader(res) {
                        Ok(container) => {
                            let solr_response_container: SolrTermResponseContainer = container;
                            Ok(solr_response_container.response.docs)
                        },
                        Err(err) => {
                            Err(format!("Error parsing response from Solr: {:?}", err))
                        }
                    }
                } else {
                    if let Some(reason) = res.status().canonical_reason() {
                        Err(format!("HTTP request to Solr failed: {} - {}", res.status(), reason))
                    } else {
                        Err(format!("HTTP request to Solr failed with status code: {}",
                                    res.status()))
                    }
                }
            },
            Err(err) => {
                Err(format!("Error from Reqwest: {:?}", err))
            }
        }
    } else {
        Ok(vec![])
    }
}

pub fn term_summary_by_id(config: &ServerConfig, termid: &str)
                          -> Result<Option<SolrTermSummary>, String>
{
    let term_url = format!("{}/terms/select?wt=json&q=id:{}",
                           config.solr_url.to_owned(), termid.replace(":", r"\:"));

    print!("url: {}", &term_url);

    match reqwest::blocking::get(&term_url) {
        Ok(res) => {
            if res.status().is_success() {
                match serde_json::from_reader(res) {
                    Ok(container) => {
                        let solr_response_container: SolrTermResponseContainer = container;
                        let summaries = solr_response_container.response.docs;
                        if let Some(summary) = summaries.get(0) {
                            if summary.id == termid {
                                Ok(Some(summary.clone()))
                            } else {
                                Ok(None)
                            }
                        } else {
                            Ok(None)
                        }
                    },
                    Err(err) => {
                        Err(format!("Error parsing response from Solr: {:?}", err))
                    }
                }
            } else {
                if let Some(reason) = res.status().canonical_reason() {
                    Err(format!("HTTP request to Solr failed: {} - {}", res.status(), reason))
                } else {
                    Err(format!("HTTP request to Solr failed with status code: {}",
                                res.status()))
                }
            }
        },
        Err(err) => {
            Err(format!("Error from Reqwest: {:?}", err))
        }
    }
}
<commit_msg>Fix mangled URL for Solr term search<commit_after>use regex::Regex;

use crate::data_types::SolrTermSummary;

use std::collections::HashMap;

use crate::web::config::ServerConfig;
use crate::api::search_types::*;
use crate::api::search_utils::{do_solr_request, clean_words, get_query_part};

#[derive(Serialize, Deserialize, Debug)]
pub struct TermSearchMatch {
    pub id: String,
    pub cv_name: String,
    pub name: String,
    pub definition: String,
    pub hl: SolrMatchHighlight,
}

#[derive(Serialize, Deserialize, Debug)]
struct TermSearchRes {
    pub id: String,
    pub cv_name: String,
    pub name: String,
    pub definition: String,
}

#[derive(Deserialize, Debug)]
struct SolrTermSearchResponse {
    pub docs: Vec<TermSearchRes>,
}

#[derive(Deserialize, Debug)]
struct SolrTermSearchResponseContainer {
    pub highlighting: HashMap<SolrMatchId, SolrMatchHighlight>,
    pub response: SolrTermSearchResponse,
}

#[derive(Deserialize, Debug)]
struct SolrTermResponse {
    pub docs: Vec<SolrTermSummary>,
}

#[derive(Deserialize, Debug)]
struct SolrTermResponseContainer {
    pub response: SolrTermResponse,
}

pub fn search_terms(config: &ServerConfig, q: &str)
                    -> Result<Vec<TermSearchMatch>, String>
{
    let cv_name = &config.cv_name_for_terms_search;

    if let Some(mut url) = make_terms_url(config, cv_name, q) {
        url += "&hl=on&hl.fl=name,definition&fl=id,name,cv_name,definition";
        let res = do_solr_request(&url)?;

        match serde_json::from_reader(res) {
            Ok(container) => {
                let response_container: SolrTermSearchResponseContainer = container;
                let mut hl_by_id = response_container.highlighting;
                let matches: Vec<TermSearchMatch> = response_container.response.docs
                    .iter().map(|doc| TermSearchMatch {
                        id: String::from(&doc.id),
                        cv_name: String::from(doc.cv_name.as_str()),
                        name: String::from(doc.name.as_str()),
                        definition: doc.definition.clone(),
                        hl: hl_by_id.remove(doc.id.as_str())
                            .unwrap_or_else(HashMap::new),
                    }).collect();
                Ok(matches)
            },
            Err(err) => {
                Err(format!("Error parsing response from Solr: {:?}", err))
            }
        }
    } else {
        Ok(vec![])
    }
}

pub fn make_terms_url(config: &ServerConfig, cv_name: &str, q: &str) -> Option<String> {
    let mut terms_url =
        config.solr_url.to_owned() + "/terms/select?wt=json&q=";

    let termid_re_string = r"^(?P<prefix>[\w_]+):(?P<accession>\d+)$";
    let termid_re = Regex::new(termid_re_string).unwrap();

    let parent_re_string = r"^\[".to_owned() + termid_re_string + r"\]$";
    let parent_re = Regex::new(&parent_re_string).unwrap();

    let maybe_captures = parent_re.captures(cv_name);

    if let Some(captures) = maybe_captures {
        let prefix = captures.name("prefix").unwrap().as_str();
        let accession = captures.name("accession").unwrap().as_str();
        terms_url += &format!("(interesting_isa_parents:{}\\:{} OR id:{}\\:{})",
                              prefix, accession, prefix, accession);
    } else {
        terms_url += &format!("cv_name:+{}^=1", cv_name);
    }

    if let Some(captures) = termid_re.captures(q) {
        let prefix = captures.name("prefix").unwrap().as_str();
        let accession = captures.name("accession").unwrap().as_str();
        terms_url = format!(r"{} AND (id:{}\:{} OR secondary_identifiers:{}\:{})",
                            terms_url, prefix, accession, prefix, accession);
    } else {
        let clean_words = clean_words(q);

        if clean_words.is_empty() {
            return None;
        }

        terms_url += " AND annotation_count:[1 TO *] AND (name:(";

        let query_part = get_query_part(&clean_words);

        terms_url += &format!("{}) OR close_synonym_words:({})^{} OR distant_synonym_words:({})^{} OR definition:({})^{})",
                              query_part, query_part, config.close_synonym_boost,
                              query_part, config.distant_synonym_boost,
                              query_part, config.term_definition_boost);
    }
    Some(terms_url)
}

pub fn term_complete(config: &ServerConfig, cv_name: &str, q: &str)
                     -> Result<Vec<SolrTermSummary>, String>
{
    if let Some(terms_url) = make_terms_url(config, cv_name, q) {
        match reqwest::blocking::get(&terms_url) {
            Ok(res) => {
                if res.status().is_success() {
                    match serde_json::from_reader(res) {
                        Ok(container) => {
                            let solr_response_container: SolrTermResponseContainer = container;
                            Ok(solr_response_container.response.docs)
                        },
                        Err(err) => {
                            Err(format!("Error parsing response from Solr: {:?}", err))
                        }
                    }
                } else {
                    if let Some(reason) = res.status().canonical_reason() {
                        Err(format!("HTTP request to Solr failed: {} - {}", res.status(), reason))
                    } else {
                        Err(format!("HTTP request to Solr failed with status code: {}",
                                    res.status()))
                    }
                }
            },
            Err(err) => {
                Err(format!("Error from Reqwest: {:?}", err))
            }
        }
    } else {
        Ok(vec![])
    }
}

pub fn term_summary_by_id(config: &ServerConfig, termid: &str)
                          -> Result<Option<SolrTermSummary>, String>
{
    let term_url = format!("{}/terms/select?wt=json&q=id:{}",
                           config.solr_url.to_owned(), termid.replace(":", r"\:"));

    print!("url: {}", &term_url);

    match reqwest::blocking::get(&term_url) {
        Ok(res) => {
            if res.status().is_success() {
                match serde_json::from_reader(res) {
                    Ok(container) => {
                        let solr_response_container: SolrTermResponseContainer = container;
                        let summaries = solr_response_container.response.docs;
                        if let Some(summary) = summaries.get(0) {
                            if summary.id == termid {
                                Ok(Some(summary.clone()))
                            } else {
                                Ok(None)
                            }
                        } else {
                            Ok(None)
                        }
                    },
                    Err(err) => {
                        Err(format!("Error parsing response from Solr: {:?}", err))
                    }
                }
            } else {
                if let Some(reason) = res.status().canonical_reason() {
                    Err(format!("HTTP request to Solr failed: {} - {}", res.status(), reason))
                } else {
                    Err(format!("HTTP request to Solr failed with status code: {}",
                                res.status()))
                }
            }
        },
        Err(err) => {
            Err(format!("Error from Reqwest: {:?}", err))
        }
    }
}
<|endoftext|>