<commit_before>// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use prelude::*;
use super::support::PathLike;
use super::{Reader, Writer, Seek};
use super::{SeekSet, SeekStyle};
use rt::rtio::{RtioFileStream, IoFactory, IoFactoryObject};
use rt::io::{io_error, read_error, EndOfFile};
use rt::local::Local;
use rt::test::*;
use libc::{O_RDWR, O_RDONLY, O_WRONLY, S_IWUSR, S_IRUSR,
           O_CREAT, O_TRUNC, O_APPEND};

/// Instructions on how to open a file and return a `FileStream`.
enum FileMode {
    /// Opens an existing file. IoError if file does not exist.
    Open,
    /// Creates a file. IoError if file exists.
    Create,
    /// Opens an existing file or creates a new one.
    OpenOrCreate,
    /// Opens an existing file or creates a new one, positioned at EOF.
    Append,
    /// Opens an existing file, truncating it to 0 bytes.
    Truncate,
    /// Opens an existing file or creates a new one, truncating it to 0 bytes.
    CreateOrTruncate,
}

/// How should the file be opened? `FileStream`s opened with `Read` will
/// raise an `io_error` condition if written to.
enum FileAccess {
    Read,
    Write,
    ReadWrite
}

/// Abstraction representing *positional* access to a file. In this case,
/// *positional* refers to it keeping an encounter *cursor* of where in the
/// file a subsequent `read` or `write` will begin from. Users of a `FileStream`
/// can `seek` to move the cursor to a given location *within the bounds of the
/// file* and can ask to have the `FileStream` `tell` them the location, in
/// bytes, of the cursor.
///
/// This abstraction is roughly modeled on the access workflow as represented
/// by `open(2)`, `read(2)`, `write(2)` and friends.
///
/// The `open` and `unlink` static methods are provided to manage creation/removal
/// of files. All other methods operatin on an instance of `FileStream`.
pub struct FileStream {
    fd: ~RtioFileStream,
    last_nread: int,
}

impl FileStream {
    pub fn open<P: PathLike>(path: &P,
                             mode: FileMode,
                             access: FileAccess
                            ) -> Option<FileStream> {
        let open_result = unsafe {
            let io = Local::unsafe_borrow::<IoFactoryObject>();
            let mut flags = match mode {
                Open => 0,
                Create => O_CREAT,
                OpenOrCreate => O_CREAT,
                Append => O_APPEND,
                Truncate => O_TRUNC,
                CreateOrTruncate => O_TRUNC | O_CREAT
            };
            flags = match access {
                Read => flags | O_RDONLY,
                Write => flags | O_WRONLY,
                ReadWrite => flags | O_RDWR
            };
            let create_mode = match mode {
                Create|OpenOrCreate|CreateOrTruncate =>
                    S_IRUSR | S_IWUSR,
                _ => 0
            };
            (*io).fs_open(path, flags as int, create_mode as int)
        };
        match open_result {
            Ok(fd) => Some(FileStream {
                fd: fd,
                last_nread: -1
            }),
            Err(ioerr) => {
                io_error::cond.raise(ioerr);
                None
            }
        }
    }
    fn unlink<P: PathLike>(path: &P) {
        let unlink_result = unsafe {
            let io = Local::unsafe_borrow::<IoFactoryObject>();
            (*io).fs_unlink(path)
        };
        match unlink_result {
            Ok(_) => (),
            Err(ioerr) => {
                io_error::cond.raise(ioerr);
            }
        }
    }
}

impl Reader for FileStream {
    fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
        match self.fd.read(buf) {
            Ok(read) => {
                self.last_nread = read;
                match read {
                    0 => None,
                    _ => Some(read as uint)
                }
            },
            Err(ioerr) => {
                // EOF is indicated by returning None
                if ioerr.kind != EndOfFile {
                    read_error::cond.raise(ioerr);
                }
                return None;
            }
        }
    }

    fn eof(&mut self) -> bool {
        self.last_nread == 0
    }
}

impl Writer for FileStream {
    fn write(&mut self, buf: &[u8]) {
        match self.fd.write(buf) {
            Ok(_) => (),
            Err(ioerr) => {
                io_error::cond.raise(ioerr);
            }
        }
    }

    fn flush(&mut self) {
        match self.fd.flush() {
            Ok(_) => (),
            Err(ioerr) => {
                read_error::cond.raise(ioerr);
            }
        }
    }
}

impl Seek for FileStream {
    fn tell(&self) -> u64 {
        let res = self.fd.tell();
        match res {
            Ok(cursor) => cursor,
            Err(ioerr) => {
                read_error::cond.raise(ioerr);
                return -1;
            }
        }
    }

    fn seek(&mut self, pos: i64, style: SeekStyle) {
        use libc::{SEEK_SET, SEEK_CUR, SEEK_END};
        let whence = match style {
            SeekSet => SEEK_SET,
            SeekCur => SEEK_CUR,
            SeekEnd => SEEK_END
        } as i64;
        match self.fd.seek(pos, whence) {
            Ok(_) => {
                // successful seek resets EOF indocator
                self.last_nread = -1;
                ()
            },
            Err(ioerr) => {
                read_error::cond.raise(ioerr);
            }
        }
    }
}

fn file_test_smoke_test_impl() {
    do run_in_newsched_task {
        let message = "it's alright. have a good time";
        let filename = &Path("./rt_io_file_test.txt");
        {
            let mut write_stream = FileStream::open(filename, Create, ReadWrite).unwrap();
            write_stream.write(message.as_bytes());
        }
        {
            use str;
            let mut read_stream = FileStream::open(filename, Open, Read).unwrap();
            let mut read_buf = [0, .. 1028];
            let read_str = match read_stream.read(read_buf).unwrap() {
                -1|0 => fail!("shouldn't happen"),
                n => str::from_bytes(read_buf.slice_to(n))
            };
            assert!(read_str == message.to_owned());
        }
        FileStream::unlink(filename);
    }
}

#[test]
fn file_test_io_smoke_test() {
    file_test_smoke_test_impl();
}

fn file_test_invalid_path_opened_without_create_should_raise_condition_impl() {
    do run_in_newsched_task {
        let filename = &Path("./file_that_does_not_exist.txt");
        let mut called = false;
        do io_error::cond.trap(|_| {
            called = true;
        }).inside {
            let result = FileStream::open(filename, Open, Read);
            assert!(result.is_none());
        }
        assert!(called);
    }
}
#[test]
fn file_test_io_invalid_path_opened_without_create_should_raise_condition() {
    file_test_invalid_path_opened_without_create_should_raise_condition_impl();
}

fn file_test_unlinking_invalid_path_should_raise_condition_impl() {
    do run_in_newsched_task {
        let filename = &Path("./another_file_that_does_not_exist.txt");
        let mut called = false;
        do io_error::cond.trap(|e| {
            called = true;
        }).inside {
            FileStream::unlink(filename);
        }
        assert!(called);
    }
}
#[test]
fn file_test_iounlinking_invalid_path_should_raise_condition() {
    file_test_unlinking_invalid_path_should_raise_condition_impl();
}

fn file_test_io_non_positional_read_impl() {
    do run_in_newsched_task {
        use str;
        let message = "ten-four";
        let mut read_mem = [0, .. 8];
        let filename = &Path("./rt_io_file_test_positional.txt");
        {
            let mut rw_stream = FileStream::open(filename, Create, ReadWrite).unwrap();
            rw_stream.write(message.as_bytes());
        }
        {
            let mut read_stream = FileStream::open(filename, Open, Read).unwrap();
            {
                let read_buf = read_mem.mut_slice(0, 4);
                read_stream.read(read_buf);
            }
            {
                let read_buf = read_mem.mut_slice(4, 8);
                read_stream.read(read_buf);
            }
        }
        FileStream::unlink(filename);
        let read_str = str::from_bytes(read_mem);
        assert!(read_str == message.to_owned());
    }
}

#[test]
fn file_test_io_non_positional_read() {
    file_test_io_non_positional_read_impl();
}

fn file_test_io_seeking_impl() {
    do run_in_newsched_task {
        use str;
        let message = "ten-four";
        let mut read_mem = [0, .. 4];
        let set_cursor = 4 as u64;
        let mut tell_pos_pre_read;
        let mut tell_pos_post_read;
        let filename = &Path("./rt_io_file_test_seeking.txt");
        {
            let mut rw_stream = FileStream::open(filename, Create, ReadWrite).unwrap();
            rw_stream.write(message.as_bytes());
        }
        {
            let mut read_stream = FileStream::open(filename, Open, Read).unwrap();
            read_stream.seek(set_cursor as i64, SeekSet);
            tell_pos_pre_read = read_stream.tell();
            read_stream.read(read_mem);
            tell_pos_post_read = read_stream.tell();
        }
        FileStream::unlink(filename);
        let read_str = str::from_bytes(read_mem);
        assert!(read_str == message.slice(4, 8).to_owned());
        assert!(tell_pos_pre_read == set_cursor);
        assert!(tell_pos_post_read == message.len() as u64);
    }
}
#[test]
fn file_test_io_seek_and_tell_smoke_test() {
    file_test_io_seeking_impl();
}
<commit_msg>std: lint appeasement for unused param in condition handler<commit_after>// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use prelude::*;
use super::support::PathLike;
use super::{Reader, Writer, Seek};
use super::{SeekSet, SeekStyle};
use rt::rtio::{RtioFileStream, IoFactory, IoFactoryObject};
use rt::io::{io_error, read_error, EndOfFile};
use rt::local::Local;
use rt::test::*;
use libc::{O_RDWR, O_RDONLY, O_WRONLY, S_IWUSR, S_IRUSR,
           O_CREAT, O_TRUNC, O_APPEND};

