<commit_before>//! Architecture support for x86_64

#![feature(asm)]
#![feature(concat_idents)]
#![feature(const_fn)]
#![feature(core_intrinsics)]
#![feature(drop_types_in_const)]
#![feature(lang_items)]
#![feature(naked_functions)]
#![feature(thread_local)]
#![feature(unique)]
#![no_std]

extern crate hole_list_allocator as allocator;

#[macro_use]
extern crate bitflags;
extern crate io;
extern crate spin;
extern crate syscall;
pub extern crate x86;

// Because the memory map is so important to not be aliased, it is defined here, in one place
// The lower 256 PML4 entries are reserved for userspace
// Each PML4 entry references up to 512 GB of memory
// The top (511) PML4 is reserved for recursive mapping
// The second from the top (510) PML4 is reserved for the kernel
    /// The size of a single PML4
    pub const PML4_SIZE: usize = 0x0000_0080_0000_0000;

    /// Offset of recursive paging
    pub const RECURSIVE_PAGE_OFFSET: usize = (-(PML4_SIZE as isize)) as usize;

    /// Offset of kernel
    pub const KERNEL_OFFSET: usize = RECURSIVE_PAGE_OFFSET - PML4_SIZE;

    /// Offset to kernel heap
    pub const KERNEL_HEAP_OFFSET: usize = KERNEL_OFFSET + PML4_SIZE/2;
    /// Size of kernel heap
    pub const KERNEL_HEAP_SIZE: usize = 64 * 1024 * 1024; // 64 MB

    /// Offset to kernel percpu variables
    //TODO: Use 64-bit fs offset to enable this pub const KERNEL_PERCPU_OFFSET: usize = KERNEL_HEAP_OFFSET - PML4_SIZE;
    pub const KERNEL_PERCPU_OFFSET: usize = 0xC000_0000;
    /// Size of kernel percpu variables
    pub const KERNEL_PERCPU_SIZE: usize = 64 * 1024; // 64 KB

    /// Offset to user image
    pub const USER_OFFSET: usize = 0;

    /// Offset to user TCB
    pub const USER_TCB_OFFSET: usize = 0xB000_0000;

    /// Offset to user arguments
    pub const USER_ARG_OFFSET: usize = USER_OFFSET + PML4_SIZE/2;

    /// Offset to user heap
    pub const USER_HEAP_OFFSET: usize = USER_OFFSET + PML4_SIZE;

    /// Offset to user grants
    pub const USER_GRANT_OFFSET: usize = USER_HEAP_OFFSET + PML4_SIZE;

    /// Offset to user stack
    pub const USER_STACK_OFFSET: usize = USER_GRANT_OFFSET + PML4_SIZE;
    /// Size of user stack
    pub const USER_STACK_SIZE: usize = 1024 * 1024; // 1 MB

    /// Offset to user TLS
    pub const USER_TLS_OFFSET: usize = USER_STACK_OFFSET + PML4_SIZE;

    /// Offset to user temporary image (used when cloning)
    pub const USER_TMP_OFFSET: usize = USER_TLS_OFFSET + PML4_SIZE;

    /// Offset to user temporary heap (used when cloning)
    pub const USER_TMP_HEAP_OFFSET: usize = USER_TMP_OFFSET + PML4_SIZE;

    /// Offset to user temporary page for grants
    pub const USER_TMP_GRANT_OFFSET: usize = USER_TMP_HEAP_OFFSET + PML4_SIZE;

    /// Offset to user temporary stack (used when cloning)
    pub const USER_TMP_STACK_OFFSET: usize = USER_TMP_GRANT_OFFSET + PML4_SIZE;

    /// Offset to user temporary tls (used when cloning)
    pub const USER_TMP_TLS_OFFSET: usize = USER_TMP_STACK_OFFSET + PML4_SIZE;

/// Print to console
#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => ({
        use core::fmt::Write;
        let _ = write!($crate::console::CONSOLE.lock(), $($arg)*);
    });
}

/// Print with new line to console
#[macro_export]
macro_rules! println {
    ($fmt:expr) => (print!(concat!($fmt, "\n")));
    ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
}

/// Create an interrupt function that can safely run rust code
#[macro_export]
macro_rules! interrupt {
    ($name:ident, $func:block) => {
        #[naked]
        pub unsafe extern fn $name () {
            #[inline(never)]
            unsafe fn inner() {
                $func
            }

            // Push scratch registers
            asm!("push rax
                push rcx
                push rdx
                push rdi
                push rsi
                push r8
                push r9
                push r10
                push r11
                push fs
                mov rax, 0x18
                mov fs, ax"
                : : : : "intel", "volatile");

            // Call inner rust function
            inner();

            // Pop scratch registers and return
            asm!("pop fs
                pop r11
                pop r10
                pop r9
                pop r8
                pop rsi
                pop rdi
                pop rdx
                pop rcx
                pop rax
                iretq"
                : : : : "intel", "volatile");
        }
    };
}

#[repr(packed)]
pub struct InterruptStack {
    fs: usize,
    r11: usize,
    r10: usize,
    r9: usize,
    r8: usize,
    rsi: usize,
    rdi: usize,
    rdx: usize,
    rcx: usize,
    rax: usize,
    rip: usize,
    cs: usize,
    rflags: usize,
}

#[macro_export]
macro_rules! interrupt_stack {
    ($name:ident, $stack: ident, $func:block) => {
        #[naked]
        pub unsafe extern fn $name () {
            #[inline(never)]
            unsafe fn inner($stack: &$crate::InterruptStack) {
                $func
            }

            // Push scratch registers
            asm!("push rax
                push rcx
                push rdx
                push rdi
                push rsi
                push r8
                push r9
                push r10
                push r11
                push fs
                mov rax, 0x18
                mov fs, ax"
                : : : : "intel", "volatile");

            // Get reference to stack variables
            let rsp: usize;
            asm!("" : "={rsp}"(rsp) : : : "intel", "volatile");

            // Call inner rust function
            inner(&*(rsp as *const $crate::InterruptStack));

            // Pop scratch registers and return
            asm!("pop fs
                pop r11
                pop r10
                pop r9
                pop r8
                pop rsi
                pop rdi
                pop rdx
                pop rcx
                pop rax
                iretq"
                : : : : "intel", "volatile");
        }
    };
}

#[repr(packed)]
pub struct InterruptErrorStack {
    fs: usize,
    r11: usize,
    r10: usize,
    r9: usize,
    r8: usize,
    rsi: usize,
    rdi: usize,
    rdx: usize,
    rcx: usize,
    rax: usize,
    code: usize,
    rip: usize,
    cs: usize,
    rflags: usize,
}

#[macro_export]
macro_rules! interrupt_error {
    ($name:ident, $stack:ident, $func:block) => {
        #[naked]
        pub unsafe extern fn $name () {
            #[inline(never)]
            unsafe fn inner($stack: &$crate::InterruptErrorStack) {
                $func
            }

            // Push scratch registers
            asm!("xchg bx, bx
                push rax
                push rcx
                push rdx
                push rdi
                push rsi
                push r8
                push r9
                push r10
                push r11
                push fs
                mov rax, 0x18
                mov fs, ax"
                : : : : "intel", "volatile");

            // Get reference to stack variables
            let rsp: usize;
            asm!("" : "={rsp}"(rsp) : : : "intel", "volatile");

            // Call inner rust function
            inner(&*(rsp as *const $crate::InterruptErrorStack));

            // Pop scratch registers, error code, and return
            asm!("pop fs
                pop r11
                pop r10
                pop r9
                pop r8
                pop rsi
                pop rdi
                pop rdx
                pop rcx
                pop rax
                add rsp, 8
                iretq"
                : : : : "intel", "volatile");
        }
    };
}

/// ACPI table parsing
pub mod acpi;

/// Console handling
pub mod console;

/// Context switching
pub mod context;

/// Devices
pub mod device;

/// Memcpy, memmove, etc.
pub mod externs;

/// Global descriptor table
pub mod gdt;

/// Interrupt descriptor table
pub mod idt;

/// Interrupt instructions
pub mod interrupt;

/// Memory management
pub mod memory;

/// Paging
pub mod paging;

/// Panic
pub mod panic;

/// Initialization and start function
pub mod start;

/// Time
pub mod time;
<commit_msg>Increase heap size<commit_after>//! Architecture support for x86_64

#![feature(asm)]
#![feature(concat_idents)]
#![feature(const_fn)]
#![feature(core_intrinsics)]
#![feature(drop_types_in_const)]
#![feature(lang_items)]
#![feature(naked_functions)]
#![feature(thread_local)]
#![feature(unique)]
#![no_std]

extern crate hole_list_allocator as allocator;

#[macro_use]
extern crate bitflags;
extern crate io;
extern crate spin;
extern crate syscall;
pub extern crate x86;

// Because the memory map is so important to not be aliased, it is defined here, in one place
// The lower 256 PML4 entries are reserved for userspace
// Each PML4 entry references up to 512 GB of memory
// The top (511) PML4 is reserved for recursive mapping
// The second from the top (510) PML4 is reserved for the kernel
    /// The size of a single PML4
    pub const PML4_SIZE: usize = 0x0000_0080_0000_0000;

    /// Offset of recursive paging
    pub const RECURSIVE_PAGE_OFFSET: usize = (-(PML4_SIZE as isize)) as usize;

    /// Offset of kernel
    pub const KERNEL_OFFSET: usize = RECURSIVE_PAGE_OFFSET - PML4_SIZE;

    /// Offset to kernel heap
    pub const KERNEL_HEAP_OFFSET: usize = KERNEL_OFFSET + PML4_SIZE/2;
    /// Size of kernel heap
    pub const KERNEL_HEAP_SIZE: usize = 256 * 1024 * 1024; // 256 MB

    /// Offset to kernel percpu variables
    //TODO: Use 64-bit fs offset to enable this pub const KERNEL_PERCPU_OFFSET: usize = KERNEL_HEAP_OFFSET - PML4_SIZE;
    pub const KERNEL_PERCPU_OFFSET: usize = 0xC000_0000;
    /// Size of kernel percpu variables
    pub const KERNEL_PERCPU_SIZE: usize = 64 * 1024; // 64 KB

    /// Offset to user image
    pub const USER_OFFSET: usize = 0;

    /// Offset to user TCB
    pub const USER_TCB_OFFSET: usize = 0xB000_0000;

    /// Offset to user arguments
    pub const USER_ARG_OFFSET: usize = USER_OFFSET + PML4_SIZE/2;

    /// Offset to user heap
    pub const USER_HEAP_OFFSET: usize = USER_OFFSET + PML4_SIZE;

    /// Offset to user grants
    pub const USER_GRANT_OFFSET: usize = USER_HEAP_OFFSET + PML4_SIZE;

    /// Offset to user stack
    pub const USER_STACK_OFFSET: usize = USER_GRANT_OFFSET + PML4_SIZE;
    /// Size of user stack
    pub const USER_STACK_SIZE: usize = 1024 * 1024; // 1 MB

    /// Offset to user TLS
    pub const USER_TLS_OFFSET: usize = USER_STACK_OFFSET + PML4_SIZE;

    /// Offset to user temporary image (used when cloning)
    pub const USER_TMP_OFFSET: usize = USER_TLS_OFFSET + PML4_SIZE;

    /// Offset to user temporary heap (used when cloning)
    pub const USER_TMP_HEAP_OFFSET: usize = USER_TMP_OFFSET + PML4_SIZE;

    /// Offset to user temporary page for grants
    pub const USER_TMP_GRANT_OFFSET: usize = USER_TMP_HEAP_OFFSET + PML4_SIZE;

    /// Offset to user temporary stack (used when cloning)
    pub const USER_TMP_STACK_OFFSET: usize = USER_TMP_GRANT_OFFSET + PML4_SIZE;

    /// Offset to user temporary tls (used when cloning)
    pub const USER_TMP_TLS_OFFSET: usize = USER_TMP_STACK_OFFSET + PML4_SIZE;

/// Print to console
#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => ({
        use core::fmt::Write;
        let _ = write!($crate::console::CONSOLE.lock(), $($arg)*);
    });
}

/// Print with new line to console
#[macro_export]
macro_rules! println {
    ($fmt:expr) => (print!(concat!($fmt, "\n")));
    ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
}

/// Create an interrupt function that can safely run rust code
#[macro_export]
macro_rules! interrupt {
    ($name:ident, $func:block) => {
        #[naked]
        pub unsafe extern fn $name () {
            #[inline(never)]
            unsafe fn inner() {
                $func
            }

            // Push scratch registers
            asm!("push rax
                push rcx
                push rdx
                push rdi
                push rsi
                push r8
                push r9
                push r10
                push r11
                push fs
                mov rax, 0x18
                mov fs, ax"
                : : : : "intel", "volatile");

            // Call inner rust function
            inner();

            // Pop scratch registers and return
            asm!("pop fs
                pop r11
                pop r10
                pop r9
                pop r8
                pop rsi
                pop rdi
                pop rdx
                pop rcx
                pop rax
                iretq"
                : : : : "intel", "volatile");
        }
    };
}

#[repr(packed)]
pub struct InterruptStack {
    fs: usize,
    r11: usize,
    r10: usize,
    r9: usize,
    r8: usize,
    rsi: usize,
    rdi: usize,
    rdx: usize,
    rcx: usize,
    rax: usize,
    rip: usize,
    cs: usize,
    rflags: usize,
}

#[macro_export]
macro_rules! interrupt_stack {
    ($name:ident, $stack: ident, $func:block) => {
        #[naked]
        pub unsafe extern fn $name () {
            #[inline(never)]
            unsafe fn inner($stack: &$crate::InterruptStack) {
                $func
            }

            // Push scratch registers
            asm!("push rax
                push rcx
                push rdx
                push rdi
                push rsi
                push r8
                push r9
                push r10
                push r11
                push fs
                mov rax, 0x18
                mov fs, ax"
                : : : : "intel", "volatile");

            // Get reference to stack variables
            let rsp: usize;
            asm!("" : "={rsp}"(rsp) : : : "intel", "volatile");

            // Call inner rust function
            inner(&*(rsp as *const $crate::InterruptStack));

            // Pop scratch registers and return
            asm!("pop fs
                pop r11
                pop r10
                pop r9
                pop r8
                pop rsi
                pop rdi
                pop rdx
                pop rcx
                pop rax
                iretq"
                : : : : "intel", "volatile");
        }
    };
}

#[repr(packed)]
pub struct InterruptErrorStack {
    fs: usize,
    r11: usize,
    r10: usize,
    r9: usize,
    r8: usize,
    rsi: usize,
    rdi: usize,
    rdx: usize,
    rcx: usize,
    rax: usize,
    code: usize,
    rip: usize,
    cs: usize,
    rflags: usize,
}

#[macro_export]
macro_rules! interrupt_error {
    ($name:ident, $stack:ident, $func:block) => {
        #[naked]
        pub unsafe extern fn $name () {
            #[inline(never)]
            unsafe fn inner($stack: &$crate::InterruptErrorStack) {
                $func
            }

            // Push scratch registers
            asm!("xchg bx, bx
                push rax
                push rcx
                push rdx
                push rdi
                push rsi
                push r8
                push r9
                push r10
                push r11
                push fs
                mov rax, 0x18
                mov fs, ax"
                : : : : "intel", "volatile");

            // Get reference to stack variables
            let rsp: usize;
            asm!("" : "={rsp}"(rsp) : : : "intel", "volatile");

            // Call inner rust function
            inner(&*(rsp as *const $crate::InterruptErrorStack));

            // Pop scratch registers, error code, and return
            asm!("pop fs
                pop r11
                pop r10
                pop r9
                pop r8
                pop rsi
                pop rdi
                pop rdx
                pop rcx
                pop rax
                add rsp, 8
                iretq"
                : : : : "intel", "volatile");
        }
    };
}

/// ACPI table parsing
pub mod acpi;

/// Console handling
pub mod console;

/// Context switching
pub mod context;

/// Devices
pub mod device;

/// Memcpy, memmove, etc.
pub mod externs;

/// Global descriptor table
pub mod gdt;

/// Interrupt descriptor table
pub mod idt;

/// Interrupt instructions
pub mod interrupt;

/// Memory management
pub mod memory;

/// Paging
pub mod paging;

/// Panic
pub mod panic;

/// Initialization and start function
pub mod start;

/// Time
pub mod time;
<|endoftext|>


<commit_before>//! common functionalities

extern crate crypto;
extern crate rand;

use std::env::set_current_dir;
use std::fs;
use std::fs::File;
use std::io;
use std::io::{Read, Write};
use std::iter::repeat;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::Command;

use self::crypto::salsa20::Salsa20;
use self::crypto::symmetriccipher::SynchronousStreamCipher;
use self::rand::{OsRng, Rng};

use base64;

/// default salt length
const SALT_LENGTH: usize = 24;
/// default key length
const KEY_LENGTH: usize = 32;

/// generate an xsalsa20 based on key, meat and salt
pub fn generate_password(key: &[u8], meat: &[u8], salt: &[u8]) -> Vec<u8> {
    let mut cipher = Salsa20::new_xsalsa20(key, salt);
    let mut buf: Vec<u8> = repeat(0).take(meat.len()).collect();
    cipher.process(meat, &mut buf);
    buf
}

/// generate a new random salt
pub fn generate_salt() -> Vec<u8> {
    let mut rng = OsRng::new().expect("OsRng init failed");
    rng.gen_iter::<u8>().take(SALT_LENGTH).collect()
}

/// generate a new random meat, based on size
pub fn generate_meat(i: usize) -> Vec<u8> {
    let mut rng = OsRng::new().expect("OsRng init failed");
    rng.gen_iter::<u8>().take(i).collect()
}

/// load the private key, or generate a new one
pub fn load_or_create_key(filename: &str) -> Vec<u8> {
    if let Ok(s) = read_data(filename) {
        let s = s.lines().next().expect("Reading base64 key failed");
        base64::decode(s).expect("Key base64 decoding failed")
    } else {
        println!("Creating a new key in {}", filename);
        let mut rng = OsRng::new().expect("OsRng init failed");
        let new_key: Vec<u8> = rng.gen_iter::<u8>().take(KEY_LENGTH).collect();
        let key_base64 = base64::encode(&new_key); // .to_base64(base64::STANDARD);
        write_data(&key_base64, filename);
        set_file_perms(filename, 0o400);
        new_key
    }
}