/// Instructions on how to open a file and return a `FileStream`.
enum FileMode {
    /// Opens an existing file. IoError if file does not exist.
    Open,
    /// Creates a file. IoError if file exists.
    Create,
    /// Opens an existing file or creates a new one.
    OpenOrCreate,
    /// Opens an existing file or creates a new one, positioned at EOF.
    Append,
    /// Opens an existing file, truncating it to 0 bytes.
    Truncate,
    /// Opens an existing file or creates a new one, truncating it to 0 bytes.
    CreateOrTruncate,
}

/// How should the file be opened? `FileStream`s opened with `Read` will
/// raise an `io_error` condition if written to.
enum FileAccess {
    Read,
    Write,
    ReadWrite
}

/// Abstraction representing *positional* access to a file. In this case,
/// *positional* refers to it keeping an encounter *cursor* of where in the
/// file a subsequent `read` or `write` will begin from. Users of a `FileStream`
/// can `seek` to move the cursor to a given location *within the bounds of the
/// file* and can ask to have the `FileStream` `tell` them the location, in
/// bytes, of the cursor.
///
/// This abstraction is roughly modeled on the access workflow as represented
/// by `open(2)`, `read(2)`, `write(2)` and friends.
///
/// The `open` and `unlink` static methods are provided to manage creation/removal
/// of files. All other methods operatin on an instance of `FileStream`.
pub struct FileStream {
    fd: ~RtioFileStream,
    last_nread: int,
}

impl FileStream {
    pub fn open<P: PathLike>(path: &P,
                             mode: FileMode,
                             access: FileAccess
                            ) -> Option<FileStream> {
        let open_result = unsafe {
            let io = Local::unsafe_borrow::<IoFactoryObject>();
            let mut flags = match mode {
                Open => 0,
                Create => O_CREAT,
                OpenOrCreate => O_CREAT,
                Append => O_APPEND,
                Truncate => O_TRUNC,
                CreateOrTruncate => O_TRUNC | O_CREAT
            };
            flags = match access {
                Read => flags | O_RDONLY,
                Write => flags | O_WRONLY,
                ReadWrite => flags | O_RDWR
            };
            let create_mode = match mode {
                Create|OpenOrCreate|CreateOrTruncate =>
                    S_IRUSR | S_IWUSR,
                _ => 0
            };
            (*io).fs_open(path, flags as int, create_mode as int)
        };
        match open_result {
            Ok(fd) => Some(FileStream {
                fd: fd,
                last_nread: -1
            }),
            Err(ioerr) => {
                io_error::cond.raise(ioerr);
                None
            }
        }
    }
    fn unlink<P: PathLike>(path: &P) {
        let unlink_result = unsafe {
            let io = Local::unsafe_borrow::<IoFactoryObject>();
            (*io).fs_unlink(path)
        };
        match unlink_result {
            Ok(_) => (),
            Err(ioerr) => {
                io_error::cond.raise(ioerr);
            }
        }
    }
}

impl Reader for FileStream {
    fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
        match self.fd.read(buf) {
            Ok(read) => {
                self.last_nread = read;
                match read {
                    0 => None,
                    _ => Some(read as uint)
                }
            },
            Err(ioerr) => {
                // EOF is indicated by returning None
                if ioerr.kind != EndOfFile {
                    read_error::cond.raise(ioerr);
                }
                return None;
            }
        }
    }

    fn eof(&mut self) -> bool {
        self.last_nread == 0
    }
}

impl Writer for FileStream {
    fn write(&mut self, buf: &[u8]) {
        match self.fd.write(buf) {
            Ok(_) => (),
            Err(ioerr) => {
                io_error::cond.raise(ioerr);
            }
        }
    }

    fn flush(&mut self) {
        match self.fd.flush() {
            Ok(_) => (),
            Err(ioerr) => {
                read_error::cond.raise(ioerr);
            }
        }
    }
}

impl Seek for FileStream {
    fn tell(&self) -> u64 {
        let res = self.fd.tell();
        match res {
            Ok(cursor) => cursor,
            Err(ioerr) => {
                read_error::cond.raise(ioerr);
                return -1;
            }
        }
    }

    fn seek(&mut self, pos: i64, style: SeekStyle) {
        use libc::{SEEK_SET, SEEK_CUR, SEEK_END};
        let whence = match style {
            SeekSet => SEEK_SET,
            SeekCur => SEEK_CUR,
            SeekEnd => SEEK_END
        } as i64;
        match self.fd.seek(pos, whence) {
            Ok(_) => {
                // successful seek resets EOF indocator
                self.last_nread = -1;
                ()
            },
            Err(ioerr) => {
                read_error::cond.raise(ioerr);
            }
        }
    }
}

fn file_test_smoke_test_impl() {
    do run_in_newsched_task {
        let message = "it's alright. have a good time";
        let filename = &Path("./rt_io_file_test.txt");
        {
            let mut write_stream = FileStream::open(filename, Create, ReadWrite).unwrap();
            write_stream.write(message.as_bytes());
        }
        {
            use str;
            let mut read_stream = FileStream::open(filename, Open, Read).unwrap();
            let mut read_buf = [0, .. 1028];
            let read_str = match read_stream.read(read_buf).unwrap() {
                -1|0 => fail!("shouldn't happen"),
                n => str::from_bytes(read_buf.slice_to(n))
            };
            assert!(read_str == message.to_owned());
        }
        FileStream::unlink(filename);
    }
}

#[test]
fn file_test_io_smoke_test() {
    file_test_smoke_test_impl();
}

fn file_test_invalid_path_opened_without_create_should_raise_condition_impl() {
    do run_in_newsched_task {
        let filename = &Path("./file_that_does_not_exist.txt");
        let mut called = false;
        do io_error::cond.trap(|_| {
            called = true;
        }).inside {
            let result = FileStream::open(filename, Open, Read);
            assert!(result.is_none());
        }
        assert!(called);
    }
}
#[test]
fn file_test_io_invalid_path_opened_without_create_should_raise_condition() {
    file_test_invalid_path_opened_without_create_should_raise_condition_impl();
}

fn file_test_unlinking_invalid_path_should_raise_condition_impl() {
    do run_in_newsched_task {
        let filename = &Path("./another_file_that_does_not_exist.txt");
        let mut called = false;
        do io_error::cond.trap(|_| {
            called = true;
        }).inside {
            FileStream::unlink(filename);
        }
        assert!(called);
    }
}
#[test]
fn file_test_iounlinking_invalid_path_should_raise_condition() {
    file_test_unlinking_invalid_path_should_raise_condition_impl();
}

fn file_test_io_non_positional_read_impl() {
    do run_in_newsched_task {
        use str;
        let message = "ten-four";
        let mut read_mem = [0, .. 8];
        let filename = &Path("./rt_io_file_test_positional.txt");
        {
            let mut rw_stream = FileStream::open(filename, Create, ReadWrite).unwrap();
            rw_stream.write(message.as_bytes());
        }
        {
            let mut read_stream = FileStream::open(filename, Open, Read).unwrap();
            {
                let read_buf = read_mem.mut_slice(0, 4);
                read_stream.read(read_buf);
            }
            {
                let read_buf = read_mem.mut_slice(4, 8);
                read_stream.read(read_buf);
            }
        }
        FileStream::unlink(filename);
        let read_str = str::from_bytes(read_mem);
        assert!(read_str == message.to_owned());
    }
}

#[test]
fn file_test_io_non_positional_read() {
    file_test_io_non_positional_read_impl();
}

fn file_test_io_seeking_impl() {
    do run_in_newsched_task {
        use str;
        let message = "ten-four";
        let mut read_mem = [0, .. 4];
        let set_cursor = 4 as u64;
        let mut tell_pos_pre_read;
        let mut tell_pos_post_read;
        let filename = &Path("./rt_io_file_test_seeking.txt");
        {
            let mut rw_stream = FileStream::open(filename, Create, ReadWrite).unwrap();
            rw_stream.write(message.as_bytes());
        }
        {
            let mut read_stream = FileStream::open(filename, Open, Read).unwrap();
            read_stream.seek(set_cursor as i64, SeekSet);
            tell_pos_pre_read = read_stream.tell();
            read_stream.read(read_mem);
            tell_pos_post_read = read_stream.tell();
        }
        FileStream::unlink(filename);
        let read_str = str::from_bytes(read_mem);
        assert!(read_str == message.slice(4, 8).to_owned());
        assert!(tell_pos_pre_read == set_cursor);
        assert!(tell_pos_post_read == message.len() as u64);
    }
}
#[test]
fn file_test_io_seek_and_tell_smoke_test() {
    file_test_io_seeking_impl();
}
<|endoftext|>


<commit_before>use log::debug;
use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor};
use serde::forward_to_deserialize_any;
use serde::serde_if_integer128;

use super::MaxMindDBError;
use super::MaxMindDBError::DecodingError;

fn to_usize(base: u8, bytes: &[u8]) -> usize {
    bytes
        .iter()
        .fold(base as usize, |acc, &b| (acc << 8) | b as usize)
}

#[derive(Debug)]
pub struct Decoder<'de> {
    buf: &'de [u8],
    current_ptr: usize,
}