/// ensure that the data dir exists
pub fn ensure_data_dir(data_dir: &str) {
    fs::create_dir_all(data_dir.to_string())
        .expect(&format!("Creating data directory {} failed", data_dir));
    set_file_perms(data_dir, 0o700);

    if !Path::new(data_dir).join(".git").exists() {
        set_current_dir(data_dir).expect("Failed to set dir to data dir");

        Command::new("git")
            .arg("init")
            .status()
            .expect("Failed to init data git repo");

        {
            let git_args = ["config", "user.email", "chaos"];
            Command::new("git")
                .args(&git_args)
                .status()
                .expect("Failed to set user.email");
        }

        {
            let git_args = ["config", "user.name", "chaos"];
            Command::new("git")
                .args(&git_args)
                .status()
                .expect("Failed to set user.name");
        }
    }
}

/// read all the data into String
pub fn read_data(path: &str) -> Result<String, io::Error> {
    let mut f = try!(File::open(path));
    let mut s = String::new();
    try!(f.read_to_string(&mut s));
    Ok(s)
}

/// commit data to VCS
pub fn commit_data(data_dir: &str, data_file: &str, commit_text: &str) {
    set_current_dir(data_dir).expect("Failed to set dir to data dir");

    let git_args = ["add", data_file].to_vec();

    Command::new("git")
        .args(&git_args)
        .status()
        .unwrap_or_else(|e| {
            panic!("Failed to add data file: {}", e);
        });

    let git_args = ["commit", "-m", commit_text].to_vec();
    Command::new("git")
        .args(&git_args)
        .status()
        .unwrap_or_else(|e| {
            panic!("Failed to git commit: {}", e);
        });
}

/// write data to file
pub fn write_data(data: &str, filename: &str) {
    let mut f = File::create(filename).expect("could not create data file");
    f.write_all(data.as_bytes())
        .expect("Data file write failed");
    f.write_all(b"\n").expect("Newline write failed!?");
    f.sync_all().expect("Sync failed");
}

/// set file permissions
pub fn set_file_perms(filename: &str, mode: u32) {
    let mut perms = fs::metadata(filename)
        .expect("Gettings perms failed")
        .permissions();
    perms.set_mode(mode);
    fs::set_permissions(filename, perms).expect("Setting permission failed");
}

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

    #[test]
    fn test_generate_salt() {
        let salt1 = generate_salt();
        let salt2 = generate_salt();
        assert!(salt1 != salt2);
    }

    #[test]
    fn test_generate_meat() {
        let meat1 = generate_meat(8);
        let meat2 = generate_meat(8);
        assert!(meat1 != meat2);
        assert_eq!(8, meat1.len());
        assert_eq!(8, meat2.len());
    }

    #[test]
    fn test_generate_password() {
        let pass1 = generate_password(
            &[
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
                24, 25, 26, 27, 28, 29, 30, 31, 32,
            ],
            &vec![1, 2],
            &vec![
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
                24,
            ],
        );
        assert_eq!(vec![230, 6], pass1);
    }
}
<commit_msg>Fix rng deprecations<commit_after>//! common functionalities

extern crate crypto;
extern crate rand;

use std::env::set_current_dir;
use std::fs;
use std::fs::File;
use std::io;
use std::io::{Read, Write};
use std::iter::repeat;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::Command;

use self::crypto::salsa20::Salsa20;
use self::crypto::symmetriccipher::SynchronousStreamCipher;
use self::rand::distributions::Standard;
use self::rand::{thread_rng, Rng};

use base64;

/// default salt length
const SALT_LENGTH: usize = 24;
/// default key length
const KEY_LENGTH: usize = 32;

/// generate an xsalsa20 based on key, meat and salt
pub fn generate_password(key: &[u8], meat: &[u8], salt: &[u8]) -> Vec<u8> {
    let mut cipher = Salsa20::new_xsalsa20(key, salt);
    let mut buf: Vec<u8> = repeat(0).take(meat.len()).collect();
    cipher.process(meat, &mut buf);
    buf
}

/// generate a new random salt
pub fn generate_salt() -> Vec<u8> {
    thread_rng()
        .sample_iter(&Standard)
        .take(SALT_LENGTH)
        .collect()
}

/// generate a new random meat, based on size
pub fn generate_meat(i: usize) -> Vec<u8> {
    thread_rng().sample_iter(&Standard).take(i).collect()
}

/// load the private key, or generate a new one
pub fn load_or_create_key(filename: &str) -> Vec<u8> {
    if let Ok(s) = read_data(filename) {
        let s = s.lines().next().expect("Reading base64 key failed");
        base64::decode(s).expect("Key base64 decoding failed")
    } else {
        println!("Creating a new key in {}", filename);
        let new_key: Vec<u8> = thread_rng()
            .sample_iter(&Standard)
            .take(KEY_LENGTH)
            .collect();
        let key_base64 = base64::encode(&new_key);
        write_data(&key_base64, filename);
        set_file_perms(filename, 0o400);
        new_key
    }
}

/// ensure that the data dir exists
pub fn ensure_data_dir(data_dir: &str) {
    fs::create_dir_all(data_dir.to_string())
        .expect(&format!("Creating data directory {} failed", data_dir));
    set_file_perms(data_dir, 0o700);

    if !Path::new(data_dir).join(".git").exists() {
        set_current_dir(data_dir).expect("Failed to set dir to data dir");

        Command::new("git")
            .arg("init")
            .status()
            .expect("Failed to init data git repo");

        {
            let git_args = ["config", "user.email", "chaos"];
            Command::new("git")
                .args(&git_args)
                .status()
                .expect("Failed to set user.email");
        }

        {
            let git_args = ["config", "user.name", "chaos"];
            Command::new("git")
                .args(&git_args)
                .status()
                .expect("Failed to set user.name");
        }
    }
}

/// read all the data into String
pub fn read_data(path: &str) -> Result<String, io::Error> {
    let mut f = try!(File::open(path));
    let mut s = String::new();
    try!(f.read_to_string(&mut s));
    Ok(s)
}

/// commit data to VCS
pub fn commit_data(data_dir: &str, data_file: &str, commit_text: &str) {
    set_current_dir(data_dir).expect("Failed to set dir to data dir");

    let git_args = ["add", data_file].to_vec();

    Command::new("git")
        .args(&git_args)
        .status()
        .unwrap_or_else(|e| {
            panic!("Failed to add data file: {}", e);
        });

    let git_args = ["commit", "-m", commit_text].to_vec();
    Command::new("git")
        .args(&git_args)
        .status()
        .unwrap_or_else(|e| {
            panic!("Failed to git commit: {}", e);
        });
}

/// write data to file
pub fn write_data(data: &str, filename: &str) {
    let mut f = File::create(filename).expect("could not create data file");
    f.write_all(data.as_bytes())
        .expect("Data file write failed");
    f.write_all(b"\n").expect("Newline write failed!?");
    f.sync_all().expect("Sync failed");
}

/// set file permissions
pub fn set_file_perms(filename: &str, mode: u32) {
    let mut perms = fs::metadata(filename)
        .expect("Gettings perms failed")
        .permissions();
    perms.set_mode(mode);
    fs::set_permissions(filename, perms).expect("Setting permission failed");
}

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

    #[test]
    fn test_generate_salt() {
        let salt1 = generate_salt();
        let salt2 = generate_salt();
        assert!(salt1 != salt2);
    }

    #[test]
    fn test_generate_meat() {
        let meat1 = generate_meat(8);
        let meat2 = generate_meat(8);
        assert!(meat1 != meat2);
        assert_eq!(8, meat1.len());
        assert_eq!(8, meat2.len());
    }

    #[test]
    fn test_generate_password() {
        let pass1 = generate_password(
            &[
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
                24, 25, 26, 27, 28, 29, 30, 31, 32,
            ],
            &vec![1, 2],
            &vec![
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
                24,
            ],
        );
        assert_eq!(vec![230, 6], pass1);
    }
}
<|endoftext|>


<commit_before>use time::get_time;
use super::Actor;

pub struct Logger;

impl Logger {
  pub fn new() -> Logger {
    Logger { }
  }
}

impl Actor for Logger {
  fn process_message(&mut self, msg: String) -> Result<(),_> {
    let ts = get_time();
    println!("{}.{} {}", ts.sec, ts.nsec/1000000, msg);
  }
}
<commit_msg>fix logger refactoring<commit_after>use time::get_time;
use super::Actor;
use ::error::PoloError;

#[derive(Clone)]
pub struct Logger;

impl Logger {
  pub fn new() -> Logger {
    Logger { }
  }
}

impl Actor for Logger {
  fn process_message(&mut self, msg: String) -> Result<(),PoloError> {
    let ts = get_time();
    println!("{}.{} {}", ts.sec, ts.nsec/1000000, msg);
    Ok(())
  }
}
<|endoftext|>


<commit_before>extern crate slack;

use std::collections::BTreeMap;
use std::env;
use std::sync::atomic::{AtomicIsize, Ordering};
use std::sync::mpsc::Sender;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::channel;
use std::thread;

use rustc_serialize::json::{self, Json, ToJson};
use rustc_serialize::json::DecoderError::MissingFieldError;
use rustc_serialize::Decodable;
use rustc_serialize::Decoder;

use slack::Message;

use adapter::ChatAdapter;
use message::AdapterMsg;
use message::IncomingMessage;
use message::OutgoingMessage;

/// SlackAdapter sends and receives messages from the Slack chat service. Until actualy
/// configuration is added, the slack token should be placed in the environment variable
/// `SLACK_BOT_TOKEN`
pub struct SlackAdapter {
    token: String
}

impl SlackAdapter {
    pub fn new() -> SlackAdapter {
        SlackAdapter {
            token: match env::var("SLACK_BOT_TOKEN") {
                Ok(t) => t,
                Err(_) => panic!("Failed to get SLACK_BOT_TOKEN from env")
            }
        }
    }
}

struct MyHandler {
  count: i64,
  tx_bot: Sender<IncomingMessage>,
  tx_adapter: Sender<AdapterMsg>
}

/// Data for a SlackMsg::Message
#[allow(dead_code)]
struct MessageData {
    text: String,
    channel: String,
    user: String,
    ts: String,
    team: String
}

macro_rules! str_accessor {
    ($s:ident) => {
        pub fn $s(&self) -> &str {
            self.$s.as_ref()
        }
    }
}

#[allow(dead_code)]
impl MessageData {
    str_accessor!(text);
    str_accessor!(channel);
    str_accessor!(user);
    str_accessor!(ts);
    str_accessor!(team);
}

/// Incoming slack messages on the websocket api
enum SlackMsg {
    /// A message was sent to a channel
    Message(MessageData),

    /// Some other type of message arrived.
    /// [The list](https://api.slack.com/rtm) is quite extensive, and only the
    /// messages the adapter is concerned with are enumerated here.
    Other
}

impl Decodable for SlackMsg {
    fn decode<D: Decoder>(d: &mut D) -> Result<SlackMsg, D::Error> {
        d.read_struct("root", 0, |root| {
            let msg_type = try!(root.read_struct_field("type", 0, |f| f.read_str() ));

            match msg_type.as_ref() {
                "message" => {
                    Ok(SlackMsg::Message(MessageData {
                        text: try!(root.read_struct_field("text", 0, |f| f.read_str())),
                        channel: try!(root.read_struct_field("channel", 0, |f| f.read_str())),
                        user: try!(root.read_struct_field("user", 0, |f| f.read_str())),
                        ts: try!(root.read_struct_field("ts", 0, |f| f.read_str())),
                        team: try!(root.read_struct_field("team", 0, |f| f.read_str())),
                    }))
                },
                _ => {
                    Ok(SlackMsg::Other)
                }
            }
        })
    }
}

/// Convert a JSON string to a SlackMsg
///
/// This methods provides additional error handling around json::decode for certain errors
/// that cannot be handled in the Decodable implementation. Specifically, MissingFieldError
/// where the field is "type" are actually valid messages despite missing the "type" field.
fn string_to_slack_msg(raw: &str) -> Result<SlackMsg, json::DecoderError> {
    // Some messages arriving from the slack client don't have a type. So far I've only
    // witnessed confirmation messages arriving in this fashion. Since they go through the same
    // pipeline as content messages, the decoder should be able to handle them.
    match json::decode::<SlackMsg>(raw) {
        Ok(value) => Ok(value),
        Err(e) => {
            match e {
                MissingFieldError(field) => {
                    match field.as_ref() {
                        "type" => Ok(SlackMsg::Other),
                        _ => Err(MissingFieldError(field))
                    }
                },
                _ => Err(e)
            }
        }
    }
}

#[derive(Debug)]
struct OutgoingSlackMsg {
    id: i64,
    channel: String,
    msg_type: String,
    text: String
}

impl OutgoingSlackMsg {
    fn new(id: i64, m: OutgoingMessage) -> OutgoingSlackMsg {
        OutgoingSlackMsg {
            id: id,
            channel: m.get_incoming().channel().expect("missing channel").to_owned(),
            msg_type: "message".to_owned(),
            text: m.as_ref().to_owned() // TODO move instead of copy
        }
    }
}

impl ToJson for OutgoingSlackMsg {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        d.insert("id".to_string(), self.id.to_json());
        d.insert("channel".to_string(), self.channel.to_json());
        d.insert("type".to_string(), self.msg_type.to_json());
        d.insert("text".to_string(), self.text.to_json());
        Json::Object(d)
    }
}

#[allow(unused_variables)]
impl slack::MessageHandler for MyHandler {
    fn on_receive(&mut self, cli: &mut slack::RtmClient, raw: &str) {
        println!("Received[{}]: {}", self.count, raw.to_string());
        self.count = self.count + 1;

        match string_to_slack_msg(raw) {
            Ok(slack_msg) => {
                match slack_msg {
                    SlackMsg::Message(msg) => {
                        let incoming = IncomingMessage::new("SlackAdapter".to_owned(), None,
                            Some(msg.channel().to_owned()), Some(msg.user().to_owned()),
                            msg.text().to_owned(), self.tx_adapter.clone());

                        self.tx_bot.send(incoming).ok().expect("Bot unable to process messages");
                    },
                    _ => ()
                }
            },
            Err(e) => {
                println!("error decoding slack message: {:?}", e);
                println!("please consider reporting this to jwilm/chatbot as it is probably a bug");
            }
        }
    }

    fn on_ping(&mut self, cli: &mut slack::RtmClient) { }

    fn on_close(&mut self, cli: &mut slack::RtmClient) { }

    fn on_connect(&mut self, cli: &mut slack::RtmClient) { }
}

impl ChatAdapter for SlackAdapter {
    /// SlackAdapter name
    fn get_name(&self) -> &str {
        "SlackAdapter"
    }

    fn process_events(&self) -> Receiver<IncomingMessage> {
        println!("SlackAdapter: process_events");
        let (tx_bot, rx_bot) = channel();
        let (tx_adapter, rx_adapter) = channel();

        let uid = AtomicIsize::new(0);

        let mut cli = slack::RtmClient::new();
        let (client, slack_rx) = cli.login(self.token.as_ref()).unwrap();
        let slack_tx = cli.get_outs().unwrap();

        thread::Builder::new().name("Chatbot Slack Receiver".to_owned()).spawn(move || {
            let mut handler = MyHandler{count: 0, tx_bot: tx_bot, tx_adapter: tx_adapter};
            cli.run::<MyHandler>(&mut handler, client, slack_rx).unwrap();
        }).ok().expect("failed to create thread for slack receiver");

        thread::Builder::new().name("Chatbot Slack Sender".to_owned()).spawn(move || {
            loop {
                match rx_adapter.recv() {
                    Ok(msg) => {
                        match msg {
                            AdapterMsg::Outgoing(m) => {
                                let id = uid.fetch_add(1, Ordering::SeqCst) as i64;
                                let out = OutgoingSlackMsg::new(id, m);
                                slack_tx.send(Message::Text(out.to_json().to_string())).unwrap();
                            }
                            _ => unreachable!("No other messages being sent yet")
                        }
                    },
                    Err(e) => {
                        println!("error receiving outgoing messages: {}", e);
                        break
                    }
                }
            }
        }).ok().expect("failed to create thread for slack sender");

        rx_bot
    }
}

#[cfg(test)]
mod tests {
    use adapter::slack::SlackMsg;
    use adapter::slack::string_to_slack_msg;

    #[test]
    fn decode_message() {
        let raw = "{\"type\":\"message\", \
                    \"channel\":\"D04UYUAMW\", \
                    \"user\":\"U02ALMR84\", \
                    \"text\":\"ping\", \
                    \"ts\":\"1432563914.000007\", \
                    \"team\":\"T02ALMR82\"}";

        let slack_msg = string_to_slack_msg(raw).unwrap();

        match slack_msg {
            SlackMsg::Message(data) => {
                assert_eq!(data.text(), "ping");
                assert_eq!(data.channel(), "D04UYUAMW");
                assert_eq!(data.user(), "U02ALMR84");
                assert_eq!(data.ts(), "1432563914.000007");
                assert_eq!(data.team(), "T02ALMR82");
            },
            _ => panic!("Expected SlackMsg::Message")
        }
    }
    #[test]
    fn decode_confirmation_message() {
        let raw = r#"{"ok":true,"reply_to":0,"ts":"1432566639.000014","text":"pong"}"#;

        match string_to_slack_msg(raw).unwrap() {
            SlackMsg::Other => (),
            _ => panic!("expected SlackMsg::Other")
        }
    }

    #[test]
    fn decode_unexpected_type() {
        let raw = r#"{"type":"not_a_slack_msg_type"}"#;

        match string_to_slack_msg(raw).unwrap() {
            SlackMsg::Other => (),
            _ => panic!("expected SlackMsg::Other")
        }
    }
}
<commit_msg>[WIP] slack: Add enum Msg for message sub types<commit_after>extern crate slack;

use std::collections::BTreeMap;
use std::env;
use std::sync::atomic::{AtomicIsize, Ordering};
use std::sync::mpsc::Sender;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::channel;
use std::thread;

use rustc_serialize::json::{self, Json, ToJson};
use rustc_serialize::json::DecoderError::MissingFieldError;
use rustc_serialize::Decodable;
use rustc_serialize::Decoder;

use slack::Message;

use adapter::ChatAdapter;
use message::AdapterMsg;
use message::IncomingMessage;
use message::OutgoingMessage;