impl<'de> Decoder<'de> {
    pub fn new(buf: &'de [u8], start_ptr: usize) -> Decoder<'de> {
        Decoder {
            buf,
            current_ptr: start_ptr,
        }
    }

    fn eat_byte(&mut self) -> u8 {
        let b = self.buf[self.current_ptr];
        self.current_ptr += 1;
        b
    }

    fn size_from_ctrl_byte(&mut self, ctrl_byte: u8, type_num: u8) -> usize {
        let size = (ctrl_byte & 0x1f) as usize;
        // extended
        if type_num == 0 {
            return size;
        }

        let bytes_to_read = if size > 28 { size - 28 } else { 0 };

        let new_offset = self.current_ptr + bytes_to_read;
        let size_bytes = &self.buf[self.current_ptr..new_offset];
        self.current_ptr = new_offset;

        match size {
            s if s < 29 => s,
            29 => 29_usize + size_bytes[0] as usize,
            30 => 285_usize + to_usize(0, size_bytes),
            _ => 65_821_usize + to_usize(0, size_bytes),
        }
    }

    fn decode_any<V: Visitor<'de>>(&mut self, visitor: V) -> DecodeResult<V::Value> {
        let ctrl_byte = self.eat_byte();
        let mut type_num = ctrl_byte >> 5;
        // Extended type
        if type_num == 0 {
            type_num = self.eat_byte() + 7;
        }
        let size = self.size_from_ctrl_byte(ctrl_byte, type_num);

        match type_num {
            1 => {
                let new_ptr = self.decode_pointer(size);
                let prev_ptr = self.current_ptr;
                self.current_ptr = new_ptr;

                let res = self.decode_any(visitor);
                self.current_ptr = prev_ptr;
                res
            }
            2 => self.decode_string(visitor, size),
            3 => self.decode_double(visitor, size),
            4 => self.decode_bytes(visitor, size),
            5 => self.decode_uint16(visitor, size),
            6 => self.decode_uint32(visitor, size),
            7 => self.decode_map(visitor, size),
            8 => self.decode_int(visitor, size),
            9 => self.decode_uint64(visitor, size),
            10 => {
                serde_if_integer128! {
                    return self.decode_uint128(visitor, size);
                }

                #[allow(unreachable_code)]
                self.decode_bytes(visitor, size)
            }
            11 => self.decode_array(visitor, size),
            14 => self.decode_bool(visitor, size),
            15 => self.decode_float(visitor, size),
            u => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "Unknown data type: {:?}",
                u
            ))),
        }
    }

    fn decode_array<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        visitor.visit_seq(ArrayAccess {
            de: self,
            count: size,
        })
    }

    fn decode_bool<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        match size {
            0 | 1 => visitor.visit_bool(size != 0),
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "bool of size {:?}",
                s
            ))),
        }
    }

    fn decode_bytes<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        let new_offset = self.current_ptr + size;
        let u8_slice = &self.buf[self.current_ptr..new_offset];
        self.current_ptr = new_offset;

        visitor.visit_borrowed_bytes(u8_slice)
    }

    fn decode_float<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        match size {
            4 => {
                let new_offset = self.current_ptr + size;
                let value = self.buf[self.current_ptr..new_offset]
                    .iter()
                    .fold(0_u32, |acc, &b| (acc << 8) | u32::from(b));
                self.current_ptr = new_offset;

                let float_value = f32::from_bits(value);
                visitor.visit_f32(float_value)
            }
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "float of size {:?}",
                s
            ))),
        }
    }

    fn decode_double<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        match size {
            8 => {
                let new_offset = self.current_ptr + size;
                let value = self.buf[self.current_ptr..new_offset]
                    .iter()
                    .fold(0_u64, |acc, &b| (acc << 8) | u64::from(b));
                self.current_ptr = new_offset;

                let float_value = f64::from_bits(value);
                visitor.visit_f64(float_value)
            }
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "double of size {:?}",
                s
            ))),
        }
    }

    fn decode_uint64<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        match size {
            s if s <= 8 => {
                let new_offset = self.current_ptr + size;

                let value = self.buf[self.current_ptr..new_offset]
                    .iter()
                    .fold(0_u64, |acc, &b| (acc << 8) | u64::from(b));
                self.current_ptr = new_offset;
                visitor.visit_u64(value)
            }
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "u64 of size {:?}",
                s
            ))),
        }
    }

    serde_if_integer128! {
        fn decode_uint128<V: Visitor<'de>>(
            &mut self,
            visitor: V,
            size: usize,
        ) -> DecodeResult<V::Value> {
            match size {
                s if s <= 16 => {
                    let new_offset = self.current_ptr + size;

                    let value = self.buf[self.current_ptr..new_offset]
                        .iter()
                        .fold(0_u128, |acc, &b| (acc << 8) | u128::from(b));
                    self.current_ptr = new_offset;
                    visitor.visit_u128(value)
                }
                s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                    "u128 of size {:?}",
                    s
                ))),
            }
        }
    }

    fn decode_uint32<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        match size {
            s if s <= 4 => {
                let new_offset = self.current_ptr + size;

                let value = self.buf[self.current_ptr..new_offset]
                    .iter()
                    .fold(0_u32, |acc, &b| (acc << 8) | u32::from(b));
                self.current_ptr = new_offset;
                visitor.visit_u32(value)
            }
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "u32 of size {:?}",
                s
            ))),
        }
    }

    fn decode_uint16<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        match size {
            s if s <= 2 => {
                let new_offset = self.current_ptr + size;

                let value = self.buf[self.current_ptr..new_offset]
                    .iter()
                    .fold(0_u16, |acc, &b| (acc << 8) | u16::from(b));
                self.current_ptr = new_offset;
                visitor.visit_u16(value)
            }
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "u16 of size {:?}",
                s
            ))),
        }
    }

    fn decode_int<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        match size {
            s if s <= 4 => {
                let new_offset = self.current_ptr + size;

                let value = self.buf[self.current_ptr..new_offset]
                    .iter()
                    .fold(0_i32, |acc, &b| (acc << 8) | i32::from(b));
                self.current_ptr = new_offset;
                visitor.visit_i32(value)
            }
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "int32 of size {:?}",
                s
            ))),
        }
    }

    fn decode_map<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        visitor.visit_map(MapAccessor {
            de: self,
            count: size * 2,
        })
    }

    fn decode_pointer(&mut self, size: usize) -> usize {
        let pointer_value_offset = [0, 0, 2048, 526_336, 0];
        let pointer_size = ((size >> 3) & 0x3) + 1;
        let new_offset = self.current_ptr + pointer_size;
        let pointer_bytes = &self.buf[self.current_ptr..new_offset];
        self.current_ptr = new_offset;

        let base = if pointer_size == 4 {
            0
        } else {
            (size & 0x7) as u8
        };
        let unpacked = to_usize(base, pointer_bytes);

        unpacked + pointer_value_offset[pointer_size]
    }

    #[cfg(feature = "unsafe-str-decode")]
    fn decode_string<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        use std::str::from_utf8_unchecked;

        let new_offset: usize = self.current_ptr + size;
        let bytes = &self.buf[self.current_ptr..new_offset];
        self.current_ptr = new_offset;
        // SAFETY:
        // A corrupt maxminddb will cause undefined behaviour.
        // If the caller has verified the integrity of their database and trusts their upstream
        // provider, they can opt-into the performance gains provided by this unsafe function via
        // the `unsafe-str-decode` feature flag.
        // This can provide around 20% performance increase in the lookup benchmark.
        let v = unsafe { from_utf8_unchecked(bytes) };
        visitor.visit_borrowed_str(v)
    }

    #[cfg(not(feature = "unsafe-str-decode"))]
    fn decode_string<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        use std::str::from_utf8;

        let new_offset: usize = self.current_ptr + size;
        let bytes = &self.buf[self.current_ptr..new_offset];
        self.current_ptr = new_offset;
        match from_utf8(bytes) {
            Ok(v) => visitor.visit_borrowed_str(v),
            Err(_) => Err(MaxMindDBError::InvalidDatabaseError(
                "error decoding string".to_owned(),
            )),
        }
    }
}

pub type DecodeResult<T> = Result<T, MaxMindDBError>;

impl<'de: 'a, 'a> de::Deserializer<'de> for &'a mut Decoder<'de> {
    type Error = MaxMindDBError;

    fn deserialize_any<V>(self, visitor: V) -> DecodeResult<V::Value>
    where
        V: Visitor<'de>,
    {
        debug!("deserialize_any");

        self.decode_any(visitor)
    }

    fn deserialize_option<V>(self, visitor: V) -> DecodeResult<V::Value>
    where
        V: Visitor<'de>,
    {
        debug!("deserialize_option");

        visitor.visit_some(self)
    }

    forward_to_deserialize_any! {
        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
        bytes byte_buf unit unit_struct newtype_struct seq tuple
        tuple_struct map struct enum identifier ignored_any
    }
}

struct ArrayAccess<'a, 'de: 'a> {
    de: &'a mut Decoder<'de>,
    count: usize,
}

// `SeqAccess` is provided to the `Visitor` to give it the ability to iterate
// through elements of the sequence.
impl<'de, 'a> SeqAccess<'de> for ArrayAccess<'a, 'de> {
    type Error = MaxMindDBError;

    fn next_element_seed<T>(&mut self, seed: T) -> DecodeResult<Option<T::Value>>
    where
        T: DeserializeSeed<'de>,
    {
        // Check if there are no more elements.
        if self.count == 0 {
            return Ok(None);
        }
        self.count -= 1;

        // Deserialize an array element.
        seed.deserialize(&mut *self.de).map(Some)
    }
}

struct MapAccessor<'a, 'de: 'a> {
    de: &'a mut Decoder<'de>,
    count: usize,
}