/// SlackAdapter sends and receives messages from the Slack chat service. Until actualy
/// configuration is added, the slack token should be placed in the environment variable
/// `SLACK_BOT_TOKEN`
pub struct SlackAdapter {
    token: String
}

impl SlackAdapter {
    pub fn new() -> SlackAdapter {
        SlackAdapter {
            token: match env::var("SLACK_BOT_TOKEN") {
                Ok(t) => t,
                Err(_) => panic!("Failed to get SLACK_BOT_TOKEN from env")
            }
        }
    }
}

struct MyHandler {
  count: i64,
  tx_bot: Sender<IncomingMessage>,
  tx_adapter: Sender<AdapterMsg>
}

/// Data for a Event::Message
#[allow(dead_code)]
struct MessageData {
    text: String,
    channel: String,
    user: String,
    ts: String,
    team: String
}

macro_rules! str_accessor {
    ($s:ident) => {
        pub fn $s(&self) -> &str {
            self.$s.as_ref()
        }
    }
}

#[allow(dead_code)]
impl MessageData {
    str_accessor!(text);
    str_accessor!(channel);
    str_accessor!(user);
    str_accessor!(ts);
    str_accessor!(team);
}

/// Incoming slack messages on the websocket api
enum Event {
    /// A message was sent to a channel
    Message(Msg),

    /// Some other type of message arrived.
    /// [The list](https://api.slack.com/rtm) is quite extensive, and only the
    /// messages the adapter is concerned with are enumerated here.
    Other(json::Object)
}

/// Event::Message sub types. Message events are the only event capable of having a sub type.
enum Msg {
    /// A regular text message from a user
    Plain(MessageData),

    /// Subtype not explicitly handled. For a complete enumeration, check
    /// https://api.slack.com/events/message
    Other(json::Object)
}

#[derive(Debug)]
struct MessageDecodeError(&'static str);

impl From<json::BuilderError> for MessageDecodeError {
    fn from(err: json::BuilderError) -> MessageDecodeError {
        MessageDecodeError("json::BuilderError")
    }
}

/// Extract a &str ($key) from a json::Object ($obj)
/// Returns an Err(MessageDecodeError) when it fails
macro_rules! get_json_string {
    ($obj:ident, $key:expr) => {
        {
            let json_str: &Json = match $obj.get($key) {
                Some(json_str) => json_str,
                None => return Err(MessageDecodeError("missing field"))
            };

            match json_str.as_string() {
                Some(slice) => slice.to_owned(),
                None => return Err(MessageDecodeError("not a string"))
            }
        }
    }
}

fn decode_msg_json(obj: json::Object, msg_type: &str) -> Result<Event, MessageDecodeError> {
    // Messages with a `subtype` are not plain text messages.. Not interested in them for now.
    if obj.contains_key("subtype") {
        return Ok(Event::Message(Msg::Other(obj)))
    }

    // It's a plain message.
    Ok(Event::Message(Msg::Plain(MessageData {
        text: get_json_string!(obj, "text"),
        channel: get_json_string!(obj, "channel"),
        user: get_json_string!(obj, "user"),
        ts: get_json_string!(obj, "ts"),
        team: get_json_string!(obj, "team"),
    })))
}

fn decode_json(raw: &str) -> Result<Event, MessageDecodeError> {
    let json = try!(Json::from_str(raw));
    let obj = match json {
        Json::Object(obj) => obj,
        _ => return Err(MessageDecodeError("string is not json object"))
    };

    // If the message does not have a `type`, it is a confirmation message
    if !obj.contains_key("type") {
        return Ok(Event::Other(obj))
    }

    let msg = match obj.get("type").expect("obj has key type but failed to get").as_string() {
        Some(s) => s.to_owned(),
        None => return Err(MessageDecodeError("type field is not a string"))
    };

    match msg.as_ref() {
        // If it's a message type, try and return an Event::Message
        "message" => decode_msg_json(obj, msg.as_ref()),
        // Some other type we don't explicitly handle
        _ => Ok(Event::Other(obj)),
    }
}

/// Convert a JSON string to a Event
///
/// This methods provides additional error handling around json::decode for certain errors
/// that cannot be handled in the Decodable implementation. Specifically, MissingFieldError
/// where the field is "type" are actually valid messages despite missing the "type" field.
fn string_to_slack_msg(raw: &str) -> Result<Event, MessageDecodeError> {
    // Some messages arriving from the slack client don't have a type. So far I've only
    // witnessed confirmation messages arriving in this fashion. Since they go through the same
    // pipeline as content messages, the decoder should be able to handle them.
    decode_json(raw)
}

#[derive(Debug)]
struct OutgoingEvent {
    id: i64,
    channel: String,
    msg_type: String,
    text: String
}

impl OutgoingEvent {
    fn new(id: i64, m: OutgoingMessage) -> OutgoingEvent {
        OutgoingEvent {
            id: id,
            channel: m.get_incoming().channel().expect("missing channel").to_owned(),
            msg_type: "message".to_owned(),
            text: m.as_ref().to_owned() // TODO move instead of copy
        }
    }
}

impl ToJson for OutgoingEvent {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        d.insert("id".to_string(), self.id.to_json());
        d.insert("channel".to_string(), self.channel.to_json());
        d.insert("type".to_string(), self.msg_type.to_json());
        d.insert("text".to_string(), self.text.to_json());
        Json::Object(d)
    }
}

#[allow(unused_variables)]
impl slack::MessageHandler for MyHandler {
    fn on_receive(&mut self, cli: &mut slack::RtmClient, raw: &str) {
        println!("Received[{}]: {}", self.count, raw.to_string());
        self.count = self.count + 1;

        match string_to_slack_msg(raw) {
            Ok(slack_msg) => {
                match slack_msg {
                    Event::Message(Msg::Plain(msg)) => {
                        let incoming = IncomingMessage::new("SlackAdapter".to_owned(), None,
                            Some(msg.channel().to_owned()), Some(msg.user().to_owned()),
                            msg.text().to_owned(), self.tx_adapter.clone());

                        self.tx_bot.send(incoming).ok().expect("Bot unable to process messages");
                    },
                    _ => ()
                }
            },
            Err(e) => {
                println!("error decoding slack message: {:?}", e);
                println!("please consider reporting this to jwilm/chatbot as it is probably a bug");
            }
        }
    }

    fn on_ping(&mut self, cli: &mut slack::RtmClient) { }

    fn on_close(&mut self, cli: &mut slack::RtmClient) { }

    fn on_connect(&mut self, cli: &mut slack::RtmClient) { }
}

impl ChatAdapter for SlackAdapter {
    /// SlackAdapter name
    fn get_name(&self) -> &str {
        "SlackAdapter"
    }

    fn process_events(&self) -> Receiver<IncomingMessage> {
        println!("SlackAdapter: process_events");
        let (tx_bot, rx_bot) = channel();
        let (tx_adapter, rx_adapter) = channel();

        let uid = AtomicIsize::new(0);

        let mut cli = slack::RtmClient::new();
        let (client, slack_rx) = cli.login(self.token.as_ref()).unwrap();
        let slack_tx = cli.get_outs().unwrap();

        thread::Builder::new().name("Chatbot Slack Receiver".to_owned()).spawn(move || {
            let mut handler = MyHandler{count: 0, tx_bot: tx_bot, tx_adapter: tx_adapter};
            cli.run::<MyHandler>(&mut handler, client, slack_rx).unwrap();
        }).ok().expect("failed to create thread for slack receiver");

        thread::Builder::new().name("Chatbot Slack Sender".to_owned()).spawn(move || {
            loop {
                match rx_adapter.recv() {
                    Ok(msg) => {
                        match msg {
                            AdapterMsg::Outgoing(m) => {
                                let id = uid.fetch_add(1, Ordering::SeqCst) as i64;
                                let out = OutgoingEvent::new(id, m);
                                slack_tx.send(Message::Text(out.to_json().to_string())).unwrap();
                            }
                            _ => unreachable!("No other messages being sent yet")
                        }
                    },
                    Err(e) => {
                        println!("error receiving outgoing messages: {}", e);
                        break
                    }
                }
            }
        }).ok().expect("failed to create thread for slack sender");

        rx_bot
    }
}

#[cfg(test)]
mod tests {
    use adapter::slack::Event;
    use adapter::slack::Msg;
    use adapter::slack::string_to_slack_msg;

    #[test]
    fn decode_message() {
        let raw = "{\"type\":\"message\", \
                    \"channel\":\"D04UYUAMW\", \
                    \"user\":\"U02ALMR84\", \
                    \"text\":\"ping\", \
                    \"ts\":\"1432563914.000007\", \
                    \"team\":\"T02ALMR82\"}";

        let slack_msg = string_to_slack_msg(raw).unwrap();

        match slack_msg {
            Event::Message(Msg::Plain(data)) => {
                assert_eq!(data.text(), "ping");
                assert_eq!(data.channel(), "D04UYUAMW");
                assert_eq!(data.user(), "U02ALMR84");
                assert_eq!(data.ts(), "1432563914.000007");
                assert_eq!(data.team(), "T02ALMR82");
            },
            _ => panic!("Expected Event::Message")
        }
    }
    #[test]
    fn decode_confirmation_message() {
        let raw = r#"{"ok":true,"reply_to":0,"ts":"1432566639.000014","text":"pong"}"#;

        match string_to_slack_msg(raw).unwrap() {
            Event::Other(_) => (),
            _ => panic!("expected Event::Other")
        }
    }