// `MapAccess` is provided to the `Visitor` to give it the ability to iterate
// through entries of the map.
impl<'de, 'a> MapAccess<'de> for MapAccessor<'a, 'de> {
    type Error = MaxMindDBError;

    fn next_key_seed<K>(&mut self, seed: K) -> DecodeResult<Option<K::Value>>
    where
        K: DeserializeSeed<'de>,
    {
        // Check if there are no more entries.
        if self.count == 0 {
            return Ok(None);
        }
        self.count -= 1;

        // Deserialize a map key.
        seed.deserialize(&mut *self.de).map(Some)
    }

    fn next_value_seed<V>(&mut self, seed: V) -> DecodeResult<V::Value>
    where
        V: DeserializeSeed<'de>,
    {
        // Check if there are no more entries.
        if self.count == 0 {
            return Err(DecodingError("no more entries".to_owned()));
        }
        self.count -= 1;

        // Deserialize a map value.
        seed.deserialize(&mut *self.de)
    }
}
<commit_msg>Use try_into instead of shifting per byte<commit_after>use log::debug;
use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor};
use serde::forward_to_deserialize_any;
use serde::serde_if_integer128;
use std::convert::TryInto;

use super::MaxMindDBError;
use super::MaxMindDBError::DecodingError;

fn to_usize(base: u8, bytes: &[u8]) -> usize {
    bytes
        .iter()
        .fold(base as usize, |acc, &b| (acc << 8) | b as usize)
}

#[derive(Debug)]
pub struct Decoder<'de> {
    buf: &'de [u8],
    current_ptr: usize,
}

impl<'de> Decoder<'de> {
    pub fn new(buf: &'de [u8], start_ptr: usize) -> Decoder<'de> {
        Decoder {
            buf,
            current_ptr: start_ptr,
        }
    }

    fn eat_byte(&mut self) -> u8 {
        let b = self.buf[self.current_ptr];
        self.current_ptr += 1;
        b
    }

    fn size_from_ctrl_byte(&mut self, ctrl_byte: u8, type_num: u8) -> usize {
        let size = (ctrl_byte & 0x1f) as usize;
        // extended
        if type_num == 0 {
            return size;
        }

        let bytes_to_read = if size > 28 { size - 28 } else { 0 };

        let new_offset = self.current_ptr + bytes_to_read;
        let size_bytes = &self.buf[self.current_ptr..new_offset];
        self.current_ptr = new_offset;

        match size {
            s if s < 29 => s,
            29 => 29_usize + size_bytes[0] as usize,
            30 => 285_usize + to_usize(0, size_bytes),
            _ => 65_821_usize + to_usize(0, size_bytes),
        }
    }

    fn decode_any<V: Visitor<'de>>(&mut self, visitor: V) -> DecodeResult<V::Value> {
        let ctrl_byte = self.eat_byte();
        let mut type_num = ctrl_byte >> 5;
        // Extended type
        if type_num == 0 {
            type_num = self.eat_byte() + 7;
        }
        let size = self.size_from_ctrl_byte(ctrl_byte, type_num);

        match type_num {
            1 => {
                let new_ptr = self.decode_pointer(size);
                let prev_ptr = self.current_ptr;
                self.current_ptr = new_ptr;

                let res = self.decode_any(visitor);
                self.current_ptr = prev_ptr;
                res
            }
            2 => self.decode_string(visitor, size),
            3 => self.decode_double(visitor, size),
            4 => self.decode_bytes(visitor, size),
            5 => self.decode_uint16(visitor, size),
            6 => self.decode_uint32(visitor, size),
            7 => self.decode_map(visitor, size),
            8 => self.decode_int(visitor, size),
            9 => self.decode_uint64(visitor, size),
            10 => {
                serde_if_integer128! {
                    return self.decode_uint128(visitor, size);
                }

                #[allow(unreachable_code)]
                self.decode_bytes(visitor, size)
            }
            11 => self.decode_array(visitor, size),
            14 => self.decode_bool(visitor, size),
            15 => self.decode_float(visitor, size),
            u => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "Unknown data type: {:?}",
                u
            ))),
        }
    }

    fn decode_array<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        visitor.visit_seq(ArrayAccess {
            de: self,
            count: size,
        })
    }

    fn decode_bool<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        match size {
            0 | 1 => visitor.visit_bool(size != 0),
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "bool of size {:?}",
                s
            ))),
        }
    }

    fn decode_bytes<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        let new_offset = self.current_ptr + size;
        let u8_slice = &self.buf[self.current_ptr..new_offset];
        self.current_ptr = new_offset;

        visitor.visit_borrowed_bytes(u8_slice)
    }

    fn decode_float<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        let new_offset = self.current_ptr + size;
        let value: [u8; 4] = self.buf[self.current_ptr..new_offset]
            .try_into()
            .map_err(|_| {
                MaxMindDBError::InvalidDatabaseError(format!(
                    "float of size {:?}",
                    new_offset - self.current_ptr
                ))
            })?;
        self.current_ptr = new_offset;
        let float_value = f32::from_be_bytes(value);
        visitor.visit_f32(float_value)
    }

    fn decode_double<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        let new_offset = self.current_ptr + size;
        let value: [u8; 8] = self.buf[self.current_ptr..new_offset]
            .try_into()
            .map_err(|_| {
                MaxMindDBError::InvalidDatabaseError(format!(
                    "double of size {:?}",
                    new_offset - self.current_ptr
                ))
            })?;
        self.current_ptr = new_offset;
        let float_value = f64::from_be_bytes(value);
        visitor.visit_f64(float_value)
    }

    fn decode_uint64<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        match size {
            s if s <= 8 => {
                let new_offset = self.current_ptr + size;

                let value = self.buf[self.current_ptr..new_offset]
                    .iter()
                    .fold(0_u64, |acc, &b| (acc << 8) | u64::from(b));
                self.current_ptr = new_offset;
                visitor.visit_u64(value)
            }
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "u64 of size {:?}",
                s
            ))),
        }
    }

    serde_if_integer128! {
        fn decode_uint128<V: Visitor<'de>>(
            &mut self,
            visitor: V,
            size: usize,
        ) -> DecodeResult<V::Value> {
            match size {
                s if s <= 16 => {
                    let new_offset = self.current_ptr + size;

                    let value = self.buf[self.current_ptr..new_offset]
                        .iter()
                        .fold(0_u128, |acc, &b| (acc << 8) | u128::from(b));
                    self.current_ptr = new_offset;
                    visitor.visit_u128(value)
                }
                s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                    "u128 of size {:?}",
                    s
                ))),
            }
        }
    }

    fn decode_uint32<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        match size {
            s if s <= 4 => {
                let new_offset = self.current_ptr + size;

                let value = self.buf[self.current_ptr..new_offset]
                    .iter()
                    .fold(0_u32, |acc, &b| (acc << 8) | u32::from(b));
                self.current_ptr = new_offset;
                visitor.visit_u32(value)
            }
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "u32 of size {:?}",
                s
            ))),
        }
    }

    fn decode_uint16<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        match size {
            s if s <= 2 => {
                let new_offset = self.current_ptr + size;

                let value = self.buf[self.current_ptr..new_offset]
                    .iter()
                    .fold(0_u16, |acc, &b| (acc << 8) | u16::from(b));
                self.current_ptr = new_offset;
                visitor.visit_u16(value)
            }
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "u16 of size {:?}",
                s
            ))),
        }
    }

    fn decode_int<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        match size {
            s if s <= 4 => {
                let new_offset = self.current_ptr + size;

                let value = self.buf[self.current_ptr..new_offset]
                    .iter()
                    .fold(0_i32, |acc, &b| (acc << 8) | i32::from(b));
                self.current_ptr = new_offset;
                visitor.visit_i32(value)
            }
            s => Err(MaxMindDBError::InvalidDatabaseError(format!(
                "int32 of size {:?}",
                s
            ))),
        }
    }

    fn decode_map<V: Visitor<'de>>(&mut self, visitor: V, size: usize) -> DecodeResult<V::Value> {
        visitor.visit_map(MapAccessor {
            de: self,
            count: size * 2,
        })
    }

    fn decode_pointer(&mut self, size: usize) -> usize {
        let pointer_value_offset = [0, 0, 2048, 526_336, 0];
        let pointer_size = ((size >> 3) & 0x3) + 1;
        let new_offset = self.current_ptr + pointer_size;
        let pointer_bytes = &self.buf[self.current_ptr..new_offset];
        self.current_ptr = new_offset;

        let base = if pointer_size == 4 {
            0
        } else {
            (size & 0x7) as u8
        };
        let unpacked = to_usize(base, pointer_bytes);

        unpacked + pointer_value_offset[pointer_size]
    }

    #[cfg(feature = "unsafe-str-decode")]
    fn decode_string<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        use std::str::from_utf8_unchecked;

        let new_offset: usize = self.current_ptr + size;
        let bytes = &self.buf[self.current_ptr..new_offset];
        self.current_ptr = new_offset;
        // SAFETY:
        // A corrupt maxminddb will cause undefined behaviour.
        // If the caller has verified the integrity of their database and trusts their upstream
        // provider, they can opt-into the performance gains provided by this unsafe function via
        // the `unsafe-str-decode` feature flag.
        // This can provide around 20% performance increase in the lookup benchmark.
        let v = unsafe { from_utf8_unchecked(bytes) };
        visitor.visit_borrowed_str(v)
    }

    #[cfg(not(feature = "unsafe-str-decode"))]
    fn decode_string<V: Visitor<'de>>(
        &mut self,
        visitor: V,
        size: usize,
    ) -> DecodeResult<V::Value> {
        use std::str::from_utf8;

        let new_offset: usize = self.current_ptr + size;
        let bytes = &self.buf[self.current_ptr..new_offset];
        self.current_ptr = new_offset;
        match from_utf8(bytes) {
            Ok(v) => visitor.visit_borrowed_str(v),
            Err(_) => Err(MaxMindDBError::InvalidDatabaseError(
                "error decoding string".to_owned(),
            )),
        }
    }
}

pub type DecodeResult<T> = Result<T, MaxMindDBError>;

impl<'de: 'a, 'a> de::Deserializer<'de> for &'a mut Decoder<'de> {
    type Error = MaxMindDBError;

    fn deserialize_any<V>(self, visitor: V) -> DecodeResult<V::Value>
    where
        V: Visitor<'de>,
    {
        debug!("deserialize_any");

        self.decode_any(visitor)
    }

    fn deserialize_option<V>(self, visitor: V) -> DecodeResult<V::Value>
    where
        V: Visitor<'de>,
    {
        debug!("deserialize_option");

        visitor.visit_some(self)
    }

    forward_to_deserialize_any! {
        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
        bytes byte_buf unit unit_struct newtype_struct seq tuple
        tuple_struct map struct enum identifier ignored_any
    }
}

struct ArrayAccess<'a, 'de: 'a> {
    de: &'a mut Decoder<'de>,
    count: usize,
}

// `SeqAccess` is provided to the `Visitor` to give it the ability to iterate
// through elements of the sequence.
impl<'de, 'a> SeqAccess<'de> for ArrayAccess<'a, 'de> {
    type Error = MaxMindDBError;

    fn next_element_seed<T>(&mut self, seed: T) -> DecodeResult<Option<T::Value>>
    where
        T: DeserializeSeed<'de>,
    {
        // Check if there are no more elements.
        if self.count == 0 {
            return Ok(None);
        }
        self.count -= 1;

        // Deserialize an array element.
        seed.deserialize(&mut *self.de).map(Some)
    }
}

struct MapAccessor<'a, 'de: 'a> {
    de: &'a mut Decoder<'de>,
    count: usize,
}

// `MapAccess` is provided to the `Visitor` to give it the ability to iterate
// through entries of the map.
impl<'de, 'a> MapAccess<'de> for MapAccessor<'a, 'de> {
    type Error = MaxMindDBError;

    fn next_key_seed<K>(&mut self, seed: K) -> DecodeResult<Option<K::Value>>
    where
        K: DeserializeSeed<'de>,
    {
        // Check if there are no more entries.
        if self.count == 0 {
            return Ok(None);
        }
        self.count -= 1;

        // Deserialize a map key.
        seed.deserialize(&mut *self.de).map(Some)
    }

    fn next_value_seed<V>(&mut self, seed: V) -> DecodeResult<V::Value>
    where
        V: DeserializeSeed<'de>,
    {
        // Check if there are no more entries.
        if self.count == 0 {
            return Err(DecodingError("no more entries".to_owned()));
        }
        self.count -= 1;

        // Deserialize a map value.
        seed.deserialize(&mut *self.de)
    }
}
<|endoftext|>


<commit_before>//! REPL for the gluon programming language
#![doc(html_root_url = "https://docs.rs/gluon_repl/0.4.1")] // # GLUON

extern crate app_dirs;
#[macro_use]
extern crate clap;
extern crate codespan;
extern crate codespan_reporting;
#[cfg(feature = "env_logger")]
extern crate env_logger;
extern crate futures;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate structopt;
extern crate tokio;
extern crate tokio_signal;
extern crate walkdir;

extern crate gluon;
extern crate gluon_doc;
extern crate gluon_format;
#[macro_use]
extern crate gluon_vm;

use std::ffi::OsStr;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;

use codespan_reporting::termcolor;
use structopt::StructOpt;
use walkdir::WalkDir;

use futures::future;
use tokio::runtime::Runtime;

use gluon::base;
use gluon::parser;
use gluon::vm;

use base::filename_to_module;

use gluon::vm::thread::ThreadInternal;
use gluon::vm::Error as VMError;
use gluon::{new_vm, Compiler, Error, Result, Thread};

mod repl;

const APP_INFO: app_dirs::AppInfo = app_dirs::AppInfo {
    name: "gluon-repl",
    author: "gluon-lang",
};

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Color {
    Auto,
    Always,
    AlwaysAnsi,
    Never,
}

impl Into<termcolor::ColorChoice> for Color {
    fn into(self) -> termcolor::ColorChoice {
        use termcolor::ColorChoice::*;
        match self {
            Color::Auto => Auto,
            Color::Always => Always,
            Color::AlwaysAnsi => AlwaysAnsi,
            Color::Never => Never,
        }
    }
}

impl Default for Color {
    fn default() -> Color {
        Color::Auto
    }
}

impl ::std::str::FromStr for Color {
    type Err = &'static str;
    fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
        use Color::*;
        Ok(match s {
            "auto" => Auto,
            "always" => Always,
            "always-ansi" => AlwaysAnsi,
            "never" => Never,
            _ => return Err("Expected on of auto, always, always-ansi, never"),
        })
    }
}

macro_rules! define_vmtype {
    ($name:ident) => {
        impl ::gluon::vm::api::VmType for $name {
            type Type = $name;
            fn make_type(vm: &::gluon::Thread) -> ::base::types::ArcType {
                let typ = concat!("repl_types.", stringify!($name));
                (*vm.global_env().get_env().find_type_info(typ).unwrap())
                    .clone()
                    .into_type()
            }
        }
    };
}

define_vmtype! { Color }

#[derive(StructOpt)]
#[structopt(about = "Formats gluon source code")]
pub struct FmtOpt {
    #[structopt(
        name = "FILE",
        parse(from_os_str),
        help = "Formats each file"
    )]
    input: Vec<PathBuf>,
}

#[derive(StructOpt)]
pub enum SubOpt {
    #[structopt(name = "fmt", about = "Formats gluon source code")]
    Fmt(FmtOpt),
    #[structopt(name = "doc", about = "Documents gluon source code")]
    Doc(::gluon_doc::Opt),
}

const LONG_VERSION: &str = concat!(crate_version!(), "\n", "commit: ", env!("GIT_HASH"));

#[derive(StructOpt)]
#[structopt(
    about = "executes gluon programs",
    raw(long_version = "LONG_VERSION")
)]
pub struct Opt {
    #[structopt(short = "i", long = "interactive", help = "Starts the repl")]
    interactive: bool,
    #[structopt(
        long = "color",
        default_value = "auto",
        help = "Coloring: auto, always, always-ansi, never"
    )]
    color: Color,
    #[structopt(
        name = "FILE",
        help = "Executes each file as a gluon program"
    )]
    input: Vec<String>,
    #[structopt(subcommand)]
    subcommand_opt: Option<SubOpt>,
}

fn run_files<I>(compiler: &mut Compiler, vm: &Thread, files: I) -> Result<()>
where
    I: IntoIterator,
    I::Item: AsRef<str>,
{
    for file in files {
        compiler.load_file(&vm, file.as_ref())?;
    }
    Ok(())
}

#[cfg(feature = "env_logger")]
fn init_env_logger() {
    let _ = ::env_logger::try_init();
}

#[cfg(not(feature = "env_logger"))]
fn init_env_logger() {}

fn format(file: &str, file_map: Arc<codespan::FileMap>) -> Result<String> {
    use gluon_format::format_expr;

    let mut compiler = Compiler::new();
    let thread = new_vm();

    Ok(format_expr(&mut compiler, &thread, file, file_map.src())?)
}

fn fmt_file(name: &Path) -> Result<()> {
    use std::fs::File;
    use std::io::Read;

    let mut buffer = String::new();
    {
        let mut input_file = File::open(name)?;
        input_file.read_to_string(&mut buffer)?;
    }

    let module_name = filename_to_module(&name.display().to_string());
    let mut code_map = codespan::CodeMap::new();
    let file_map = code_map.add_filemap(module_name.clone().into(), buffer);
    let formatted = format(&module_name, file_map.clone())?;

    // Avoid touching the .glu file if it did not change
    if file_map.src() != formatted {
        let bk_name = name.with_extension("glu.bk");
        let tmp_name = name.with_extension("tmp");
        {
            let mut backup = File::create(&*bk_name)?;
            backup.write_all(formatted.as_bytes())?;
        }
        fs::rename(name, tmp_name)?;
        fs::rename(bk_name, name)?;
    }
    Ok(())
}

fn fmt_stdio() -> Result<()> {
    use std::io::{stdin, stdout, Read};

    let mut buffer = String::new();
    stdin().read_to_string(&mut buffer)?;

    let mut code_map = codespan::CodeMap::new();
    let file_map = code_map.add_filemap("STDIN".into(), buffer);

    let formatted = format("STDIN", file_map)?;
    stdout().write_all(formatted.as_bytes())?;
    Ok(())
}

fn run(
    opt: &Opt,
    compiler: &mut Compiler,
    color: Color,
    vm: &Thread,
) -> std::result::Result<(), gluon::Error> {
    match opt.subcommand_opt {
        Some(SubOpt::Fmt(ref fmt_opt)) => {
            if !fmt_opt.input.is_empty() {
                let mut gluon_files = fmt_opt
                    .input
                    .iter()
                    .flat_map(|arg| {
                        WalkDir::new(arg).into_iter().filter_map(|entry| {
                            entry.ok().and_then(|entry| {
                                if entry.file_type().is_file()
                                    && entry.path().extension() == Some(OsStr::new("glu"))
                                {
                                    Some(entry.path().to_owned())
                                } else {
                                    None
                                }
                            })
                        })
                    })
                    .collect::<Vec<_>>();
                gluon_files.sort();
                gluon_files.dedup();

                for file in gluon_files {
                    fmt_file(&file)?;
                }
            } else {
                fmt_stdio()?;
            }
        }
        Some(SubOpt::Doc(ref doc_opt)) => {
            let input = &doc_opt.input;
            let output = &doc_opt.output;
            gluon_doc::generate_for_path(&new_vm(), input, output)
                .map_err(|err| format!("{}\n{}", err, err.backtrace()))?;
        }
        None => if opt.interactive {
            let mut runtime = Runtime::new()?;
            runtime.block_on(future::lazy(move || repl::run(color)))?;
        } else if !opt.input.is_empty() {
            run_files(compiler, &vm, &opt.input)?;
        } else {
            write!(io::stderr(), "{}", Opt::clap().get_matches().usage())
                .expect("Error writing help to stderr");
        },
    }
    Ok(())
}