    #[test]
    fn decode_unexpected_type() {
        let raw = r#"{"type":"not_a_slack_msg_type"}"#;

        match string_to_slack_msg(raw).unwrap() {
            Event::Other(_) => (),
            _ => panic!("expected Event::Other")
        }
    }

    #[test]
    fn decode_message_changed() {
        let raw = r#"{
            "type":"message",
            "message":{
                "type":"message",
                "user":"U02ALMR84",
                "text":"arst",
                "edited":{
                    "user":"U02ALMR84",
                    "ts":"1432695814.000000"
                },
                "ts":"1432695812.000056"
            },
            "subtype":"message_changed",
            "hidden":true,
            "channel":"D04UYUAMW",
            "event_ts":"1432695814.616510",
            "ts":"1432695814.000057"
        }"#;

        match string_to_slack_msg(raw).unwrap() {
            Event::Message(Msg::Other(_)) => return,
            _ => panic!("expected Msg::Other")
        }
    }

    #[test]
    fn decode_me_message() {
        let raw = r#"{
            "text":"is a potato",
            "type":"message",
            "subtype":"me_message",
            "user":"U02ALMR84",
            "channel":"D04UYUAMW",
            "ts":"1432695826.000060"
        }"#;
    }

    #[test]
    fn decode_message_deleted() {
        let raw = r#"{
            "type":"message",
            "deleted_ts":"1432695826.000060",
            "subtype":"message_deleted",
            "hidden":true,
            "channel":"D04UYUAMW",
            "event_ts":"1432695848.617155",
            "ts":"1432695848.000061"
        }"#;
    }

}
<|endoftext|>


<commit_before>extern crate proc_macro;

use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;

#[proc_macro_derive(Asn1Read, attributes(explicit, implicit, default))]
pub fn derive_asn1_read(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input as syn::DeriveInput);

    let name = input.ident;
    let (impl_lifetimes, ty_lifetimes, lifetime_name) = add_lifetime_if_none(input.generics);

    let expanded = match input.data {
        syn::Data::Struct(data) => {
            let read_block = generate_struct_read_block(&data);
            quote::quote! {
                impl<#impl_lifetimes> asn1::SimpleAsn1Readable<#lifetime_name> for #name<#ty_lifetimes> {
                    const TAG: u8 = <asn1::Sequence as asn1::SimpleAsn1Readable>::TAG;
                    fn parse_data(data: &#lifetime_name [u8]) -> asn1::ParseResult<Self> {
                        asn1::parse(data, |p| #read_block)
                    }
                }
            }
        }
        syn::Data::Enum(data) => {
            let (read_block, can_parse_block) = generate_enum_read_block(&name, &data);
            quote::quote! {
                impl<#impl_lifetimes> asn1::Asn1Readable<#lifetime_name> for #name<#ty_lifetimes> {
                    fn parse(parser: &mut asn1::Parser<#lifetime_name>) -> asn1::ParseResult<Self> {
                        let tlv = parser.read_element::<asn1::Tlv>()?;
                        #read_block
                        Err(asn1::ParseError::UnexpectedTag{actual: tlv.tag()})
                    }

                    fn can_parse(tag: u8) -> bool {
                        #can_parse_block
                        false
                    }
                }
            }
        }
        _ => unimplemented!("Not supported for unions"),
    };

    proc_macro::TokenStream::from(expanded)
}

#[proc_macro_derive(Asn1Write, attributes(explicit, implicit, default))]
pub fn derive_asn1_write(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input as syn::DeriveInput);

    let name = input.ident;
    let (impl_lifetimes, ty_lifetimes, lifetime_name) = add_lifetime_if_none(input.generics);

    let expanded = match input.data {
        syn::Data::Struct(data) => {
            let write_block = generate_struct_write_block(&data);
            quote::quote! {
                impl<#impl_lifetimes> asn1::SimpleAsn1Writable<#lifetime_name> for #name<#ty_lifetimes> {
                    const TAG: u8 = <asn1::SequenceWriter as asn1::SimpleAsn1Writable>::TAG;
                    fn write_data(&self, dest: &mut Vec<u8>) {
                        #write_block
                    }
                }
            }
        }
        syn::Data::Enum(data) => {
            let write_block = generate_enum_write_block(&name, &data);
            quote::quote! {
                impl<#impl_lifetimes> asn1::Asn1Writable<#lifetime_name> for #name<#ty_lifetimes> {
                    fn write(&self, w: &mut asn1::Writer) {
                        #write_block
                    }
                }
            }
        }
        _ => unimplemented!("Not supported for unions"),
    };

    proc_macro::TokenStream::from(expanded)
}

fn add_lifetime_if_none(
    mut generics: syn::Generics,
) -> (
    Punctuated<syn::Lifetime, Comma>,
    Punctuated<syn::Lifetime, Comma>,
    syn::Lifetime,
) {
    let mut impl_lifetimes = Punctuated::new();
    let mut ty_lifetimes = Punctuated::new();
    let mut lifetime = None;
    for param in &mut generics.params {
        if let syn::GenericParam::Lifetime(lifetime_def) = param {
            impl_lifetimes.push(lifetime_def.lifetime.clone());
            ty_lifetimes.push(lifetime_def.lifetime.clone());
            lifetime = Some(lifetime_def.lifetime.clone());
        }
    }

    let lifetime = lifetime.unwrap_or_else(|| {
        let lifetime = syn::Lifetime::new("'a", proc_macro2::Span::call_site());
        impl_lifetimes.push(lifetime.clone());
        lifetime
    });
    (impl_lifetimes, ty_lifetimes, lifetime)
}

enum OpType {
    Regular,
    Explicit(proc_macro2::Literal),
    Implicit(proc_macro2::Literal),
}