fn main() {
    init_env_logger();

    let opt = Opt::from_args();

    let mut compiler = Compiler::new().run_io(true);
    let vm = new_vm();

    if let Err(err) = run(&opt, &mut compiler, opt.color, &vm) {
        match err {
            Error::VM(VMError::Message(_)) => eprintln!("{}\n{}", err, vm.context().stacktrace(0)),
            _ => {
                let mut stderr = termcolor::StandardStream::stderr(opt.color.into());
                if let Err(err) = err.emit(&mut stderr, compiler.code_map()) {
                    eprintln!("{}", err);
                }
            }
        }
        ::std::process::exit(1);
    }
}

#[cfg(test)]
mod tests {
    // If nothing else this suppresses the unused imports warnings when compiling in test mode
    #[test]
    fn execute_repl_help() {
        super::main();
    }
}
<commit_msg>Add missing newline after usage when no arguments given<commit_after>//! REPL for the gluon programming language
#![doc(html_root_url = "https://docs.rs/gluon_repl/0.4.1")] // # GLUON

extern crate app_dirs;
#[macro_use]
extern crate clap;
extern crate codespan;
extern crate codespan_reporting;
#[cfg(feature = "env_logger")]
extern crate env_logger;
extern crate futures;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate structopt;
extern crate tokio;
extern crate tokio_signal;
extern crate walkdir;

extern crate gluon;
extern crate gluon_doc;
extern crate gluon_format;
#[macro_use]
extern crate gluon_vm;

use std::ffi::OsStr;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;

use codespan_reporting::termcolor;
use structopt::StructOpt;
use walkdir::WalkDir;

use futures::future;
use tokio::runtime::Runtime;

use gluon::base;
use gluon::parser;
use gluon::vm;

use base::filename_to_module;

use gluon::vm::thread::ThreadInternal;
use gluon::vm::Error as VMError;
use gluon::{new_vm, Compiler, Error, Result, Thread};

mod repl;

const APP_INFO: app_dirs::AppInfo = app_dirs::AppInfo {
    name: "gluon-repl",
    author: "gluon-lang",
};

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Color {
    Auto,
    Always,
    AlwaysAnsi,
    Never,
}

impl Into<termcolor::ColorChoice> for Color {
    fn into(self) -> termcolor::ColorChoice {
        use termcolor::ColorChoice::*;
        match self {
            Color::Auto => Auto,
            Color::Always => Always,
            Color::AlwaysAnsi => AlwaysAnsi,
            Color::Never => Never,
        }
    }
}

impl Default for Color {
    fn default() -> Color {
        Color::Auto
    }
}

impl ::std::str::FromStr for Color {
    type Err = &'static str;
    fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
        use Color::*;
        Ok(match s {
            "auto" => Auto,
            "always" => Always,
            "always-ansi" => AlwaysAnsi,
            "never" => Never,
            _ => return Err("Expected on of auto, always, always-ansi, never"),
        })
    }
}

macro_rules! define_vmtype {
    ($name:ident) => {
        impl ::gluon::vm::api::VmType for $name {
            type Type = $name;
            fn make_type(vm: &::gluon::Thread) -> ::base::types::ArcType {
                let typ = concat!("repl_types.", stringify!($name));
                (*vm.global_env().get_env().find_type_info(typ).unwrap())
                    .clone()
                    .into_type()
            }
        }
    };
}

define_vmtype! { Color }

#[derive(StructOpt)]
#[structopt(about = "Formats gluon source code")]
pub struct FmtOpt {
    #[structopt(
        name = "FILE",
        parse(from_os_str),
        help = "Formats each file"
    )]
    input: Vec<PathBuf>,
}

#[derive(StructOpt)]
pub enum SubOpt {
    #[structopt(name = "fmt", about = "Formats gluon source code")]
    Fmt(FmtOpt),
    #[structopt(name = "doc", about = "Documents gluon source code")]
    Doc(::gluon_doc::Opt),
}

const LONG_VERSION: &str = concat!(crate_version!(), "\n", "commit: ", env!("GIT_HASH"));

#[derive(StructOpt)]
#[structopt(
    about = "executes gluon programs",
    raw(long_version = "LONG_VERSION")
)]
pub struct Opt {
    #[structopt(short = "i", long = "interactive", help = "Starts the repl")]
    interactive: bool,
    #[structopt(
        long = "color",
        default_value = "auto",
        help = "Coloring: auto, always, always-ansi, never"
    )]
    color: Color,
    #[structopt(
        name = "FILE",
        help = "Executes each file as a gluon program"
    )]
    input: Vec<String>,
    #[structopt(subcommand)]
    subcommand_opt: Option<SubOpt>,
}

fn run_files<I>(compiler: &mut Compiler, vm: &Thread, files: I) -> Result<()>
where
    I: IntoIterator,
    I::Item: AsRef<str>,
{
    for file in files {
        compiler.load_file(&vm, file.as_ref())?;
    }
    Ok(())
}

#[cfg(feature = "env_logger")]
fn init_env_logger() {
    let _ = ::env_logger::try_init();
}

#[cfg(not(feature = "env_logger"))]
fn init_env_logger() {}

fn format(file: &str, file_map: Arc<codespan::FileMap>) -> Result<String> {
    use gluon_format::format_expr;

    let mut compiler = Compiler::new();
    let thread = new_vm();

    Ok(format_expr(&mut compiler, &thread, file, file_map.src())?)
}

fn fmt_file(name: &Path) -> Result<()> {
    use std::fs::File;
    use std::io::Read;

    let mut buffer = String::new();
    {
        let mut input_file = File::open(name)?;
        input_file.read_to_string(&mut buffer)?;
    }

    let module_name = filename_to_module(&name.display().to_string());
    let mut code_map = codespan::CodeMap::new();
    let file_map = code_map.add_filemap(module_name.clone().into(), buffer);
    let formatted = format(&module_name, file_map.clone())?;

    // Avoid touching the .glu file if it did not change
    if file_map.src() != formatted {
        let bk_name = name.with_extension("glu.bk");
        let tmp_name = name.with_extension("tmp");
        {
            let mut backup = File::create(&*bk_name)?;
            backup.write_all(formatted.as_bytes())?;
        }
        fs::rename(name, tmp_name)?;
        fs::rename(bk_name, name)?;
    }
    Ok(())
}

fn fmt_stdio() -> Result<()> {
    use std::io::{stdin, stdout, Read};

    let mut buffer = String::new();
    stdin().read_to_string(&mut buffer)?;

    let mut code_map = codespan::CodeMap::new();
    let file_map = code_map.add_filemap("STDIN".into(), buffer);

    let formatted = format("STDIN", file_map)?;
    stdout().write_all(formatted.as_bytes())?;
    Ok(())
}

fn run(
    opt: &Opt,
    compiler: &mut Compiler,
    color: Color,
    vm: &Thread,
) -> std::result::Result<(), gluon::Error> {
    match opt.subcommand_opt {
        Some(SubOpt::Fmt(ref fmt_opt)) => {
            if !fmt_opt.input.is_empty() {
                let mut gluon_files = fmt_opt
                    .input
                    .iter()
                    .flat_map(|arg| {
                        WalkDir::new(arg).into_iter().filter_map(|entry| {
                            entry.ok().and_then(|entry| {
                                if entry.file_type().is_file()
                                    && entry.path().extension() == Some(OsStr::new("glu"))
                                {
                                    Some(entry.path().to_owned())
                                } else {
                                    None
                                }
                            })
                        })
                    })
                    .collect::<Vec<_>>();
                gluon_files.sort();
                gluon_files.dedup();

                for file in gluon_files {
                    fmt_file(&file)?;
                }
            } else {
                fmt_stdio()?;
            }
        }
        Some(SubOpt::Doc(ref doc_opt)) => {
            let input = &doc_opt.input;
            let output = &doc_opt.output;
            gluon_doc::generate_for_path(&new_vm(), input, output)
                .map_err(|err| format!("{}\n{}", err, err.backtrace()))?;
        }
        None => if opt.interactive {
            let mut runtime = Runtime::new()?;
            runtime.block_on(future::lazy(move || repl::run(color)))?;
        } else if !opt.input.is_empty() {
            run_files(compiler, &vm, &opt.input)?;
        } else {
            writeln!(io::stderr(), "{}", Opt::clap().get_matches().usage())
                .expect("Error writing help to stderr");
        },
    }
    Ok(())
}

fn main() {
    init_env_logger();

    let opt = Opt::from_args();

    let mut compiler = Compiler::new().run_io(true);
    let vm = new_vm();

    if let Err(err) = run(&opt, &mut compiler, opt.color, &vm) {
        match err {
            Error::VM(VMError::Message(_)) => eprintln!("{}\n{}", err, vm.context().stacktrace(0)),
            _ => {
                let mut stderr = termcolor::StandardStream::stderr(opt.color.into());
                if let Err(err) = err.emit(&mut stderr, compiler.code_map()) {
                    eprintln!("{}", err);
                }
            }
        }
        ::std::process::exit(1);
    }
}

#[cfg(test)]
mod tests {
    // If nothing else this suppresses the unused imports warnings when compiling in test mode
    #[test]
    fn execute_repl_help() {
        super::main();
    }
}
<|endoftext|>


<commit_before>extern crate regex;

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

extern crate stat;
pub use self::stat::*;

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

    #[test]
    fn test_access() {
        assert_eq!("drwxr-xr-x", pretty_access(S_IFDIR | 0o755));
        assert_eq!("-rw-r--r--", pretty_access(S_IFREG | 0o644));
        assert_eq!("srw-r-----", pretty_access(S_IFSOCK | 0o640));
        assert_eq!("lrw-r-xr-x", pretty_access(S_IFLNK | 0o655));
        assert_eq!("?rw-r-xr-x", pretty_access(0o655));

        assert_eq!(
            "brwSr-xr-x",
            pretty_access(S_IFBLK | S_ISUID as mode_t | 0o655)
        );
        assert_eq!(
            "brwsr-xr-x",
            pretty_access(S_IFBLK | S_ISUID as mode_t | 0o755)
        );

        assert_eq!(
            "prw---sr--",
            pretty_access(S_IFIFO | S_ISGID as mode_t | 0o614)
        );
        assert_eq!(
            "prw---Sr--",
            pretty_access(S_IFIFO | S_ISGID as mode_t | 0o604)
        );

        assert_eq!(
            "c---r-xr-t",
            pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o055)
        );
        assert_eq!(
            "c---r-xr-T",
            pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o054)
        );
    }

    #[test]
    fn test_file_type() {
        assert_eq!("block special file", pretty_filetype(S_IFBLK, 0));
        assert_eq!("character special file", pretty_filetype(S_IFCHR, 0));
        assert_eq!("regular file", pretty_filetype(S_IFREG, 1));
        assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0));
        assert_eq!("weird file", pretty_filetype(0, 0));
    }

    #[test]
    fn test_fs_type() {
        assert_eq!("ext2/ext3", pretty_fstype(0xEF53));
        assert_eq!("tmpfs", pretty_fstype(0x01021994));
        assert_eq!("nfs", pretty_fstype(0x6969));
        assert_eq!("btrfs", pretty_fstype(0x9123683e));
        assert_eq!("xfs", pretty_fstype(0x58465342));
        assert_eq!("zfs", pretty_fstype(0x2FC12FC1));
        assert_eq!("ntfs", pretty_fstype(0x5346544e));
        assert_eq!("fat", pretty_fstype(0x4006));
        assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234));
    }
}

#[test]
fn test_scanutil() {
    assert_eq!(Some((-5, 2)), "-5zxc".scan_num::<i32>());
    assert_eq!(Some((51, 2)), "51zxc".scan_num::<u32>());
    assert_eq!(Some((192, 4)), "+192zxc".scan_num::<i32>());
    assert_eq!(None, "z192zxc".scan_num::<i32>());

    assert_eq!(Some(('a', 3)), "141zxc".scan_char(8));
    assert_eq!(Some(('\n', 2)), "12qzxc".scan_char(8));
    assert_eq!(Some(('\r', 1)), "dqzxc".scan_char(16));
    assert_eq!(None, "z2qzxc".scan_char(8));
}

#[test]
fn test_groupnum() {
    assert_eq!("12,379,821,234", group_num("12379821234"));
    assert_eq!("21,234", group_num("21234"));
    assert_eq!("821,234", group_num("821234"));
    assert_eq!("1,821,234", group_num("1821234"));
    assert_eq!("1,234", group_num("1234"));
    assert_eq!("234", group_num("234"));
    assert_eq!("24", group_num("24"));
    assert_eq!("4", group_num("4"));
    assert_eq!("", group_num(""));
}

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

    #[test]
    fn normal_format() {
        let s = "%'010.2ac%-#5.w\n";
        let expected = vec![
            Token::Directive {
                flag: F_GROUP | F_ZERO,
                width: 10,
                precision: 2,
                format: 'a',
            },
            Token::Char('c'),
            Token::Directive {
                flag: F_LEFT | F_ALTER,
                width: 5,
                precision: 0,
                format: 'w',
            },
            Token::Char('\n'),
        ];
        assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap());
    }

    #[test]
    fn printf_format() {
        let s = "%-# 15a\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.-23w\\x12\\167\\132\\112\\n";
        let expected = vec![
            Token::Directive {
                flag: F_LEFT | F_ALTER | F_SPACE,
                width: 15,
                precision: -1,
                format: 'a',
            },
            Token::Char('\r'),
            Token::Char('"'),
            Token::Char('\\'),
            Token::Char('\x07'),
            Token::Char('\x08'),
            Token::Char('\x1B'),
            Token::Char('\x0C'),
            Token::Char('\x0B'),
            Token::Directive {
                flag: F_SIGN | F_ZERO,
                width: 20,
                precision: -1,
                format: 'w',
            },
            Token::Char('\x12'),
            Token::Char('w'),
            Token::Char('Z'),
            Token::Char('J'),
            Token::Char('\n'),
        ];
        assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap());
    }
}

#[test]
fn test_invalid_option() {
    new_ucmd!().arg("-w").arg("-q").arg("/").fails();
}

#[cfg(target_os = "linux")]
const NORMAL_FMTSTR: &'static str =
    "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s %u %U %x %X %y %Y %z %Z"; // avoid "%w %W" (birth/creation) due to `stat` limitations and linux kernel & rust version capability variations
#[cfg(target_os = "linux")]
const DEV_FMTSTR: &'static str =
    "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s (%t/%T) %u %U %w %W %x %X %y %Y %z %Z";
#[cfg(target_os = "linux")]
const FS_FMTSTR: &'static str = "%b %c %i %l %n %s %S %t %T"; // avoid "%a %d %f" which can cause test failure due to race conditions