fn generate_read_element(f: &syn::Field) -> proc_macro2::TokenStream {
    let mut read_type = OpType::Regular;
    let mut default = None;
    for attr in &f.attrs {
        if attr.path.is_ident("explicit") {
            if let OpType::Regular = read_type {
                read_type = OpType::Explicit(attr.parse_args::<proc_macro2::Literal>().unwrap());
            } else {
                panic!("Can't specify #[explicit] or #[implicit] more than once")
            }
        } else if attr.path.is_ident("implicit") {
            if let OpType::Regular = read_type {
                read_type = OpType::Implicit(attr.parse_args::<proc_macro2::Literal>().unwrap());
            } else {
                panic!("Can't specify #[explicit] or #[implicit] more than once")
            }
        } else if attr.path.is_ident("default") {
            if default.is_some() {
                panic!("Can't specify #[default] more than once");
            }
            default = Some(attr.parse_args::<syn::Lit>().unwrap());
        }
    }

    let mut read_op = match read_type {
        OpType::Explicit(arg) => quote::quote! {
            p.read_optional_explicit_element(#arg)?
        },
        OpType::Implicit(arg) => quote::quote! {
            p.read_optional_implicit_element(#arg)?
        },
        OpType::Regular => quote::quote! {
            p.read_element()?
        },
    };
    if let Some(default) = default {
        read_op = quote::quote! {{
            asn1::from_optional_default(#read_op, #default.into())?
        }};
    }
    read_op
}

fn generate_struct_read_block(data: &syn::DataStruct) -> proc_macro2::TokenStream {
    match data.fields {
        syn::Fields::Named(ref fields) => {
            let recurse = fields.named.iter().map(|f| {
                let name = &f.ident;
                let read_op = generate_read_element(f);
                quote::quote_spanned! {f.span() =>
                    #name: #read_op,
                }
            });

            quote::quote! {
                Ok(Self {
                    #(#recurse)*
                })
            }
        }
        syn::Fields::Unnamed(ref fields) => {
            let recurse = fields.unnamed.iter().map(|f| {
                let read_op = generate_read_element(f);
                quote::quote_spanned! {f.span() =>
                    #read_op,
                }
            });

            quote::quote! {
                Ok(Self(
                    #(#recurse)*
                ))
            }
        }
        syn::Fields::Unit => {
            quote::quote! { Ok(Self) }
        }
    }
}

fn generate_enum_read_block(
    name: &syn::Ident,
    data: &syn::DataEnum,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
    let mut read_blocks = vec![];
    let mut can_parse_blocks = vec![];

    for variant in &data.variants {
        let field = match &variant.fields {
            syn::Fields::Unnamed(fields) => {
                assert_eq!(fields.unnamed.len(), 1);
                &fields.unnamed[0]
            }
            _ => panic!("enum elements must have a single field"),
        };
        let ty = &field.ty;
        let ident = &variant.ident;
        read_blocks.push(quote::quote! {
            if <#ty>::can_parse(tlv.tag()) {
                return Ok(#name::#ident(tlv.parse()?));
            }
        });
        can_parse_blocks.push(quote::quote! {
            if <#ty>::can_parse(tag) {
                return true;
            }
        });
    }

    let read_block = quote::quote! {
        #(#read_blocks)*
    };
    let can_parse_block = quote::quote! {
        #(#can_parse_blocks)*
    };
    (read_block, can_parse_block)
}

fn generate_write_element(
    f: &syn::Field,
    mut field_read: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
    let mut write_type = OpType::Regular;
    let mut default = None;
    for attr in &f.attrs {
        if attr.path.is_ident("explicit") {
            if let OpType::Regular = write_type {
                write_type = OpType::Explicit(attr.parse_args::<proc_macro2::Literal>().unwrap());
            } else {
                panic!("Can't specify #[explicit] or #[implicit] more than once")
            }
        } else if attr.path.is_ident("implicit") {
            if let OpType::Regular = write_type {
                write_type = OpType::Implicit(attr.parse_args::<proc_macro2::Literal>().unwrap());
            } else {
                panic!("Can't specify #[explicit] or #[implicit] more than once")
            }
        } else if attr.path.is_ident("default") {
            if default.is_some() {
                panic!("Can't specify #[default] more than once");
            }
            default = Some(attr.parse_args::<syn::Lit>().unwrap());
        }
    }

    if let Some(default) = default {
        field_read = quote::quote! {&{
            asn1::to_optional_default(#field_read, &(#default).into())
        }}
    }

    match write_type {
        OpType::Explicit(arg) => quote::quote_spanned! {f.span() =>
            w.write_optional_explicit_element(#field_read, #arg);
        },
        OpType::Implicit(arg) => quote::quote_spanned! {f.span() =>
            w.write_optional_implicit_element(#field_read, #arg);
        },
        OpType::Regular => quote::quote! {
            w.write_element(#field_read);
        },
    }
}

fn generate_struct_write_block(data: &syn::DataStruct) -> proc_macro2::TokenStream {
    match data.fields {
        syn::Fields::Named(ref fields) => {
            let recurse = fields.named.iter().map(|f| {
                let name = &f.ident;
                generate_write_element(f, quote::quote! { &self.#name })
            });

            quote::quote! {
                let mut w = asn1::Writer::new(dest);
                #(#recurse)*
            }
        }
        syn::Fields::Unnamed(ref fields) => {
            let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| {
                let index = syn::Index::from(i);
                generate_write_element(f, quote::quote! { &self.#index })
            });

            quote::quote! {
                let mut w = asn1::Writer::new(dest);
                #(#recurse)*
            }
        }
        syn::Fields::Unit => {
            quote::quote! {}
        }
    }
}

fn generate_enum_write_block(name: &syn::Ident, data: &syn::DataEnum) -> proc_macro2::TokenStream {
    let write_arms = data.variants.iter().map(|v| {
        match &v.fields {
            syn::Fields::Unnamed(fields) => {
                assert_eq!(fields.unnamed.len(), 1);
            }
            _ => panic!("enum elements must have a single field"),
        };
        let ident = &v.ident;

        quote::quote! {
            #name::#ident(value) => w.write_element(value),
        }
    });
    quote::quote! {
        match self {
            #(#write_arms)*
        }
    }
}
<commit_msg>Remove some duplicate code in derive implementation (#164)<commit_after>extern crate proc_macro;

use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;

#[proc_macro_derive(Asn1Read, attributes(explicit, implicit, default))]
pub fn derive_asn1_read(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input as syn::DeriveInput);

    let name = input.ident;
    let (impl_lifetimes, ty_lifetimes, lifetime_name) = add_lifetime_if_none(input.generics);

    let expanded = match input.data {
        syn::Data::Struct(data) => {
            let read_block = generate_struct_read_block(&data);
            quote::quote! {
                impl<#impl_lifetimes> asn1::SimpleAsn1Readable<#lifetime_name> for #name<#ty_lifetimes> {
                    const TAG: u8 = <asn1::Sequence as asn1::SimpleAsn1Readable>::TAG;
                    fn parse_data(data: &#lifetime_name [u8]) -> asn1::ParseResult<Self> {
                        asn1::parse(data, |p| #read_block)
                    }
                }
            }
        }
        syn::Data::Enum(data) => {
            let (read_block, can_parse_block) = generate_enum_read_block(&name, &data);
            quote::quote! {
                impl<#impl_lifetimes> asn1::Asn1Readable<#lifetime_name> for #name<#ty_lifetimes> {
                    fn parse(parser: &mut asn1::Parser<#lifetime_name>) -> asn1::ParseResult<Self> {
                        let tlv = parser.read_element::<asn1::Tlv>()?;
                        #read_block
                        Err(asn1::ParseError::UnexpectedTag{actual: tlv.tag()})
                    }

                    fn can_parse(tag: u8) -> bool {
                        #can_parse_block
                        false
                    }
                }
            }
        }
        _ => unimplemented!("Not supported for unions"),
    };

    proc_macro::TokenStream::from(expanded)
}

#[proc_macro_derive(Asn1Write, attributes(explicit, implicit, default))]
pub fn derive_asn1_write(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input as syn::DeriveInput);

    let name = input.ident;
    let (impl_lifetimes, ty_lifetimes, lifetime_name) = add_lifetime_if_none(input.generics);

    let expanded = match input.data {
        syn::Data::Struct(data) => {
            let write_block = generate_struct_write_block(&data);
            quote::quote! {
                impl<#impl_lifetimes> asn1::SimpleAsn1Writable<#lifetime_name> for #name<#ty_lifetimes> {
                    const TAG: u8 = <asn1::SequenceWriter as asn1::SimpleAsn1Writable>::TAG;
                    fn write_data(&self, dest: &mut Vec<u8>) {
                        #write_block
                    }
                }
            }
        }
        syn::Data::Enum(data) => {
            let write_block = generate_enum_write_block(&name, &data);
            quote::quote! {
                impl<#impl_lifetimes> asn1::Asn1Writable<#lifetime_name> for #name<#ty_lifetimes> {
                    fn write(&self, w: &mut asn1::Writer) {
                        #write_block
                    }
                }
            }
        }
        _ => unimplemented!("Not supported for unions"),
    };

    proc_macro::TokenStream::from(expanded)
}

fn add_lifetime_if_none(
    mut generics: syn::Generics,
) -> (
    Punctuated<syn::Lifetime, Comma>,
    Punctuated<syn::Lifetime, Comma>,
    syn::Lifetime,
) {
    let mut impl_lifetimes = Punctuated::new();
    let mut ty_lifetimes = Punctuated::new();
    let mut lifetime = None;
    for param in &mut generics.params {
        if let syn::GenericParam::Lifetime(lifetime_def) = param {
            impl_lifetimes.push(lifetime_def.lifetime.clone());
            ty_lifetimes.push(lifetime_def.lifetime.clone());
            lifetime = Some(lifetime_def.lifetime.clone());
        }
    }

    let lifetime = lifetime.unwrap_or_else(|| {
        let lifetime = syn::Lifetime::new("'a", proc_macro2::Span::call_site());
        impl_lifetimes.push(lifetime.clone());
        lifetime
    });
    (impl_lifetimes, ty_lifetimes, lifetime)
}

enum OpType {
    Regular,
    Explicit(proc_macro2::Literal),
    Implicit(proc_macro2::Literal),
}

fn extract_field_properties(f: &syn::Field) -> (OpType, Option<syn::Lit>) {
    let mut op_type = OpType::Regular;
    let mut default = None;
    for attr in &f.attrs {
        if attr.path.is_ident("explicit") {
            if let OpType::Regular = op_type {
                op_type = OpType::Explicit(attr.parse_args::<proc_macro2::Literal>().unwrap());
            } else {
                panic!("Can't specify #[explicit] or #[implicit] more than once")
            }
        } else if attr.path.is_ident("implicit") {
            if let OpType::Regular = op_type {
                op_type = OpType::Implicit(attr.parse_args::<proc_macro2::Literal>().unwrap());
            } else {
                panic!("Can't specify #[explicit] or #[implicit] more than once")
            }
        } else if attr.path.is_ident("default") {
            if default.is_some() {
                panic!("Can't specify #[default] more than once");
            }
            default = Some(attr.parse_args::<syn::Lit>().unwrap());
        }
    }

    (op_type, default)
}

fn generate_read_element(f: &syn::Field) -> proc_macro2::TokenStream {
    let (read_type, default) = extract_field_properties(f);

    let mut read_op = match read_type {
        OpType::Explicit(arg) => quote::quote! {
            p.read_optional_explicit_element(#arg)?
        },
        OpType::Implicit(arg) => quote::quote! {
            p.read_optional_implicit_element(#arg)?
        },
        OpType::Regular => quote::quote! {
            p.read_element()?
        },
    };
    if let Some(default) = default {
        read_op = quote::quote! {{
            asn1::from_optional_default(#read_op, #default.into())?
        }};
    }
    read_op
}

fn generate_struct_read_block(data: &syn::DataStruct) -> proc_macro2::TokenStream {
    match data.fields {
        syn::Fields::Named(ref fields) => {
            let recurse = fields.named.iter().map(|f| {
                let name = &f.ident;
                let read_op = generate_read_element(f);
                quote::quote_spanned! {f.span() =>
                    #name: #read_op,
                }
            });

            quote::quote! {
                Ok(Self {
                    #(#recurse)*
                })
            }
        }
        syn::Fields::Unnamed(ref fields) => {
            let recurse = fields.unnamed.iter().map(|f| {
                let read_op = generate_read_element(f);
                quote::quote_spanned! {f.span() =>
                    #read_op,
                }
            });

            quote::quote! {
                Ok(Self(
                    #(#recurse)*
                ))
            }
        }
        syn::Fields::Unit => {
            quote::quote! { Ok(Self) }
        }
    }
}

fn generate_enum_read_block(
    name: &syn::Ident,
    data: &syn::DataEnum,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
    let mut read_blocks = vec![];
    let mut can_parse_blocks = vec![];

    for variant in &data.variants {
        let field = match &variant.fields {
            syn::Fields::Unnamed(fields) => {
                assert_eq!(fields.unnamed.len(), 1);
                &fields.unnamed[0]
            }
            _ => panic!("enum elements must have a single field"),
        };
        let ty = &field.ty;
        let ident = &variant.ident;
        read_blocks.push(quote::quote! {
            if <#ty>::can_parse(tlv.tag()) {
                return Ok(#name::#ident(tlv.parse()?));
            }
        });
        can_parse_blocks.push(quote::quote! {
            if <#ty>::can_parse(tag) {
                return true;
            }
        });
    }

    let read_block = quote::quote! {
        #(#read_blocks)*
    };
    let can_parse_block = quote::quote! {
        #(#can_parse_blocks)*
    };
    (read_block, can_parse_block)
}

fn generate_write_element(
    f: &syn::Field,
    mut field_read: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
    let (write_type, default) = extract_field_properties(f);

    if let Some(default) = default {
        field_read = quote::quote! {&{
            asn1::to_optional_default(#field_read, &(#default).into())
        }}
    }

    match write_type {
        OpType::Explicit(arg) => quote::quote_spanned! {f.span() =>
            w.write_optional_explicit_element(#field_read, #arg);
        },
        OpType::Implicit(arg) => quote::quote_spanned! {f.span() =>
            w.write_optional_implicit_element(#field_read, #arg);
        },
        OpType::Regular => quote::quote! {
            w.write_element(#field_read);
        },
    }
}

fn generate_struct_write_block(data: &syn::DataStruct) -> proc_macro2::TokenStream {
    match data.fields {
        syn::Fields::Named(ref fields) => {
            let recurse = fields.named.iter().map(|f| {
                let name = &f.ident;
                generate_write_element(f, quote::quote! { &self.#name })
            });

            quote::quote! {
                let mut w = asn1::Writer::new(dest);
                #(#recurse)*
            }
        }
        syn::Fields::Unnamed(ref fields) => {
            let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| {
                let index = syn::Index::from(i);
                generate_write_element(f, quote::quote! { &self.#index })
            });

            quote::quote! {
                let mut w = asn1::Writer::new(dest);
                #(#recurse)*
            }
        }
        syn::Fields::Unit => {
            quote::quote! {}
        }
    }
}

fn generate_enum_write_block(name: &syn::Ident, data: &syn::DataEnum) -> proc_macro2::TokenStream {
    let write_arms = data.variants.iter().map(|v| {
        match &v.fields {
            syn::Fields::Unnamed(fields) => {
                assert_eq!(fields.unnamed.len(), 1);
            }
            _ => panic!("enum elements must have a single field"),
        };
        let ident = &v.ident;

        quote::quote! {
            #name::#ident(value) => w.write_element(value),
        }
    });
    quote::quote! {
        match self {
            #(#write_arms)*
        }
    }
}
<|endoftext|>


<commit_before>use std::fs;
use std::io;
use std::io::prelude::*;
use std::path;
use toml;
use error;

#[allow(dead_code)]
#[derive(Deserialize)]
pub struct Config {
    version: f64,
    tools: Vec<Tools>,
}

#[allow(dead_code)]
#[derive(Deserialize)]
pub struct Tools {
    command: String,
    extentions: Vec<String>,
}

pub fn load_config(conf_name: &str) -> Result<Config, error::AppError> {
    let path = path::Path::new(conf_name);
    if path.exists() == false {
        return Result::Err(error::AppError::Io(io::Error::new(
            io::ErrorKind::NotFound,
            format!("{} is not exist", path.to_str().unwrap_or("")),
        )));
    }

    let mut reader = io::BufReader::new(fs::File::open(path).map_err(error::AppError::Io)?);
    let mut s = String::new();
    reader.read_to_string(&mut s).map_err(error::AppError::Io)?;

    let conf: Config = toml::from_str(s.as_str()).map_err(error::AppError::Toml)?;

    Ok(conf)
}

pub fn get_commnad(conf: &Config, extension: &str) -> Option<String> {
    let ext_string = extension.to_string();
    for t in conf.tools.iter() {
        if t.extentions.contains(&ext_string) {
            return Some(t.command.clone());
        }
    }
    None
}

#[test]
fn test_decode_toml() {
    let toml_config_str = r#"
    version = 0.0

    [[tools]]
    command = "apvlv"
    extentions = [ "pdf" ]

    [[tools]]
    command = "mirage"
    extentions = [ "jpg", "png", "gif" ]

    [[tools]]
    command = "vlc"
    extentions = [ "mp4", "mov", "avi" ]

    [[tools]]
    command = "vim -R"
    extentions = [ "conf" ]
    "#;

    let config: Config = toml::from_str(toml_config_str).unwrap();
    assert_eq!(config.version, 0.0);

    assert_eq!(config.tools[0].command, "apvlv".to_string());
    assert_eq!(config.tools[0].extentions[0], "pdf".to_string());

    assert_eq!(config.tools[1].command, "mirage".to_string());
    assert_eq!(config.tools[1].extentions[0], "jpg".to_string());
    assert_eq!(config.tools[1].extentions[1], "png".to_string());
    assert_eq!(config.tools[1].extentions[2], "gif".to_string());

    assert_eq!(config.tools[2].command, "vlc".to_string());
    assert_eq!(config.tools[2].extentions[0], "mp4".to_string());
    assert_eq!(config.tools[2].extentions[1], "mov".to_string());
    assert_eq!(config.tools[2].extentions[2], "avi".to_string());

    assert_eq!(config.tools[3].command, "vim -R".to_string());
    assert_eq!(config.tools[3].extentions[0], "conf".to_string());
}
<commit_msg>add test<commit_after>use std::fs;
use std::io;
use std::io::prelude::*;
use std::path;
use toml;
use error;

#[allow(dead_code)]
#[derive(Deserialize)]
pub struct Config {
    version: f64,
    tools: Vec<Tools>,
}

#[allow(dead_code)]
#[derive(Deserialize)]
pub struct Tools {
    command: String,
    extentions: Vec<String>,
}

pub fn load_config(conf_name: &str) -> Result<Config, error::AppError> {
    let path = path::Path::new(conf_name);
    if path.exists() == false {
        return Result::Err(error::AppError::Io(io::Error::new(
            io::ErrorKind::NotFound,
            format!("{} is not exist", path.to_str().unwrap_or("")),
        )));
    }

    let mut reader = io::BufReader::new(fs::File::open(path).map_err(error::AppError::Io)?);
    let mut s = String::new();
    reader.read_to_string(&mut s).map_err(error::AppError::Io)?;

    let conf: Config = toml::from_str(s.as_str()).map_err(error::AppError::Toml)?;

    Ok(conf)
}

pub fn get_commnad(conf: &Config, extension: &str) -> Option<String> {
    let ext_string = extension.to_string();
    for t in conf.tools.iter() {
        if t.extentions.contains(&ext_string) {
            return Some(t.command.clone());
        }
    }
    None
}

#[test]
fn test_decode_toml() {
    let toml_config_str = r#"
    version = 0.0

    [[tools]]
    command = "apvlv"
    extentions = [ "pdf" ]

    [[tools]]
    command = "mirage"
    extentions = [ "jpg", "png", "gif" ]

    [[tools]]
    command = "vlc"
    extentions = [ "mp4", "mov", "avi" ]

    [[tools]]
    command = "vim -R"
    extentions = [ "conf" ]
    "#;

    let config: Config = toml::from_str(toml_config_str).unwrap();
    assert_eq!(config.version, 0.0);

    assert_eq!(config.tools[0].command, "apvlv".to_string());
    assert_eq!(config.tools[0].extentions[0], "pdf".to_string());

    assert_eq!(config.tools[1].command, "mirage".to_string());
    assert_eq!(config.tools[1].extentions[0], "jpg".to_string());
    assert_eq!(config.tools[1].extentions[1], "png".to_string());
    assert_eq!(config.tools[1].extentions[2], "gif".to_string());

    assert_eq!(config.tools[2].command, "vlc".to_string());
    assert_eq!(config.tools[2].extentions[0], "mp4".to_string());
    assert_eq!(config.tools[2].extentions[1], "mov".to_string());
    assert_eq!(config.tools[2].extentions[2], "avi".to_string());

    assert_eq!(config.tools[3].command, "vim -R".to_string());
    assert_eq!(config.tools[3].extentions[0], "conf".to_string());
}

#[test]
fn test_get_commnad() {
    let conf = Config {
        version: 0.0,
        tools: vec![
            Tools {
                command: "cat".to_string(),
                extentions: vec!["txt".to_string(), "log".to_string()],
            },
        ],
    };

    assert_eq!(get_commnad(&conf, "txt").unwrap(), "cat");
    assert_eq!(get_commnad(&conf, "log").unwrap(), "cat");
    assert_eq!(
        get_commnad(&conf, "jpg").unwrap_or("none".to_string()),
        "none"
    );
}
<|endoftext|>