#[test]
#[cfg(target_os = "linux")]
fn test_terse_fs_format() {
    let args = ["-f", "-t", "/proc"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_fs_format() {
    let args = ["-f", "-c", FS_FMTSTR, "/dev/shm"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_terse_normal_format() {
    // note: contains birth/creation date which increases test fragility
    // * results may vary due to built-in `stat` limitations as well as linux kernel and rust version capability variations
    let args = ["-t", "/"];
    let actual = new_ucmd!().args(&args).succeeds().stdout_move_str();
    let expect = expected_result(&args);
    println!("actual: {:?}", actual);
    println!("expect: {:?}", expect);
    let v_actual: Vec<&str> = actual.split(' ').collect();
    let v_expect: Vec<&str> = expect.split(' ').collect();
    assert!(!v_expect.is_empty());
    // * allow for inequality if `stat` (aka, expect) returns "0" (unknown value)
    assert!(
        expect == "0"
            || expect == "0\n"
            || v_actual
                .iter()
                .zip(v_expect.iter())
                .all(|(a, e)| a == e || *e == "0" || *e == "0\n")
    );
}

#[test]
#[cfg(target_os = "linux")]
fn test_format_created_time() {
    let args = ["-c", "%w", "/boot"];
    let actual = new_ucmd!().args(&args).succeeds().stdout_move_str();
    let expect = expected_result(&args);
    println!("actual: {:?}", actual);
    println!("expect: {:?}", expect);
    // note: using a regex instead of `split_whitespace()` in order to detect whitespace differences
    let re = regex::Regex::new(r"\s").unwrap();
    let v_actual: Vec<&str> = re.split(&actual).collect();
    let v_expect: Vec<&str> = re.split(&expect).collect();
    assert!(!v_expect.is_empty());
    // * allow for inequality if `stat` (aka, expect) returns "-" (unknown value)
    assert!(
        expect == "-"
            || expect == "-\n"
            || v_actual
                .iter()
                .zip(v_expect.iter())
                .all(|(a, e)| a == e || *e == "-" || *e == "-\n")
    );
}

#[test]
#[cfg(target_os = "linux")]
fn test_format_created_seconds() {
    let args = ["-c", "%W", "/boot"];
    let actual = new_ucmd!().args(&args).succeeds().stdout_move_str();
    let expect = expected_result(&args);
    println!("actual: {:?}", actual);
    println!("expect: {:?}", expect);
    // note: using a regex instead of `split_whitespace()` in order to detect whitespace differences
    let re = regex::Regex::new(r"\s").unwrap();
    let v_actual: Vec<&str> = re.split(&actual).collect();
    let v_expect: Vec<&str> = re.split(&expect).collect();
    assert!(!v_expect.is_empty());
    // * allow for inequality if `stat` (aka, expect) returns "0" (unknown value)
    assert!(
        expect == "0"
            || expect == "0\n"
            || v_actual
                .iter()
                .zip(v_expect.iter())
                .all(|(a, e)| a == e || *e == "0" || *e == "0\n")
    );
}

#[test]
#[cfg(target_os = "linux")]
fn test_normal_format() {
    let args = ["-c", NORMAL_FMTSTR, "/boot"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_follow_symlink() {
    let args = ["-L", "-c", DEV_FMTSTR, "/dev/cdrom"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_symlink() {
    let args = ["-c", DEV_FMTSTR, "/dev/cdrom"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_char() {
    let args = ["-c", DEV_FMTSTR, "/dev/pts/ptmx"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_multi_files() {
    let args = [
        "-c",
        NORMAL_FMTSTR,
        "/dev",
        "/usr/lib",
        "/etc/fstab",
        "/var",
    ];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_printf() {
    let args = [
        "--printf=123%-# 15q\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.23m\\x12\\167\\132\\112\\n",
        "/",
    ];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[cfg(target_os = "linux")]
fn expected_result(args: &[&str]) -> String {
    TestScenario::new(util_name!())
        .cmd_keepenv(util_name!())
        .env("LANGUAGE", "C")
        .args(args)
        .run()
        .stdout_move_str()
}
<commit_msg>stat: fix test to ignore selinux related output<commit_after>extern crate regex;

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

extern crate stat;
pub use self::stat::*;

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

    #[test]
    fn test_access() {
        assert_eq!("drwxr-xr-x", pretty_access(S_IFDIR | 0o755));
        assert_eq!("-rw-r--r--", pretty_access(S_IFREG | 0o644));
        assert_eq!("srw-r-----", pretty_access(S_IFSOCK | 0o640));
        assert_eq!("lrw-r-xr-x", pretty_access(S_IFLNK | 0o655));
        assert_eq!("?rw-r-xr-x", pretty_access(0o655));

        assert_eq!(
            "brwSr-xr-x",
            pretty_access(S_IFBLK | S_ISUID as mode_t | 0o655)
        );
        assert_eq!(
            "brwsr-xr-x",
            pretty_access(S_IFBLK | S_ISUID as mode_t | 0o755)
        );

        assert_eq!(
            "prw---sr--",
            pretty_access(S_IFIFO | S_ISGID as mode_t | 0o614)
        );
        assert_eq!(
            "prw---Sr--",
            pretty_access(S_IFIFO | S_ISGID as mode_t | 0o604)
        );

        assert_eq!(
            "c---r-xr-t",
            pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o055)
        );
        assert_eq!(
            "c---r-xr-T",
            pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o054)
        );
    }

    #[test]
    fn test_file_type() {
        assert_eq!("block special file", pretty_filetype(S_IFBLK, 0));
        assert_eq!("character special file", pretty_filetype(S_IFCHR, 0));
        assert_eq!("regular file", pretty_filetype(S_IFREG, 1));
        assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0));
        assert_eq!("weird file", pretty_filetype(0, 0));
    }

    #[test]
    fn test_fs_type() {
        assert_eq!("ext2/ext3", pretty_fstype(0xEF53));
        assert_eq!("tmpfs", pretty_fstype(0x01021994));
        assert_eq!("nfs", pretty_fstype(0x6969));
        assert_eq!("btrfs", pretty_fstype(0x9123683e));
        assert_eq!("xfs", pretty_fstype(0x58465342));
        assert_eq!("zfs", pretty_fstype(0x2FC12FC1));
        assert_eq!("ntfs", pretty_fstype(0x5346544e));
        assert_eq!("fat", pretty_fstype(0x4006));
        assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234));
    }
}

#[test]
fn test_scanutil() {
    assert_eq!(Some((-5, 2)), "-5zxc".scan_num::<i32>());
    assert_eq!(Some((51, 2)), "51zxc".scan_num::<u32>());
    assert_eq!(Some((192, 4)), "+192zxc".scan_num::<i32>());
    assert_eq!(None, "z192zxc".scan_num::<i32>());

    assert_eq!(Some(('a', 3)), "141zxc".scan_char(8));
    assert_eq!(Some(('\n', 2)), "12qzxc".scan_char(8));
    assert_eq!(Some(('\r', 1)), "dqzxc".scan_char(16));
    assert_eq!(None, "z2qzxc".scan_char(8));
}

#[test]
fn test_groupnum() {
    assert_eq!("12,379,821,234", group_num("12379821234"));
    assert_eq!("21,234", group_num("21234"));
    assert_eq!("821,234", group_num("821234"));
    assert_eq!("1,821,234", group_num("1821234"));
    assert_eq!("1,234", group_num("1234"));
    assert_eq!("234", group_num("234"));
    assert_eq!("24", group_num("24"));
    assert_eq!("4", group_num("4"));
    assert_eq!("", group_num(""));
}

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

    #[test]
    fn normal_format() {
        let s = "%'010.2ac%-#5.w\n";
        let expected = vec![
            Token::Directive {
                flag: F_GROUP | F_ZERO,
                width: 10,
                precision: 2,
                format: 'a',
            },
            Token::Char('c'),
            Token::Directive {
                flag: F_LEFT | F_ALTER,
                width: 5,
                precision: 0,
                format: 'w',
            },
            Token::Char('\n'),
        ];
        assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap());
    }

    #[test]
    fn printf_format() {
        let s = "%-# 15a\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.-23w\\x12\\167\\132\\112\\n";
        let expected = vec![
            Token::Directive {
                flag: F_LEFT | F_ALTER | F_SPACE,
                width: 15,
                precision: -1,
                format: 'a',
            },
            Token::Char('\r'),
            Token::Char('"'),
            Token::Char('\\'),
            Token::Char('\x07'),
            Token::Char('\x08'),
            Token::Char('\x1B'),
            Token::Char('\x0C'),
            Token::Char('\x0B'),
            Token::Directive {
                flag: F_SIGN | F_ZERO,
                width: 20,
                precision: -1,
                format: 'w',
            },
            Token::Char('\x12'),
            Token::Char('w'),
            Token::Char('Z'),
            Token::Char('J'),
            Token::Char('\n'),
        ];
        assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap());
    }
}

#[test]
fn test_invalid_option() {
    new_ucmd!().arg("-w").arg("-q").arg("/").fails();
}

#[cfg(target_os = "linux")]
const NORMAL_FMTSTR: &'static str =
    "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s %u %U %x %X %y %Y %z %Z"; // avoid "%w %W" (birth/creation) due to `stat` limitations and linux kernel & rust version capability variations
#[cfg(target_os = "linux")]
const DEV_FMTSTR: &'static str =
    "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s (%t/%T) %u %U %w %W %x %X %y %Y %z %Z";
#[cfg(target_os = "linux")]
const FS_FMTSTR: &'static str = "%b %c %i %l %n %s %S %t %T"; // avoid "%a %d %f" which can cause test failure due to race conditions

#[test]
#[cfg(target_os = "linux")]
fn test_terse_fs_format() {
    let args = ["-f", "-t", "/proc"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_fs_format() {
    let args = ["-f", "-c", FS_FMTSTR, "/dev/shm"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_terse_normal_format() {
    // note: contains birth/creation date which increases test fragility
    // * results may vary due to built-in `stat` limitations as well as linux kernel and rust version capability variations
    let args = ["-t", "/"];
    let actual = new_ucmd!().args(&args).succeeds().stdout_move_str();
    let expect = expected_result(&args);
    println!("actual: {:?}", actual);
    println!("expect: {:?}", expect);
    let v_actual: Vec<&str> = actual.trim().split(' ').collect();
    let mut v_expect: Vec<&str> = expect.trim().split(' ').collect();
    assert!(!v_expect.is_empty());

    // uu_stat does not support selinux
    if v_actual.len() == v_expect.len() - 1 && v_expect[v_expect.len() - 1].contains(":") {
        // assume last element contains: `SELinux security context string`
        v_expect.pop();
    }

    // * allow for inequality if `stat` (aka, expect) returns "0" (unknown value)
    assert!(
        expect == "0"
            || expect == "0\n"
            || v_actual
                .iter()
                .zip(v_expect.iter())
                .all(|(a, e)| a == e || *e == "0" || *e == "0\n")
    );
}

#[test]
#[cfg(target_os = "linux")]
fn test_format_created_time() {
    let args = ["-c", "%w", "/boot"];
    let actual = new_ucmd!().args(&args).succeeds().stdout_move_str();
    let expect = expected_result(&args);
    println!("actual: {:?}", actual);
    println!("expect: {:?}", expect);
    // note: using a regex instead of `split_whitespace()` in order to detect whitespace differences
    let re = regex::Regex::new(r"\s").unwrap();
    let v_actual: Vec<&str> = re.split(&actual).collect();
    let v_expect: Vec<&str> = re.split(&expect).collect();
    assert!(!v_expect.is_empty());
    // * allow for inequality if `stat` (aka, expect) returns "-" (unknown value)
    assert!(
        expect == "-"
            || expect == "-\n"
            || v_actual
                .iter()
                .zip(v_expect.iter())
                .all(|(a, e)| a == e || *e == "-" || *e == "-\n")
    );
}

#[test]
#[cfg(target_os = "linux")]
fn test_format_created_seconds() {
    let args = ["-c", "%W", "/boot"];
    let actual = new_ucmd!().args(&args).succeeds().stdout_move_str();
    let expect = expected_result(&args);
    println!("actual: {:?}", actual);
    println!("expect: {:?}", expect);
    // note: using a regex instead of `split_whitespace()` in order to detect whitespace differences
    let re = regex::Regex::new(r"\s").unwrap();
    let v_actual: Vec<&str> = re.split(&actual).collect();
    let v_expect: Vec<&str> = re.split(&expect).collect();
    assert!(!v_expect.is_empty());
    // * allow for inequality if `stat` (aka, expect) returns "0" (unknown value)
    assert!(
        expect == "0"
            || expect == "0\n"
            || v_actual
                .iter()
                .zip(v_expect.iter())
                .all(|(a, e)| a == e || *e == "0" || *e == "0\n")
    );
}

#[test]
#[cfg(target_os = "linux")]
fn test_normal_format() {
    let args = ["-c", NORMAL_FMTSTR, "/boot"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_follow_symlink() {
    let args = ["-L", "-c", DEV_FMTSTR, "/dev/cdrom"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_symlink() {
    let args = ["-c", DEV_FMTSTR, "/dev/cdrom"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_char() {
    let args = ["-c", DEV_FMTSTR, "/dev/pts/ptmx"];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_multi_files() {
    let args = [
        "-c",
        NORMAL_FMTSTR,
        "/dev",
        "/usr/lib",
        "/etc/fstab",
        "/var",
    ];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[test]
#[cfg(target_os = "linux")]
fn test_printf() {
    let args = [
        "--printf=123%-# 15q\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.23m\\x12\\167\\132\\112\\n",
        "/",
    ];
    new_ucmd!()
        .args(&args)
        .run()
        .stdout_is(expected_result(&args));
}

#[cfg(target_os = "linux")]
fn expected_result(args: &[&str]) -> String {
    TestScenario::new(util_name!())
        .cmd_keepenv(util_name!())
        .env("LANGUAGE", "C")
        .args(args)
        .run()
        .stdout_move_str()
}
<|endoftext|>