<commit_before>#![allow(dead_code)]
// This file contains the memory allocator used by the rust_alloc module
//
// The implementation in this file is a simple first-fit allocator.
//
// Invariants:
// * All blocks will be a multiple of 16B
// * All blocks will be 16B-aligned
//
// TODO: lock free list

extern crate core;

use core::mem::{size_of};
use core::option::Option::{self, Some, None};

static mut START: usize = 0;
static mut END: usize = 0;

static mut free_list: *mut Block = (0 as *mut Block);

// heap stats
static mut SUCC_MALLOCS: usize = 0;
static mut FAIL_MALLOCS: usize = 0;
static mut FREES:        usize = 0;

// memory block
// the last word of every block is its allocation size
#[repr(C, packed)]
struct Block {
    magic: usize, // If this is a free block, then it is magical: 0xCAFEFACE
    size: usize, // Includes the size of the footer
    next: *mut Block,
    prev: *mut Block,
}

impl Block {
    // METHODS FOR ALL BLOCKS

    // returns true if this is a valid free block
    fn is_free(&self) -> bool {
        self.magic == 0xCAFEFACE && self.size % 0x10 == 0
    }

    // get the prev block in memory
    //
    // note: this is distinct from the prev block in the free list
    // behavior is undefined if this is the last block.
    unsafe fn get_prev(&self) -> *mut Block {
        // check for corner cases
        if self.this() == START {
            panic!("Free block has no previous block: {}", self.this());
        }

        // get the addr of previous block's size
        let prev_foot: *const usize = (self.this() - core::mem::size_of::<usize>()) as *const usize;
        let prev_size = *prev_foot;

        // get previous block's addr
        (self.this() - prev_size) as *mut Block
    }

    // get the next block in memory
    //
    // note: this is distinct from the next block in the free list
    // behavior is undefined if this is the last block.
    unsafe fn get_next(&self) -> *mut Block {
        // check for corner cases
        if self.this() + self.size == END {
            panic!("Free block has no next block: {}", self.this());
        }

        (self.this() + self.size) as *mut Block
    }

    // Set the footer for this block. This method does not error checking, so
    // be careful!
    unsafe fn set_footer(&self, size: usize) {
        let footer = (self.this() + self.size - core::mem::size_of::<usize>()) as *mut usize;
        *footer = size;
    }

    #[inline]
    unsafe fn this(&self) -> usize {
        self as *const Block as usize
    }

    // METHODS FOR ONLY FREE BLOCKS

    // split the block into two blocks. The first block will be of the
    // given size. The block must be free
    unsafe fn split(&mut self, size: usize) {
        // check that the math works out
        if !self.is_free() {
            panic!("Attempt to split non-free block 0x{:X}",
                   (self as *const Block) as usize);
        }
        if round_to_16(size + core::mem::size_of::<usize>()) >= self.size {
            panic!("Splitting block that is too small: 0x{:X}, size {}",
                   (self as *const Block) as usize, size);
        }

        // get new block addr
        let new_size = round_to_16(size + core::mem::size_of::<usize>());

        // create new block and set magic bits
        let block = (self.this() + new_size) as *mut Block;
        (*block).size = self.size - new_size;
        (*block).set_footer((*block).size);

        // adjust this block's metadata
        self.size = new_size;

        // insert at tail of free list
        (*block).insert();
    }

    // coalesce this block with the next one. The two blocks must be free
    unsafe fn combine(&mut self) {
        // check that both are free!
        if !self.is_free() {
            panic!("Attempt to coalesce non-free block 0x{:X}",
                   (self as *const Block) as usize);
        } else if !(*self.get_next()).is_free() {
            panic!("Attempt to coalesce non-free block 0x{:X}",
                   self.get_next() as *const Block as usize);
        }

        let next = self.get_next();

       // increase the size of this block
       self.size += (*next).size + core::mem::size_of::<usize>();

       // remove next block from free list
       (*next).remove();
    }

    // remove this block from the free list. The block
    // must be free. After this operation, the block's magic word is
    // set to 0xDEADBEEF.
    unsafe fn remove(&mut self) {
        if !self.is_free() {
            panic!("Attempt to remove non-free block from free list: {}", self.this());
        }

        // Set magic, so that this is definitely not a free block
        self.magic = 0xDEADBEEF;

        // get prev and next in free list
        // corner cases:
        // - if this is the head, prev = NULL
        // - if this is the tail, next = NULL

        if self.prev != (0 as *mut Block) {
            (*self.prev).next = self.next;
        } else {
            // remove the head of the list
            free_list = self.next;
        }

        if self.next != (0 as *mut Block) {
            // not the tail
            (*self.next).prev = self.prev;
        }
    }

    // METHODS FOR ONLY USED BLOCKS

    // inserts the given block at the head of the free list and sets
    // the magic bits. The block cannot already be free.
    unsafe fn insert(&mut self) {
        if self.is_free() {
            panic!("Attempt to insert a free block: {}", self.this());
        }

        // set magic bits
        self.magic = 0xCAFEFACE;

        // insert at head
        let next = free_list;

        free_list = self as *mut Block;
        self.next = next;

        if next != (0 as *mut Block) {
            (*next).prev = self.this() as *mut Block;
        }
    }

    // STATIC

    // Find a block that fits the bill
    unsafe fn find(size: usize, align: usize) -> Option<*mut Block> {
        let mut found = None;

        // loop through blocks until a good one is found
        let mut block_addr = free_list;

        while block_addr != (0 as *mut Block) {
            let block = &*block_addr;

            if Block::is_match(block, size, align) {
                found = Some(block_addr);
                break;
            }

            if (block_addr as usize) + block.size >= END {
                break;
            } else {
                block_addr = block.get_next();
            }
        }

        found
    }

    fn is_match(block: &Block, size: usize, align: usize) -> bool {
        // TODO: alignment

        block.size >= size
    }
}

// round up to the nearest multiple of 16
fn round_to_16(size: usize) -> usize {
    if (size & 0xF) == 0 { size } else { (size & !0xF) + 0x10 }
}

// Init the heap
pub fn init(start: usize, end: usize) {
    // Round to nearest multiple of 16
    unsafe {
        // round START up
        START = round_to_16(start);

        // round END down
        END = end & !0xF;

        // bounds check
        if END <= START {
            panic!("No heap space");
        }

        printf! ("In heap init\nstart addr: {:x}, end addr: {:x}, {} bytes\n",
                 START, END, END - START);
    }

    // create first block and free list
    unsafe {
        let first: &mut Block = &mut*(START as *mut Block);

        first.magic = 0xCAFEFACE;
        first.size = END - START;
        first.next = 0 as *mut Block;
        first.prev = 0 as *mut Block;
        first.set_footer(first.size);

        free_list = START as *mut Block;
    }

    print_stats();
}

/// Return a pointer to `size` bytes of memory aligned to `align`.
///
/// On failure, return a null pointer.
///
/// Behavior is undefined if the requested size is 0 or the alignment is not a
/// power of 2. The alignment must be no larger than the largest supported page
/// size on the platform.
pub unsafe fn malloc(size: usize, align: usize) -> *mut u8 {
    // TODO: alignment

    // get free block
    let block_addr = Block::find(size, align);

    match block_addr {
        None => {
            // check for out-of-memory; that is, empty free list
            if free_list == (0 as *mut Block) {
                panic!("Out of memory!");
            }

            // update stats
            FAIL_MALLOCS += 1;

            // return failure
            0 as *mut u8
        }
        Some(addr) => {
            let block: &mut Block = &mut*addr;

            // Split the block if it is too big
            if block.size > round_to_16(size + core::mem::size_of::<usize>()) {
                block.split(size);
            }

            // Remove the block from the free list
            block.remove();

            // update stats
            SUCC_MALLOCS += 1;

            // return ptr
            addr as *mut u8
        }
    }
}

/// Deallocates the memory referenced by `ptr`.
///
/// The `ptr` parameter must not be null.
///
/// The `old_size` and `align` parameters are the parameters that were used to
/// create the allocation referenced by `ptr`. The `old_size` parameter may be
/// any value in range_inclusive(requested_size, usable_size).
pub unsafe fn free(ptr: *mut u8, old_size: usize) {
    FREES += 1;
    //TODO
}

/// Returns the usable size of an allocation created with the specified the
/// `size` and `align`.
pub fn usable_size(size: usize, align: usize) -> usize {
    let usize_size = core::mem::size_of::<usize>();
    round_to_16(size + usize_size) - usize_size
}

/// Prints implementation-defined allocator statistics.
///
/// These statistics may be inconsistent if other threads use the allocator
/// during the call.
pub fn print_stats() {
    printf!("\nHeap stats\n----------\n");

    // Number of free blocks and amount of free memory
    let (num_free, size_free, size_used) = get_block_stats();
    printf!("{} free blocks; {} bytes free, {} bytes used\n",
            num_free, size_free, size_used);

    // Number of mallocs and frees
    unsafe {
        printf!("Successfull mallocs: {}; Failed mallocs: {}; Frees: {}\n\n",
                SUCC_MALLOCS, FAIL_MALLOCS, FREES);
    }
}

// Helper methods to compute stats
fn get_block_stats() -> (usize, usize, usize) {
    // counters
    let mut num_free = 0;
    let mut size_free = 0;

    // loop through all blocks
    unsafe{
        let mut block_addr = free_list as *mut Block;

        while block_addr != (0 as *mut Block) {
            let block = &*block_addr;

            num_free += 1;
            size_free += block.size;

            if block.next == (0 as *mut Block) {
                break;
            } else {
                block_addr = block.next;
            }
        }
    }

    (num_free, size_free, unsafe { END - START - size_free })
}
<commit_msg>Planning for locking, but not implementing yet<commit_after>#![allow(dead_code)]
// This file contains the memory allocator used by the rust_alloc module
//
// The implementation in this file is a simple first-fit allocator.
//
// Invariants:
// * All blocks will be a multiple of 16B
// * All blocks will be 16B-aligned

extern crate core;

use core::mem::{size_of};
use core::option::Option::{self, Some, None};

static mut START: usize = 0;
static mut END: usize = 0;

static mut free_list: *mut Block = (0 as *mut Block);

// heap stats
static mut SUCC_MALLOCS: usize = 0;
static mut FAIL_MALLOCS: usize = 0;
static mut FREES:        usize = 0;

// memory block
// the last word of every block is its allocation size
#[repr(C, packed)]
struct Block {
    magic: usize, // If this is a free block, then it is magical: 0xCAFEFACE
    size: usize, // Includes the size of the footer
    next: *mut Block,
    prev: *mut Block,
}

impl Block {
    // METHODS FOR ALL BLOCKS

    // returns true if this is a valid free block
    fn is_free(&self) -> bool {
        self.magic == 0xCAFEFACE && self.size % 0x10 == 0
    }

    // get the prev block in memory
    //
    // note: this is distinct from the prev block in the free list
    // behavior is undefined if this is the last block.
    unsafe fn get_prev(&self) -> *mut Block {
        // check for corner cases
        if self.this() == START {
            panic!("Free block has no previous block: {}", self.this());
        }

        // get the addr of previous block's size
        let prev_foot: *const usize = (self.this() - core::mem::size_of::<usize>()) as *const usize;
        let prev_size = *prev_foot;

        // get previous block's addr
        (self.this() - prev_size) as *mut Block
    }

    // get the next block in memory
    //
    // note: this is distinct from the next block in the free list
    // behavior is undefined if this is the last block.
    unsafe fn get_next(&self) -> *mut Block {
        // check for corner cases
        if self.this() + self.size == END {
            panic!("Free block has no next block: {}", self.this());
        }

        (self.this() + self.size) as *mut Block
    }

    // Set the footer for this block. This method does not error checking, so
    // be careful!
    unsafe fn set_footer(&self, size: usize) {
        let footer = (self.this() + self.size - core::mem::size_of::<usize>()) as *mut usize;
        *footer = size;
    }

    #[inline]
    unsafe fn this(&self) -> usize {
        self as *const Block as usize
    }

    // METHODS FOR ONLY FREE BLOCKS

    // split the block into two blocks. The first block will be of the
    // given size. The block must be free
    unsafe fn split(&mut self, size: usize) {
        // check that the math works out
        if !self.is_free() {
            panic!("Attempt to split non-free block 0x{:X}",
                   (self as *const Block) as usize);
        }
        if round_to_16(size + core::mem::size_of::<usize>()) >= self.size {
            panic!("Splitting block that is too small: 0x{:X}, size {}",
                   (self as *const Block) as usize, size);
        }

        // get new block addr
        let new_size = round_to_16(size + core::mem::size_of::<usize>());

        // create new block and set magic bits
        let block = (self.this() + new_size) as *mut Block;
        (*block).size = self.size - new_size;
        (*block).set_footer((*block).size);

        // adjust this block's metadata
        self.size = new_size;

        // insert at tail of free list
        (*block).insert();
    }

    // coalesce this block with the next one. The two blocks must be free
    unsafe fn combine(&mut self) {
        // check that both are free!
        if !self.is_free() {
            panic!("Attempt to coalesce non-free block 0x{:X}",
                   (self as *const Block) as usize);
        } else if !(*self.get_next()).is_free() {
            panic!("Attempt to coalesce non-free block 0x{:X}",
                   self.get_next() as *const Block as usize);
        }

        let next = self.get_next();

       // increase the size of this block
       self.size += (*next).size + core::mem::size_of::<usize>();

       // remove next block from free list
       (*next).remove();
    }

    // remove this block from the free list. The block
    // must be free. After this operation, the block's magic word is
    // set to 0xDEADBEEF.
    unsafe fn remove(&mut self) {
        if !self.is_free() {
            panic!("Attempt to remove non-free block from free list: {}", self.this());
        }

        // Set magic, so that this is definitely not a free block
        self.magic = 0xDEADBEEF;

        // get prev and next in free list
        // corner cases:
        // - if this is the head, prev = NULL
        // - if this is the tail, next = NULL

        if self.prev != (0 as *mut Block) {
            (*self.prev).next = self.next;
        } else {
            // remove the head of the list
            free_list = self.next;
        }

        if self.next != (0 as *mut Block) {
            // not the tail
            (*self.next).prev = self.prev;
        }
    }

    // METHODS FOR ONLY USED BLOCKS

    // inserts the given block at the head of the free list and sets
    // the magic bits. The block cannot already be free.
    unsafe fn insert(&mut self) {
        if self.is_free() {
            panic!("Attempt to insert a free block: {}", self.this());
        }

        // set magic bits
        self.magic = 0xCAFEFACE;

        // insert at head
        let next = free_list;

        free_list = self as *mut Block;
        self.next = next;

        if next != (0 as *mut Block) {
            (*next).prev = self.this() as *mut Block;
        }
    }

    // STATIC

    // Find a block that fits the bill
    unsafe fn find(size: usize, align: usize) -> Option<*mut Block> {
        let mut found = None;

        // loop through blocks until a good one is found
        let mut block_addr = free_list;

        while block_addr != (0 as *mut Block) {
            let block = &*block_addr;

            if Block::is_match(block, size, align) {
                found = Some(block_addr);
                break;
            }

            if (block_addr as usize) + block.size >= END {
                break;
            } else {
                block_addr = block.get_next();
            }
        }

        found
    }

    fn is_match(block: &Block, size: usize, align: usize) -> bool {
        // TODO: alignment

        block.size >= size
    }
}

// round up to the nearest multiple of 16
fn round_to_16(size: usize) -> usize {
    if (size & 0xF) == 0 { size } else { (size & !0xF) + 0x10 }
}

// Init the heap
pub fn init(start: usize, end: usize) {
    // Round to nearest multiple of 16
    unsafe {
        // round START up
        START = round_to_16(start);

        // round END down
        END = end & !0xF;

        // bounds check
        if END <= START {
            panic!("No heap space");
        }

        printf! ("In heap init\nstart addr: {:x}, end addr: {:x}, {} bytes\n",
                 START, END, END - START);
    }

    // create first block and free list
    unsafe {
        let first: &mut Block = &mut*(START as *mut Block);

        first.magic = 0xCAFEFACE;
        first.size = END - START;
        first.next = 0 as *mut Block;
        first.prev = 0 as *mut Block;
        first.set_footer(first.size);

        free_list = START as *mut Block;
    }

    print_stats();
}

/// Return a pointer to `size` bytes of memory aligned to `align`.
///
/// On failure, return a null pointer.
///
/// Behavior is undefined if the requested size is 0 or the alignment is not a
/// power of 2. The alignment must be no larger than the largest supported page
/// size on the platform.
pub unsafe fn malloc(size: usize, align: usize) -> *mut u8 {
    // TODO: alignment

    // TODO: lock here

    // get free block
    let block_addr = Block::find(size, align);

    match block_addr {
        None => {
            // check for out-of-memory; that is, empty free list
            if free_list == (0 as *mut Block) {
                panic!("Out of memory!");
            }

            // update stats
            FAIL_MALLOCS += 1;

            // TODO: unlock here

            // return failure
            0 as *mut u8
        }
        Some(addr) => {
            let block: &mut Block = &mut*addr;

            // Split the block if it is too big
            if block.size > round_to_16(size + core::mem::size_of::<usize>()) {
                block.split(size);
            }

            // Remove the block from the free list
            block.remove();

            // update stats
            SUCC_MALLOCS += 1;

            // TODO: unlock here

            // return ptr
            addr as *mut u8
        }
    }
}

/// Deallocates the memory referenced by `ptr`.
///
/// The `ptr` parameter must not be null.
///
/// The `old_size` and `align` parameters are the parameters that were used to
/// create the allocation referenced by `ptr`. The `old_size` parameter may be
/// any value in range_inclusive(requested_size, usable_size).
pub unsafe fn free(ptr: *mut u8, old_size: usize) {
    FREES += 1;
    //TODO
}

/// Returns the usable size of an allocation created with the specified the
/// `size` and `align`.
pub fn usable_size(size: usize, align: usize) -> usize {
    let usize_size = core::mem::size_of::<usize>();
    round_to_16(size + usize_size) - usize_size
}

/// Prints implementation-defined allocator statistics.
///
/// These statistics may be inconsistent if other threads use the allocator
/// during the call.
pub fn print_stats() {
    printf!("\nHeap stats\n----------\n");

    // Number of free blocks and amount of free memory
    let (num_free, size_free, size_used) = get_block_stats();
    printf!("{} free blocks; {} bytes free, {} bytes used\n",
            num_free, size_free, size_used);

    // Number of mallocs and frees
    unsafe {
        printf!("Successfull mallocs: {}; Failed mallocs: {}; Frees: {}\n\n",
                SUCC_MALLOCS, FAIL_MALLOCS, FREES);
    }
}

// Helper methods to compute stats
fn get_block_stats() -> (usize, usize, usize) {
    // counters
    let mut num_free = 0;
    let mut size_free = 0;

    // TODO: lock here

    // loop through all blocks
    unsafe{
        let mut block_addr = free_list as *mut Block;

        while block_addr != (0 as *mut Block) {
            let block = &*block_addr;

            num_free += 1;
            size_free += block.size;

            if block.next == (0 as *mut Block) {
                break;
            } else {
                block_addr = block.next;
            }
        }
    }

    // TODO: unlock here

    (num_free, size_free, unsafe { END - START - size_free })
}
<|endoftext|>


<commit_before>use crate::{
    error_holder::ErrorHolder,
    internal_rpc::InternalRPCHandle,
    options::{BasicAckOptions, BasicNackOptions, BasicRejectOptions},
    protocol::{AMQPError, AMQPSoftError},
    types::{ChannelId, DeliveryTag},
    Error, Promise, PromiseResolver, Result,
};

use std::sync::{
    atomic::{AtomicBool, Ordering},
    Arc,
};

#[derive(Default, Debug)]
pub struct Acker {
    channel_id: ChannelId,
    delivery_tag: DeliveryTag,
    internal_rpc: Option<InternalRPCHandle>,
    error: Option<ErrorHolder>,
    used: Arc<AtomicBool>,
}

impl Acker {
    pub(crate) fn new(
        channel_id: ChannelId,
        delivery_tag: DeliveryTag,
        internal_rpc: Option<InternalRPCHandle>,
        error: Option<ErrorHolder>,
    ) -> Self {
        Self {
            channel_id,
            delivery_tag,
            internal_rpc,
            error,
            used: Arc::default(),
        }
    }

    // FIXME: consume self and drop used
    pub async fn ack(&self, options: BasicAckOptions) -> Result<()> {
        self.rpc(|internal_rpc, resolver| {
            internal_rpc.basic_ack(
                self.channel_id,
                self.delivery_tag,
                options,
                resolver,
                self.error.clone(),
            )
        })
        .await
    }

    // FIXME: consume self and drop used
    pub async fn nack(&self, options: BasicNackOptions) -> Result<()> {
        self.rpc(|internal_rpc, resolver| {
            internal_rpc.basic_nack(
                self.channel_id,
                self.delivery_tag,
                options,
                resolver,
                self.error.clone(),
            )
        })
        .await
    }

    // FIXME: consume self and drop used
    pub async fn reject(&self, options: BasicRejectOptions) -> Result<()> {
        self.rpc(|internal_rpc, resolver| {
            internal_rpc.basic_reject(
                self.channel_id,
                self.delivery_tag,
                options,
                resolver,
                self.error.clone(),
            )
        })
        .await
    }

    async fn rpc<F: Fn(&InternalRPCHandle, PromiseResolver<()>)>(&self, f: F) -> Result<()> {
        if self.used.swap(true, Ordering::SeqCst) {
            return Err(Error::ProtocolError(AMQPError::new(
                AMQPSoftError::PRECONDITIONFAILED.into(),
                "Attempted to use an already used Acker".into(),
            )));
        }
        if let Some(error) = self.error.as_ref() {
            error.check()?;
        }
        if let Some(internal_rpc) = self.internal_rpc.as_ref() {
            let (promise, resolver) = Promise::new();
            f(internal_rpc, resolver);
            promise.await
        } else {
            Ok(())
        }
    }
}

impl PartialEq for Acker {
    fn eq(&self, other: &Acker) -> bool {
        self.channel_id == other.channel_id && self.delivery_tag == other.delivery_tag
    }
}
<commit_msg>drop now erraneous FIXMEs<commit_after>use crate::{
    error_holder::ErrorHolder,
    internal_rpc::InternalRPCHandle,
    options::{BasicAckOptions, BasicNackOptions, BasicRejectOptions},
    protocol::{AMQPError, AMQPSoftError},
    types::{ChannelId, DeliveryTag},
    Error, Promise, PromiseResolver, Result,
};

use std::sync::{
    atomic::{AtomicBool, Ordering},
    Arc,
};

#[derive(Default, Debug)]
pub struct Acker {
    channel_id: ChannelId,
    delivery_tag: DeliveryTag,
    internal_rpc: Option<InternalRPCHandle>,
    error: Option<ErrorHolder>,
    used: Arc<AtomicBool>,
}

impl Acker {
    pub(crate) fn new(
        channel_id: ChannelId,
        delivery_tag: DeliveryTag,
        internal_rpc: Option<InternalRPCHandle>,
        error: Option<ErrorHolder>,
    ) -> Self {
        Self {
            channel_id,
            delivery_tag,
            internal_rpc,
            error,
            used: Arc::default(),
        }
    }

    pub async fn ack(&self, options: BasicAckOptions) -> Result<()> {
        self.rpc(|internal_rpc, resolver| {
            internal_rpc.basic_ack(
                self.channel_id,
                self.delivery_tag,
                options,
                resolver,
                self.error.clone(),
            )
        })
        .await
    }

    pub async fn nack(&self, options: BasicNackOptions) -> Result<()> {
        self.rpc(|internal_rpc, resolver| {
            internal_rpc.basic_nack(
                self.channel_id,
                self.delivery_tag,
                options,
                resolver,
                self.error.clone(),
            )
        })
        .await
    }

    pub async fn reject(&self, options: BasicRejectOptions) -> Result<()> {
        self.rpc(|internal_rpc, resolver| {
            internal_rpc.basic_reject(
                self.channel_id,
                self.delivery_tag,
                options,
                resolver,
                self.error.clone(),
            )
        })
        .await
    }

    async fn rpc<F: Fn(&InternalRPCHandle, PromiseResolver<()>)>(&self, f: F) -> Result<()> {
        if self.used.swap(true, Ordering::SeqCst) {
            return Err(Error::ProtocolError(AMQPError::new(
                AMQPSoftError::PRECONDITIONFAILED.into(),
                "Attempted to use an already used Acker".into(),
            )));
        }
        if let Some(error) = self.error.as_ref() {
            error.check()?;
        }
        if let Some(internal_rpc) = self.internal_rpc.as_ref() {
            let (promise, resolver) = Promise::new();
            f(internal_rpc, resolver);
            promise.await
        } else {
            Ok(())
        }
    }
}

impl PartialEq for Acker {
    fn eq(&self, other: &Acker) -> bool {
        self.channel_id == other.channel_id && self.delivery_tag == other.delivery_tag
    }
}
<|endoftext|>


<commit_before>use std::{
	error,
	fmt,
	io,
};

use crate::ffi;

/// API Error
pub enum Error {
	/// If error code used some recognized name
	KnownError(ffi::DNSServiceError),
	/// Unrecognized error codes
	UnknownError(i32),
	/// IO error
	IoError(io::Error),
}

impl Error {
	/// Check if a raw error code represents an error, and convert it
	/// accordingly.  (Not all codes are treated as an error, including
	/// `0`).
	pub fn from(value: ffi::DNSServiceErrorType) -> Result<(), Error> {
		if ffi::DNSServiceNoError::try_from(value).is_some() {
			Ok(())
		} else {
			match ffi::DNSServiceError::try_from(value) {
				Some(e) => Err(Error::KnownError(e)),
				None => Err(Error::UnknownError(value)),
			}
		}
	}
}

impl From<io::Error> for Error {
	fn from(e: io::Error) -> Self {
		Self::IoError(e)
	}
}

impl From<Error> for io::Error {
	fn from(e: Error) -> Self {
		match e {
			Error::IoError(e) => e,
			e => io::Error::new(io::ErrorKind::Other, e)
		}
	}
}

impl fmt::Debug for Error {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			Self::KnownError(ffi_err) => write!(f, "known error {:?}: {}", ffi_err, ffi_err),
			Self::UnknownError(e) => write!(f, "unknown error code: {:?}", e),
			Self::IoError(e) => write!(f, "io error: {:?}", e),
		}
	}
}
impl fmt::Display for Error {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			Self::KnownError(ffi_err) => write!(f, "{}", ffi_err),
			Self::UnknownError(e) => write!(f, "unknown error code: {:?}", e),
			Self::IoError(e) => write!(f, "io error: {}", e),
		}
	}
}
impl error::Error for Error {
}

impl ffi::DNSServiceError {
	pub fn description(&self) -> &str {
		use ffi::DNSServiceError::*;
		match *self {
			Unknown => "unknown error",
			NoSuchName => "no such name",
			NoMemory => "out of memory",
			BadParam => "bad parameter",
			BadReference => "bad reference",
			BadState => "bad state",
			BadFlags => "bad flags",
			Unsupported => "not supported",
			NotInitialized => "not initialized",
			NoCache => "no cache",
			AlreadyRegistered => "already registered",
			NameConflict => "name conflict",
			Invalid => "invalid",
			Incompatible => "client library incompatible with daemon",
			BadInterfaceIndex => "bad interface index",
			Refused => "refused",
			NoSuchRecord => "no such record",
			NoAuth => "no auth",
			NoSuchKey => "no such key",
			NoValue => "no value",
			BufferTooSmall => "buffer too small",
		}
	}
}

impl fmt::Display for ffi::DNSServiceError {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "{}", self.description())
	}
}
impl error::Error for ffi::DNSServiceError {
	fn description(&self) -> &str {
		self.description()
	}
}

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

	#[test]
	#[allow(deprecated)]
	fn test_ffi_err_description() {
		// make sure Error::description still works, although we now provide a
		// (non-trait) description method
		assert_eq!(error::Error::description(&ffi::DNSServiceError::NoAuth), "no auth");
	}
}
<commit_msg>implement Error::source for our Error type<commit_after>use std::{
	error,
	fmt,
	io,
};

use crate::ffi;

/// API Error
pub enum Error {
	/// If error code used some recognized name
	KnownError(ffi::DNSServiceError),
	/// Unrecognized error codes
	UnknownError(i32),
	/// IO error
	IoError(io::Error),
}

impl Error {
	/// Check if a raw error code represents an error, and convert it
	/// accordingly.  (Not all codes are treated as an error, including
	/// `0`).
	pub fn from(value: ffi::DNSServiceErrorType) -> Result<(), Error> {
		if ffi::DNSServiceNoError::try_from(value).is_some() {
			Ok(())
		} else {
			match ffi::DNSServiceError::try_from(value) {
				Some(e) => Err(Error::KnownError(e)),
				None => Err(Error::UnknownError(value)),
			}
		}
	}
}

impl From<io::Error> for Error {
	fn from(e: io::Error) -> Self {
		Self::IoError(e)
	}
}

impl From<Error> for io::Error {
	fn from(e: Error) -> Self {
		match e {
			Error::IoError(e) => e,
			e => io::Error::new(io::ErrorKind::Other, e)
		}
	}
}

impl fmt::Debug for Error {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			Self::KnownError(ffi_err) => write!(f, "known error {:?}: {}", ffi_err, ffi_err),
			Self::UnknownError(e) => write!(f, "unknown error code: {:?}", e),
			Self::IoError(e) => write!(f, "io error: {:?}", e),
		}
	}
}
impl fmt::Display for Error {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			Self::KnownError(ffi_err) => write!(f, "{}", ffi_err),
			Self::UnknownError(e) => write!(f, "unknown error code: {:?}", e),
			Self::IoError(e) => write!(f, "io error: {}", e),
		}
	}
}
impl error::Error for Error {
	fn source(&self) -> Option<&(dyn error::Error + 'static)> {
		match self {
			Self::KnownError(ffi_err) => Some(ffi_err),
			Self::UnknownError(_) => None,
			Self::IoError(e) => Some(e),
		}
	}
}

impl ffi::DNSServiceError {
	pub fn description(&self) -> &str {
		use ffi::DNSServiceError::*;
		match *self {
			Unknown => "unknown error",
			NoSuchName => "no such name",
			NoMemory => "out of memory",
			BadParam => "bad parameter",
			BadReference => "bad reference",
			BadState => "bad state",
			BadFlags => "bad flags",
			Unsupported => "not supported",
			NotInitialized => "not initialized",
			NoCache => "no cache",
			AlreadyRegistered => "already registered",
			NameConflict => "name conflict",
			Invalid => "invalid",
			Incompatible => "client library incompatible with daemon",
			BadInterfaceIndex => "bad interface index",
			Refused => "refused",
			NoSuchRecord => "no such record",
			NoAuth => "no auth",
			NoSuchKey => "no such key",
			NoValue => "no value",
			BufferTooSmall => "buffer too small",
		}
	}
}

impl fmt::Display for ffi::DNSServiceError {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "{}", self.description())
	}
}
impl error::Error for ffi::DNSServiceError {
	fn description(&self) -> &str {
		self.description()
	}
}

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

	#[test]
	#[allow(deprecated)]
	fn test_ffi_err_description() {
		// make sure Error::description still works, although we now provide a
		// (non-trait) description method
		assert_eq!(error::Error::description(&ffi::DNSServiceError::NoAuth), "no auth");
	}
}
<|endoftext|>


<commit_before>use iron::{Middleware, Request, Response, Status, Continue, Url};
use collections::slice::ImmutableEqVector;

/// Exposes the original, unmodified path to be stored in an Alloy.
pub struct OriginalUrl(pub Url);

/// `Mount` is a simple mounting middleware.
///
/// `Mount` allows you to mount other middleware within a larger chain.
///
/// Mounted middleware will receive a modified version of the request
/// url with the mount pattern removed from the start of the url.
#[deriving(Clone)]
pub struct Mount<M> {
    route: String,
    matches: Vec<String>,
    middleware: M
}

impl<M: Middleware + Send> Mount<M> {
    /// Creates a new instance of `Mount` mounting the given instance of Iron
    /// on the given path.
    pub fn new(route: &str, middleware: M) -> Mount<M> {
        Mount {
            route: route.to_string(),
            middleware: middleware,
            matches: Path::new(route)
                        .str_components()
                        .map(|s| s.unwrap().to_string())
                        .collect()
        }
    }
}

impl<M: Middleware + Send> Middleware for Mount<M> {
    fn enter(&mut self,
             req: &mut Request,
             res: &mut Response) -> Status {
        // Check for a route match.
        if !req.url.path.as_slice().starts_with(self.matches.as_slice()) {
            return Continue;
        }

        // We have a match, so fire off the child middleware.
        // Insert the unmodified path into the alloy.
        match req.alloy.find::<OriginalUrl>() {
            Some(_) => (),
            None => req.alloy.insert(OriginalUrl(req.url.clone()))
        }

        // Remove the prefix from the request's path before passing it to the mounted middleware.
        // Preserve the rust-url invariant that the path list is non-empty ("" corresponds to /).
        req.url.path = match req.url.path.as_slice().slice_from(self.matches.len()) {
            [] => vec!["".to_string()],
            list => list.to_vec()
        };

        let terminator = self.middleware.enter(req, res);
        let _ = self.middleware.exit(req, res);

        // Reverse the URL munging, for future middleware.
        let &OriginalUrl(ref original) = req.alloy.find::<OriginalUrl>().unwrap();
        req.url = original.clone();

        // We dispatched the request, so return the terminator.
        terminator
    }
}

<commit_msg>(fix) Alloy -> Extension change.<commit_after>use iron::{Middleware, Request, Response, Status, Continue, Url};
use collections::slice::ImmutablePartialEqSlice;

/// Exposes the original, unmodified path to be stored in an Alloy.
pub struct OriginalUrl(pub Url);

/// `Mount` is a simple mounting middleware.
///
/// `Mount` allows you to mount other middleware within a larger chain.
///
/// Mounted middleware will receive a modified version of the request
/// url with the mount pattern removed from the start of the url.
#[deriving(Clone)]
pub struct Mount<M> {
    route: String,
    matches: Vec<String>,
    middleware: M
}

impl<M: Middleware + Send> Mount<M> {
    /// Creates a new instance of `Mount` mounting the given instance of Iron
    /// on the given path.
    pub fn new(route: &str, middleware: M) -> Mount<M> {
        Mount {
            route: route.to_string(),
            middleware: middleware,
            matches: Path::new(route)
                        .str_components()
                        .map(|s| s.unwrap().to_string())
                        .collect()
        }
    }
}

impl<M: Middleware + Send> Middleware for Mount<M> {
    fn enter(&mut self,
             req: &mut Request,
             res: &mut Response) -> Status {
        // Check for a route match.
        if !req.url.path.as_slice().starts_with(self.matches.as_slice()) {
            return Continue;
        }

        // We have a match, so fire off the child middleware.
        // Insert the unmodified path into the extensions.
        match req.extensions.find::<OriginalUrl>() {
            Some(_) => (),
            None => req.extensions.insert(OriginalUrl(req.url.clone()))
        }

        // Remove the prefix from the request's path before passing it to the mounted middleware.
        // Preserve the rust-url invariant that the path list is non-empty ("" corresponds to /).
        req.url.path = match req.url.path.as_slice().slice_from(self.matches.len()) {
            [] => vec!["".to_string()],
            list => list.to_vec()
        };

        let terminator = self.middleware.enter(req, res);
        let _ = self.middleware.exit(req, res);

        // Reverse the URL munging, for future middleware.
        req.url = match req.extensions.find::<OriginalUrl>().unwrap() {
            &OriginalUrl(ref original) => original.clone(),
        };

        // Remove the object from the extensions map to prevent leakage.
        req.extensions.remove::<OriginalUrl>();

        // We dispatched the request, so return the terminator.
        terminator
    }
}

<|endoftext|>


<commit_before>use slack_hook::{Attachment, AttachmentBuilder, Field, PayloadBuilder, Slack, SlackText, SlackLink};
use slack_hook::SlackTextContent::{Text, Link};

use Push;
use config::Config;
use taste::{BenchmarkResult, TastingResult};

pub struct SlackNotifier {
    conn: Slack,
    channel: String,
    verbose: bool,
}

impl SlackNotifier {
    pub fn new(hook_url: &str, channel: &str, _repo_url: &str, verbose: bool) -> SlackNotifier {
        SlackNotifier {
            conn: Slack::new(hook_url).unwrap(),
            channel: String::from(channel),
            verbose: verbose,
        }
    }

    pub fn notify(&self,
                  cfg: Option<&Config>,
                  res: &TastingResult,
                  push: &Push)
                  -> Result<(), String> {
        let mut text =
            vec![Text("I've tasted _".into()),
                 Text(format!("\"{}\"_ (", push.head_commit.msg.lines().next().unwrap()).into()),
                 Link(SlackLink::new(&push.head_commit.url,
                                     &format!("{}", push.head_commit.id)[0..6]))];
        match push.pusher {
            Some(ref p) => {
                let alias = match cfg {
                    Some(cfg) => {
                        match cfg.slack_aliases.get(p) {
                            None => p,
                            Some(a) => a,
                        }
                    }
                    None => p,
                };
                text.push(Text(format!("), pushed by @{}", alias).into()))
            }
            None => text.push(Text(format!(")").into())),
        }
        match res.branch {
            Some(ref b) => text.push(Text(format!("to *{}*", b).into())),
            None => (),
        }
        let payload = PayloadBuilder::new()
            .text(text.as_slice())
            .attachments(self.result_to_attachments(&res))
            .channel(self.channel.clone())
            .username("taster")
            .icon_emoji(":tea:")
            .build()
            .unwrap();

        match self.conn.send(&payload) {
            Ok(_) => Ok(()),
            Err(e) => Err(format!("{:?}", e)),
        }
    }

    fn result_to_attachments(&self, res: &TastingResult) -> Vec<Attachment> {
        let color = if !res.build || !res.test || !res.bench {
            "danger"
        } else {
            "good"
        };

        let taste = if !res.build || !res.bench {
            "was inedible"
        } else if !res.test {
            "had a mixed palate"
        } else {
            "tasted nice"
        };

        let check = |title: &str, result: bool| {
            let mut out = format!("{}: ", title);
            if result {
                out.push_str(":white_check_mark:");
            } else {
                out.push_str(":x:");
            }
            out
        };

        let mut attachments = Vec::new();
        let build_att = AttachmentBuilder::new("")
            .title(format!("It {}.", taste))
            .text(format!("{} {} {}",
                          check("Build", res.build),
                          check("Tests", res.test),
                          check("Benchmarks", res.bench)))
            .color(color)
            .build()
            .unwrap();
        attachments.push(build_att);

        let is_regression = |(_, v): (_, &BenchmarkResult<f64>)| match *v {
            BenchmarkResult::Regression(_, _) => true,
            _ => false,
        };
        let is_neutral = |(_, v): (_, &BenchmarkResult<f64>)| match *v {
            BenchmarkResult::Neutral(_, _) => true,
            _ => false,
        };

        match res.results {
            None => (),
            Some(ref r) => {
                for res in r {
                    let mut nv = res.iter()
                        .map(|(k, v)| {
                            let val = match *v {
                                BenchmarkResult::Improvement(ref s, ref p) => (s, p),
                                BenchmarkResult::Neutral(ref s, ref p) => (s, p),
                                BenchmarkResult::Regression(ref s, ref p) => (s, p),
                            };
                            let icon = if *val.1 > 0.1 {
                                ":chart_with_upwards_trend:"
                            } else if *val.1 < -0.1 {
                                ":chart_with_downwards_trend:"
                            } else {
                                ""
                            };
                            Field {
                                title: k.clone(),
                                value: SlackText::new(format!("{} {} ({:+.2}%)",
                                                              icon,
                                                              val.0,
                                                              val.1 * 100.0)),
                                short: Some(true),
                            }
                        })
                        .collect::<Vec<_>>();
                    nv.sort_by(|a, b| b.title.cmp(&a.title));

                    let col = if res.iter().all(&is_regression) {
                        // red
                        "danger"
                    } else if res.iter().any(&is_regression) {
                        // amber
                        "warning"
                    } else if res.iter().all(&is_neutral) {
                        // default, gray
                        ""
                    } else {
                        // green
                        "good"
                    };

                    if self.verbose || !res.iter().all(&is_neutral) {
                        let att = AttachmentBuilder::new("")
                            .color(col)
                            .fields(nv)
                            .build()
                            .unwrap();
                        attachments.push(att);
                    }
                }
            }
        }
        attachments
    }
}
<commit_msg>Link users and channels in Slack notifications<commit_after>use slack_hook::{Attachment, AttachmentBuilder, Field, PayloadBuilder, Slack, SlackText, SlackLink};
use slack_hook::SlackTextContent::{Text, Link};

use Push;
use config::Config;
use taste::{BenchmarkResult, TastingResult};

pub struct SlackNotifier {
    conn: Slack,
    channel: String,
    verbose: bool,
}

impl SlackNotifier {
    pub fn new(hook_url: &str, channel: &str, _repo_url: &str, verbose: bool) -> SlackNotifier {
        SlackNotifier {
            conn: Slack::new(hook_url).unwrap(),
            channel: String::from(channel),
            verbose: verbose,
        }
    }

    pub fn notify(&self,
                  cfg: Option<&Config>,
                  res: &TastingResult,
                  push: &Push)
                  -> Result<(), String> {
        let mut text =
            vec![Text("I've tasted _".into()),
                 Text(format!("\"{}\"_ (", push.head_commit.msg.lines().next().unwrap()).into()),
                 Link(SlackLink::new(&push.head_commit.url,
                                     &format!("{}", push.head_commit.id)[0..6]))];
        match push.pusher {
            Some(ref p) => {
                let alias = match cfg {
                    Some(cfg) => {
                        match cfg.slack_aliases.get(p) {
                            None => p,
                            Some(a) => a,
                        }
                    }
                    None => p,
                };
                text.push(Text(format!("), pushed by @{}", alias).into()))
            }
            None => text.push(Text(format!(")").into())),
        }
        match res.branch {
            Some(ref b) => text.push(Text(format!("to *{}*", b).into())),
            None => (),
        }
        let payload = PayloadBuilder::new()
            .text(text.as_slice())
            .attachments(self.result_to_attachments(&res))
            .channel(self.channel.clone())
            .username("taster")
            .icon_emoji(":tea:")
            .link_names(true)
            .build()
            .unwrap();

        match self.conn.send(&payload) {
            Ok(_) => Ok(()),
            Err(e) => Err(format!("{:?}", e)),
        }
    }

    fn result_to_attachments(&self, res: &TastingResult) -> Vec<Attachment> {
        let color = if !res.build || !res.test || !res.bench {
            "danger"
        } else {
            "good"
        };

        let taste = if !res.build || !res.bench {
            "was inedible"
        } else if !res.test {
            "had a mixed palate"
        } else {
            "tasted nice"
        };

        let check = |title: &str, result: bool| {
            let mut out = format!("{}: ", title);
            if result {
                out.push_str(":white_check_mark:");
            } else {
                out.push_str(":x:");
            }
            out
        };

        let mut attachments = Vec::new();
        let build_att = AttachmentBuilder::new("")
            .title(format!("It {}.", taste))
            .text(format!("{} {} {}",
                          check("Build", res.build),
                          check("Tests", res.test),
                          check("Benchmarks", res.bench)))
            .color(color)
            .build()
            .unwrap();
        attachments.push(build_att);

        let is_regression = |(_, v): (_, &BenchmarkResult<f64>)| match *v {
            BenchmarkResult::Regression(_, _) => true,
            _ => false,
        };
        let is_neutral = |(_, v): (_, &BenchmarkResult<f64>)| match *v {
            BenchmarkResult::Neutral(_, _) => true,
            _ => false,
        };

        match res.results {
            None => (),
            Some(ref r) => {
                for res in r {
                    let mut nv = res.iter()
                        .map(|(k, v)| {
                            let val = match *v {
                                BenchmarkResult::Improvement(ref s, ref p) => (s, p),
                                BenchmarkResult::Neutral(ref s, ref p) => (s, p),
                                BenchmarkResult::Regression(ref s, ref p) => (s, p),
                            };
                            let icon = if *val.1 > 0.1 {
                                ":chart_with_upwards_trend:"
                            } else if *val.1 < -0.1 {
                                ":chart_with_downwards_trend:"
                            } else {
                                ""
                            };
                            Field {
                                title: k.clone(),
                                value: SlackText::new(format!("{} {} ({:+.2}%)",
                                                              icon,
                                                              val.0,
                                                              val.1 * 100.0)),
                                short: Some(true),
                            }
                        })
                        .collect::<Vec<_>>();
                    nv.sort_by(|a, b| b.title.cmp(&a.title));

                    let col = if res.iter().all(&is_regression) {
                        // red
                        "danger"
                    } else if res.iter().any(&is_regression) {
                        // amber
                        "warning"
                    } else if res.iter().all(&is_neutral) {
                        // default, gray
                        ""
                    } else {
                        // green
                        "good"
                    };

                    if self.verbose || !res.iter().all(&is_neutral) {
                        let att = AttachmentBuilder::new("")
                            .color(col)
                            .fields(nv)
                            .build()
                            .unwrap();
                        attachments.push(att);
                    }
                }
            }
        }
        attachments
    }
}
<|endoftext|>


<commit_before>use std::fmt;
use std::f64;
use std::f64::consts::PI;

use radians::Radians;
use rand::{Rand, Rng};
use {Distance};

const MAX_SPEED: u8 = 25;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub(crate) enum SpeedLevel {
    Value(u8),
    Instant,
}

/// Represents the supported movement and rotation speeds
///
/// See [`Turtle::set_speed` method](struct.Turtle.html#method.set_speed) for more information.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Speed(SpeedLevel);

impl Speed {
    /// Converts a speed to its value as a movement speed in pixels per second
    pub fn to_movement(self) -> Distance {
        use self::SpeedLevel::*;
        match self.0 {
            Value(speed) => speed as f64 * 500.0,
            Instant => f64::INFINITY,
        }
    }

    /// Converts a speed to its value as radians per second
    pub(crate) fn to_rotation(self) -> Radians {
        use self::SpeedLevel::*;
        Radians::from_radians_value(match self.0 {
            Value(speed) => speed as f64 * (2.0*PI),
            Instant => f64::INFINITY,
        })
    }
}

impl fmt::Display for Speed {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::SpeedLevel::*;
        write!(f, "{}", match self.0 {
            Value(speed) => speed,
            Instant => 0,
        })
    }
}

impl Rand for Speed {
    fn rand<R: Rng>(rng: &mut R) -> Self {
        (rng.gen::<i32>() % MAX_SPEED as i32).into()
    }
}

impl<'a> From<&'a str> for Speed {
    fn from(s: &'a str) -> Self {
        use self::SpeedLevel::*;

        Speed(match s {
            "slowest" => Value(1),
            "slower" => Value(5),
            "slow" => Value(8),
            "normal" => Value(10),
            "fast" => Value(12),
            "faster" => Value(15),
            "instant" => Instant,
            _ => panic!("Invalid speed specified, use one of the words: 'slowest', 'slower', 'slow', 'normal', 'fast', 'faster', 'instant'"),
        })
    }
}

impl From<i32> for Speed {
    fn from(n: i32) -> Self {
        use self::SpeedLevel::*;

        Speed(match n {
            0 => Instant,
            n if n as u8 <= MAX_SPEED => Value(n as u8),
            n => panic!("Invalid speed: {}. Must be a value between 0 and {}", n, MAX_SPEED),
        })
    }
}

// We include this implementation because we use f64 in the rest of the library.
// It makes sense to implement this so that they don't get messed up if they accidentally use a
// floating point literal with set_speed.
impl From<f64> for Speed {
    fn from(n: f64) -> Self {
        (n.round() as i32).into()
    }
}

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

    #[test]
    fn speed_names() {
        let mut turtle = Turtle::new();
        turtle.set_speed("slowest");
        assert_eq!(turtle.speed(), Speed::One);
        turtle.set_speed("slow");
        assert_eq!(turtle.speed(), Speed::Three);
        turtle.set_speed("normal");
        assert_eq!(turtle.speed(), Speed::Six);
        turtle.set_speed("fast");
        assert_eq!(turtle.speed(), Speed::Eight);
        turtle.set_speed("fastest");
        assert_eq!(turtle.speed(), Speed::Ten);
        turtle.set_speed("instant");
        assert_eq!(turtle.speed(), Speed::Instant);
    }

    #[test]
    #[should_panic(expected = "Invalid speed specified, use one of the words: 'slowest', 'slow', 'normal', 'fast', 'fastest', 'instant'")]
    fn invalid_speed() {
        let mut turtle = Turtle::new();
        turtle.set_speed("wrong");
    }

    #[test]
    fn speed_values() {
        let mut turtle = Turtle::new();
        turtle.set_speed(0);
        assert_eq!(turtle.speed(), Speed::Instant);
        turtle.set_speed(1);
        assert_eq!(turtle.speed(), Speed::One);
        turtle.set_speed(2);
        assert_eq!(turtle.speed(), Speed::Two);
        turtle.set_speed(3);
        assert_eq!(turtle.speed(), Speed::Three);
        turtle.set_speed(4);
        assert_eq!(turtle.speed(), Speed::Four);
        turtle.set_speed(5);
        assert_eq!(turtle.speed(), Speed::Five);
        turtle.set_speed(6);
        assert_eq!(turtle.speed(), Speed::Six);
        turtle.set_speed(7);
        assert_eq!(turtle.speed(), Speed::Seven);
        turtle.set_speed(8);
        assert_eq!(turtle.speed(), Speed::Eight);
        turtle.set_speed(9);
        assert_eq!(turtle.speed(), Speed::Nine);
        turtle.set_speed(10);
        assert_eq!(turtle.speed(), Speed::Ten);
        turtle.set_speed(11);
        assert_eq!(turtle.speed(), Speed::Instant);
        turtle.set_speed(20394);
        assert_eq!(turtle.speed(), Speed::Instant);
    }

    #[test]
    fn speed_values_f64() {
        let mut turtle = Turtle::new();
        turtle.set_speed(0.4);
        assert_eq!(turtle.speed(), Speed::Instant);
        turtle.set_speed(1.4);
        assert_eq!(turtle.speed(), Speed::One);
        turtle.set_speed(2.4);
        assert_eq!(turtle.speed(), Speed::Two);
        turtle.set_speed(3.4);
        assert_eq!(turtle.speed(), Speed::Three);
        turtle.set_speed(4.4);
        assert_eq!(turtle.speed(), Speed::Four);
        turtle.set_speed(5.4);
        assert_eq!(turtle.speed(), Speed::Five);
        turtle.set_speed(6.4);
        assert_eq!(turtle.speed(), Speed::Six);
        turtle.set_speed(7.4);
        assert_eq!(turtle.speed(), Speed::Seven);
        turtle.set_speed(8.4);
        assert_eq!(turtle.speed(), Speed::Eight);
        turtle.set_speed(9.4);
        assert_eq!(turtle.speed(), Speed::Nine);
        turtle.set_speed(10.4);
        assert_eq!(turtle.speed(), Speed::Ten);
        turtle.set_speed(11.4);
        assert_eq!(turtle.speed(), Speed::Instant);
        turtle.set_speed(20394.4);
        assert_eq!(turtle.speed(), Speed::Instant);
    }
}
<commit_msg>Implementing most of the speed type and updating docs and tests in speed.rs<commit_after>use std::fmt;
use std::f64;
use std::f64::consts::PI;

use radians::Radians;
use rand::{Rand, Rng};
use {Distance};

const MAX_SPEED: i32 = 25;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub(crate) enum SpeedLevel {
    Value(i32),
    Instant,
}

/// Represents both the movement and rotation speed of the turtle.
///
/// You can create a `Speed` value by converting it from a number of different types. `Speed`
/// values can be compared for equality with `i32` values. This is a little more convenient than
/// converting the `i32` to `Speed` every time you want to make a comparison.
///
/// ```rust
/// # use turtle::Speed;
/// // This value is of type `Speed` and it is converted from an `i32`
/// let speed: Speed = 1.into();
/// // Speed values can be compared to integers
/// assert_eq!(speed, 1);
/// // This is equivalent to the following
/// assert_eq!(speed, Speed::from(1));
/// ```
///
/// There is no need to call `.into()` when passing a speed into the
/// [`Turtle::set_speed` method](struct.Turtle.html#method.set_speed).
///
/// ```rust
/// # use turtle::{Turtle};
/// let mut turtle = Turtle::new();
/// turtle.set_speed(22);
/// ```
///
/// See the [`Turtle::set_speed` method](struct.Turtle.html#method.set_speed) for more information.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Speed(SpeedLevel);

impl Speed {
    /// Returns the speed value that represents instantly moving and rotating with no further
    /// animation
    ///
    /// ```rust
    /// use turtle::{Speed};
    /// assert_eq!(Speed::instant(), 0);
    /// ```
    pub fn instant() -> Self {
        Speed(SpeedLevel::Instant)
    }

    /// Converts a speed to its value as a movement speed in pixels per second
    pub(crate) fn to_movement(self) -> Distance {
        use self::SpeedLevel::*;
        match self.0 {
            Value(speed) => speed as f64 * 500.0,
            Instant => f64::INFINITY,
        }
    }

    /// Converts a speed to its value as radians per second
    pub(crate) fn to_rotation(self) -> Radians {
        use self::SpeedLevel::*;
        Radians::from_radians_value(match self.0 {
            Value(speed) => speed as f64 * (2.0*PI),
            Instant => f64::INFINITY,
        })
    }
}

impl fmt::Display for Speed {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::SpeedLevel::*;
        write!(f, "{}", match self.0 {
            Value(speed) => speed,
            Instant => 0,
        })
    }
}

impl Rand for Speed {
    fn rand<R: Rng>(rng: &mut R) -> Self {
        (rng.gen::<i32>() % MAX_SPEED).into()
    }
}

impl<'a> From<&'a str> for Speed {
    fn from(s: &'a str) -> Self {
        use self::SpeedLevel::*;

        Speed(match s {
            "slowest" => Value(1),
            "slower" => Value(5),
            "slow" => Value(8),
            "normal" => Value(10),
            "fast" => Value(12),
            "faster" => Value(15),
            "instant" => Instant,
            _ => panic!("Invalid speed specified, use one of the words: 'slowest', 'slower', 'slow', 'normal', 'fast', 'faster', 'instant'"),
        })
    }
}

impl PartialEq<i32> for Speed {
    fn eq(&self, other: &i32) -> bool {
        self.eq(&Speed::from(*other))
    }
}

impl From<i32> for Speed {
    fn from(n: i32) -> Self {
        use self::SpeedLevel::*;

        Speed(match n {
            0 => Instant,
            n if n <= MAX_SPEED => Value(n),
            n => panic!("Invalid speed: {}. Must be a value between 0 and {}", n, MAX_SPEED),
        })
    }
}

// We include this implementation because we use f64 in the rest of the library.
// It makes sense to implement this so that they don't get messed up if they accidentally use a
// floating point literal with set_speed.
impl From<f64> for Speed {
    fn from(n: f64) -> Self {
        (n.round() as i32).into()
    }
}

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

    #[test]
    fn display() {
        for value in 0..MAX_SPEED {
            let speed: Speed = value.into();
            assert_eq!(format!("{}", speed), value.to_string());
        }
    }

    #[test]
    fn speed_strings() {
        let mut turtle = Turtle::new();
        turtle.set_speed("slowest");
        assert_eq!(turtle.speed(), 1);
        turtle.set_speed("slower");
        assert_eq!(turtle.speed(), 5);
        turtle.set_speed("slow");
        assert_eq!(turtle.speed(), 8);
        turtle.set_speed("normal");
        assert_eq!(turtle.speed(), 10);
        turtle.set_speed("fast");
        assert_eq!(turtle.speed(), 12);
        turtle.set_speed("faster");
        assert_eq!(turtle.speed(), 15);
        turtle.set_speed("instant");
        assert_eq!(turtle.speed(), 0);
    }

    #[test]
    #[should_panic(expected = "Invalid speed specified, use one of the words: 'slowest', 'slower', 'slow', 'normal', 'fast', 'faster', 'instant'")]
    fn invalid_speed() {
        let mut turtle = Turtle::new();
        turtle.set_speed("wrong");
    }

    #[test]
    fn speed_values() {
        let mut turtle = Turtle::new();
        for speed in 0..MAX_SPEED {
            turtle.set_speed(speed);
            assert_eq!(turtle.speed(), speed);
        }
    }

    #[test]
    #[should_panic(expected = "Invalid speed: 26. Must be a value between 0 and 25")]
    fn speed_value_out_of_range() {
        let mut turtle = Turtle::new();
        turtle.set_speed(26);
        assert_eq!(turtle.speed(), 0);
    }

    #[test]
    #[should_panic(expected = "Invalid speed: 20394. Must be a value between 0 and 25")]
    fn speed_value_out_of_range2() {
        let mut turtle = Turtle::new();
        turtle.set_speed(20394);
        assert_eq!(turtle.speed(), 0);
    }

    #[test]
    fn speed_values_f64() {
        let mut turtle = Turtle::new();
        for speed in 0..MAX_SPEED {
            turtle.set_speed(speed as f64 + 0.4);
            assert_eq!(turtle.speed(), speed);
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use scope::Scope;
use ast::{Expr, EvalResult};
use ast::EvalError::*;

pub struct State<'s> {
    default_ns: String,
    namespaces: HashMap<String, Scope<'s>>,
}

impl<'s> State<'s> {
    pub fn new(default: String) -> State<'s> {
        State {
            default_ns: default,
            namespaces: HashMap::new(),
        }
    }

    pub fn eval(&self, expr: &Expr) -> EvalResult {
        match try!(self.expand(expr)) {
            ref sym_expr @ Expr::Symbol { .. } => {
                self.eval_symbol(sym_expr)
            },
            ref def_expr @ Expr::Def { .. } => {
                self.eval_def(def_expr)
            },
            ref call_expr @ Expr::Call { .. } => {
                self.eval_call(call_expr)
            },
            ref let_expr @ Expr::Let { .. } => {
                self.eval_let(let_expr)
            },
            other_expr => {
                Ok(other_expr)
            },
        }
    }

    fn eval_symbol(&self, expr: &Expr) -> EvalResult {
        if let Expr::Symbol { ref ns, ref name , .. } = *expr {
            let ns_name = ns.as_ref().unwrap_or(&self.default_ns);
            match self.namespaces.get(ns_name) {
                Some(scope) => {
                    scope.get(name)
                         .map_or(Err(ResolveError(name.clone())), |e| Ok(e.clone()))
                },
                None => {
                    Err(ResolveError(ns_name.clone()))
                }
            }
        } else {
            Err(DispatchError(expr.clone()))
        }
    }

    fn eval_def(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn eval_call(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn eval_let(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand(&self, expr: &Expr) -> EvalResult {
        if let Expr::List(ref l) = *expr {
            if l.len() > 0 {
                if let Expr::Symbol { ref name, .. } = l[0] {
                    match &name[..] {
                        "def" => {
                            return self.expand_def(expr)
                        },
                        "fn" => {
                            return self.expand_fn(expr)
                        },
                        "macro" => {
                            return self.expand_macro(expr)
                        },
                        "quote" => {
                            return self.expand_quote(expr)
                        },
                        "unquote" => {
                            return self.expand_unquote(expr)
                        },
                        "unquote-splicing" => {
                            return self.expand_unquote_splicing(expr)
                        },
                        "let" => {
                            return self.expand_let(expr)
                        },
                        _ => {
                            return self.expand_call(expr)
                        }
                    }
                } else {
                    return Err(DispatchError(l[0].clone()))
                }
            }
        }
        Ok(expr.clone())
    }

    fn expand_def(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_fn(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_macro(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_quote(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_unquote(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_unquote_splicing(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_let(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_call(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

}
<commit_msg>Implement insert on state<commit_after>use std::collections::HashMap;
use scope::Scope;
use ast::{Expr, EvalResult};
use ast::EvalError::*;

pub struct State<'s> {
    default_ns: String,
    namespaces: HashMap<String, Scope<'s>>,
    parent: Option<&'s State<'s>>,
}

impl<'s> State<'s> {
    pub fn new(default: String) -> State<'s> {
        State {
            default_ns: default,
            namespaces: HashMap::new(),
            parent: None,
        }
    }

    pub fn insert(&mut self, ns: Option<String>, name: String, expr: Expr) -> Option<Expr> {
        if let Some(ns) = ns {
            if let Some(ns) = self.namespaces.get_mut(&ns) {
                return ns.insert(name, expr)
            }
        } else {
            if let Some(ns) = self.namespaces.get_mut(&self.default_ns) {
                return ns.insert(name, expr)
            }
        }
        None
    }

    pub fn eval(&self, expr: &Expr) -> EvalResult {
        match try!(self.expand(expr)) {
            ref sym_expr @ Expr::Symbol { .. } => {
                self.eval_symbol(sym_expr)
            },
            ref def_expr @ Expr::Def { .. } => {
                self.eval_def(def_expr)
            },
            ref call_expr @ Expr::Call { .. } => {
                self.eval_call(call_expr)
            },
            ref let_expr @ Expr::Let { .. } => {
                self.eval_let(let_expr)
            },
            other_expr => {
                Ok(other_expr)
            },
        }
    }

    fn eval_symbol(&self, expr: &Expr) -> EvalResult {
        if let Expr::Symbol { ref ns, ref name , .. } = *expr {
            let ns_name = ns.as_ref().unwrap_or(&self.default_ns);
            match self.namespaces.get(ns_name) {
                Some(scope) => {
                    scope.get(name)
                         .map_or(Err(ResolveError(name.clone())), |e| Ok(e.clone()))
                },
                None => {
                    Err(ResolveError(ns_name.clone()))
                }
            }
        } else {
            Err(DispatchError(expr.clone()))
        }
    }

    fn eval_def(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn eval_call(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn eval_let(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand(&self, expr: &Expr) -> EvalResult {
        if let Expr::List(ref l) = *expr {
            if l.len() > 0 {
                if let Expr::Symbol { ref name, .. } = l[0] {
                    match &name[..] {
                        "def" => {
                            return self.expand_def(expr)
                        },
                        "fn" => {
                            return self.expand_fn(expr)
                        },
                        "macro" => {
                            return self.expand_macro(expr)
                        },
                        "quote" => {
                            return self.expand_quote(expr)
                        },
                        "unquote" => {
                            return self.expand_unquote(expr)
                        },
                        "unquote-splicing" => {
                            return self.expand_unquote_splicing(expr)
                        },
                        "let" => {
                            return self.expand_let(expr)
                        },
                        _ => {
                            return self.expand_call(expr)
                        }
                    }
                } else {
                    return Err(DispatchError(l[0].clone()))
                }
            }
        }
        Ok(expr.clone())
    }

    fn expand_def(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_fn(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_macro(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_quote(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_unquote(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_unquote_splicing(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_let(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

    fn expand_call(&self, expr: &Expr) -> EvalResult {
        Ok(expr.clone())
    }

}
<|endoftext|>


<commit_before>//! Holds common search paths implementations
use super::config::data::PATHS_MAX;
use std::path::Path;

/// This struct holds the search paths, which can change before startup but not later
pub struct SearchPath {
    paths: Vec<String>,
}

impl SearchPath {
    /// Create a controlled SearchPath from a simple vector
    pub fn new(paths: &Vec<String>) -> Self {
        // 2) clean-up and straighten
        let cleaned_paths = clean_paths(&paths);
        // 3) and then cut-off unwanted parts (more paths input than PATHS_MAX)
        let paths = cleaned_paths
            .into_iter()
            .enumerate()
            .filter(|(i, _el)| i < &PATHS_MAX)
            .map(|(_i, el)| el)
            .collect();
        Self { paths }
    }
    pub fn update(&mut self, new_vec: Vec<String>) {
        let cleaned_new = clean_paths(&new_vec);
        self.paths = cleaned_new;
    }
    pub fn read(&self) -> Vec<String> {
        self.paths.clone()
    }
    pub fn len(&self) -> usize {
        self.paths.len()
    }
}

/// Clean paths checks that paths exists and that intersection
/// paths are exluded, also down-up-climbing of existing paths
/// hierarchy are working!
///
/// E.g. given:
///     "/home/user/Music/audiobooks/E-F"
///     "/home/user/Music/audiobooks/E-F/George Orwell"
///     "/home/user/Music/audiobooks/A-D/../../audiobooks/A-D"
///     "/home/user/Music/audiobooks/A-D"
///     "/home/user/Music/audiobooks/E-F/George Orwell/Animal Farm"
///
/// will lead to:
///     "/home/user/Music/audiobooks/E-F"
///     "/home/user/Music/audiobooks/A-D"
///
///
/// todo: implement vfs then write document_test
fn clean_paths(unchecked_paths: &Vec<String>) -> Vec<String> {
    let mut checked_paths: Vec<String> = vec![];
    for unchecked in unchecked_paths {
        if let Ok(path_ok) = Path::new(unchecked).canonicalize() {
            if path_ok.is_dir() {
                if let Some(path_str) = path_ok.to_str() {
                    let mut is_add_worthy = true;
                    let path_string = path_str.to_string();
                    for checked in checked_paths.iter_mut() {
                        // check all already checked path, if there is
                        // no reason to not add it, add it.
                        let path_len = path_str.len();
                        let checked_len = checked.len();
                        if path_len < checked_len {
                            // if substring matches checked must be exchanged
                            if checked[..path_len] == path_string {
                                *checked = path_string;
                                is_add_worthy = false;
                                break;
                            }
                        } else {
                            // only add if not substring with any
                            if path_string[..checked_len] == *checked {
                                //
                                is_add_worthy = false;
                                break;
                            }
                        }
                    }
                    // add it if there is no reason not to
                    if is_add_worthy {
                        checked_paths.push(path_str.to_string());
                    }
                } else {
                    warn!(
                        "Path {:?} has some encoding problem, and will not be included in search!",
                        unchecked
                    )
                }
            } else {
                warn!("Path {:?} does not exist as directory/folder, and will not be included in search!", unchecked)
            }
        } else {
            error!(
                "Path {:?} is not a valid directory/folder, and will not be included in search!",
                unchecked
            );
        }
    }
    checked_paths
}

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

    fn init() {
        let _ = env_logger::builder().is_test(true).try_init();
    }

    // todo: use crate vfs for better unit tests
    static HAS_FS_UP: bool = false;
    // run: mkdir -p "/tmp/adbf/Music/audiobooks/A-D"
    //      mkdir -p "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm"
    //      mkdir -p "/tmp/adbf/audible/George Orwell/Animal Farm"
    //      mkdir -p "/tmp/adbf/audible/Philip K. Dick/Electric Dreams"
    //      mkdir -p "/tmp/adbf"
    static _TEST_DATA: [&str; 6] = [
        "/tmp/adbf/Music/audiobooks/A-D",
        "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm",
        "/tmp/adbf/audible/George Orwell/Animal Farm",
        "/tmp/adbf/audible/..",
        "/tmp/adbf/audible/Philip K. Dick/Electric Dreams",
        "/tmp/adbf",
    ];

    #[test]
    fn test_clean_paths_overlap() {
        init();

        // 1 out
        let testing_path = vec![
            "/tmp/adbf/Music/audiobooks/A-D".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
            "/tmp/adbf/audible/George Orwell/Animal Farm".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F".to_string(),
        ];
        let return_value = clean_paths(&testing_path);
        if HAS_FS_UP {
            assert_eq!(
                return_value,
                vec![
                    "/tmp/adbf/Music/audiobooks/A-D".to_string(),
                    "/tmp/adbf/Music/audiobooks/E-F".to_string(),
                    "/tmp/adbf/audible/George Orwell/Animal Farm".to_string(),
                ]
            );
        }

        // 2 out
        let testing_path = vec![
            "/tmp/adbf/Music/audiobooks/A-D".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
            "/tmp/adbf/audible/George Orwell/Animal Farm".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F".to_string(),
            "/tmp/adbf/audible/George Orwell".to_string(),
        ];
        let return_value = clean_paths(&testing_path);

        if HAS_FS_UP {
            assert_eq!(
                return_value,
                vec![
                    "/tmp/adbf/Music/audiobooks/A-D".to_string(),
                    "/tmp/adbf/Music/audiobooks/E-F".to_string(),
                    "/tmp/adbf/audible/George Orwell".to_string(),
                ]
            );
        }
    }

    #[test]
    fn test_clean_paths_parent() {
        init();

        // ..
        let testing_path = vec![
            "/tmp/adbf/Music/audiobooks/A-D".to_string(),
            "/tmp/adbf/Music/audiobooks/../audiobooks/E-F/George Orwell/Animal Farm".to_string(),
            "/tmp/adbf/audible/George Orwell/Animal Farm".to_string(),
        ];
        let return_value = clean_paths(&testing_path);
        if HAS_FS_UP {
            assert_eq!(
                return_value,
                vec![
                    "/tmp/adbf/Music/audiobooks/A-D".to_string(),
                    "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
                    "/tmp/adbf/audible/George Orwell/Animal Farm".to_string(),
                ]
            );
        }

        // ..
        let testing_path = vec![
            "/tmp/adbf/Music/../../adbf/Music/audiobooks/A-D".to_string(),
            "/tmp/adbf/Music/audiobooks".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
        ];
        let return_value = clean_paths(&testing_path);
        if HAS_FS_UP {
            assert_eq!(
                return_value,
                vec!["/tmp/adbf/Music/audiobooks".to_string(),]
            );
        }

        // ..
        let testing_path = vec![
            "/tmp/adbf/Music/../../adbf/Music/audiobooks/A-D".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
        ];
        let return_value = clean_paths(&testing_path);
        if HAS_FS_UP {
            assert_eq!(
                return_value,
                vec![
                    "/tmp/adbf/Music/audiobooks/A-D".to_string(),
                    "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
                ]
            );
        }
    }
}
<commit_msg>[documentation] added usage for searchpath<commit_after>//! Holds common search paths implementations
use super::config::data::PATHS_MAX;
use std::path::Path;

/// This struct holds the search paths, which can change before startup but not later.
/// An Arc<SearchPath> shall be used in order to get current changes for all modules.
pub struct SearchPath {
    paths: Vec<String>,
}

impl SearchPath {
    /// Create a controlled SearchPath from a simple vector
    pub fn new(paths: &Vec<String>) -> Self {
        // 2) clean-up and straighten
        let cleaned_paths = clean_paths(&paths);
        // 3) and then cut-off unwanted parts (more paths input than PATHS_MAX)
        let paths = cleaned_paths
            .into_iter()
            .enumerate()
            .filter(|(i, _el)| i < &PATHS_MAX)
            .map(|(_i, el)| el)
            .collect();
        Self { paths }
    }
    pub fn update(&mut self, new_vec: Vec<String>) {
        let cleaned_new = clean_paths(&new_vec);
        self.paths = cleaned_new;
    }
    pub fn read(&self) -> Vec<String> {
        self.paths.clone()
    }
    pub fn len(&self) -> usize {
        self.paths.len()
    }
}

/// Clean paths checks that paths exists and that intersection
/// paths are exluded, also down-up-climbing of existing paths
/// hierarchy are working!
///
/// E.g. given:
///     "/home/user/Music/audiobooks/E-F"
///     "/home/user/Music/audiobooks/E-F/George Orwell"
///     "/home/user/Music/audiobooks/A-D/../../audiobooks/A-D"
///     "/home/user/Music/audiobooks/A-D"
///     "/home/user/Music/audiobooks/E-F/George Orwell/Animal Farm"
///
/// will lead to:
///     "/home/user/Music/audiobooks/E-F"
///     "/home/user/Music/audiobooks/A-D"
///
///
/// todo: implement vfs then write document_test
fn clean_paths(unchecked_paths: &Vec<String>) -> Vec<String> {
    let mut checked_paths: Vec<String> = vec![];
    for unchecked in unchecked_paths {
        if let Ok(path_ok) = Path::new(unchecked).canonicalize() {
            if path_ok.is_dir() {
                if let Some(path_str) = path_ok.to_str() {
                    let mut is_add_worthy = true;
                    let path_string = path_str.to_string();
                    for checked in checked_paths.iter_mut() {
                        // check all already checked path, if there is
                        // no reason to not add it, add it.
                        let path_len = path_str.len();
                        let checked_len = checked.len();
                        if path_len < checked_len {
                            // if substring matches checked must be exchanged
                            if checked[..path_len] == path_string {
                                *checked = path_string;
                                is_add_worthy = false;
                                break;
                            }
                        } else {
                            // only add if not substring with any
                            if path_string[..checked_len] == *checked {
                                //
                                is_add_worthy = false;
                                break;
                            }
                        }
                    }
                    // add it if there is no reason not to
                    if is_add_worthy {
                        checked_paths.push(path_str.to_string());
                    }
                } else {
                    warn!(
                        "Path {:?} has some encoding problem, and will not be included in search!",
                        unchecked
                    )
                }
            } else {
                warn!("Path {:?} does not exist as directory/folder, and will not be included in search!", unchecked)
            }
        } else {
            error!(
                "Path {:?} is not a valid directory/folder, and will not be included in search!",
                unchecked
            );
        }
    }
    checked_paths
}

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

    fn init() {
        let _ = env_logger::builder().is_test(true).try_init();
    }

    // todo: use crate vfs for better unit tests
    static HAS_FS_UP: bool = false;
    // run: mkdir -p "/tmp/adbf/Music/audiobooks/A-D"
    //      mkdir -p "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm"
    //      mkdir -p "/tmp/adbf/audible/George Orwell/Animal Farm"
    //      mkdir -p "/tmp/adbf/audible/Philip K. Dick/Electric Dreams"
    //      mkdir -p "/tmp/adbf"
    static _TEST_DATA: [&str; 6] = [
        "/tmp/adbf/Music/audiobooks/A-D",
        "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm",
        "/tmp/adbf/audible/George Orwell/Animal Farm",
        "/tmp/adbf/audible/..",
        "/tmp/adbf/audible/Philip K. Dick/Electric Dreams",
        "/tmp/adbf",
    ];

    #[test]
    fn test_clean_paths_overlap() {
        init();

        // 1 out
        let testing_path = vec![
            "/tmp/adbf/Music/audiobooks/A-D".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
            "/tmp/adbf/audible/George Orwell/Animal Farm".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F".to_string(),
        ];
        let return_value = clean_paths(&testing_path);
        if HAS_FS_UP {
            assert_eq!(
                return_value,
                vec![
                    "/tmp/adbf/Music/audiobooks/A-D".to_string(),
                    "/tmp/adbf/Music/audiobooks/E-F".to_string(),
                    "/tmp/adbf/audible/George Orwell/Animal Farm".to_string(),
                ]
            );
        }

        // 2 out
        let testing_path = vec![
            "/tmp/adbf/Music/audiobooks/A-D".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
            "/tmp/adbf/audible/George Orwell/Animal Farm".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F".to_string(),
            "/tmp/adbf/audible/George Orwell".to_string(),
        ];
        let return_value = clean_paths(&testing_path);

        if HAS_FS_UP {
            assert_eq!(
                return_value,
                vec![
                    "/tmp/adbf/Music/audiobooks/A-D".to_string(),
                    "/tmp/adbf/Music/audiobooks/E-F".to_string(),
                    "/tmp/adbf/audible/George Orwell".to_string(),
                ]
            );
        }
    }

    #[test]
    fn test_clean_paths_parent() {
        init();

        // ..
        let testing_path = vec![
            "/tmp/adbf/Music/audiobooks/A-D".to_string(),
            "/tmp/adbf/Music/audiobooks/../audiobooks/E-F/George Orwell/Animal Farm".to_string(),
            "/tmp/adbf/audible/George Orwell/Animal Farm".to_string(),
        ];
        let return_value = clean_paths(&testing_path);
        if HAS_FS_UP {
            assert_eq!(
                return_value,
                vec![
                    "/tmp/adbf/Music/audiobooks/A-D".to_string(),
                    "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
                    "/tmp/adbf/audible/George Orwell/Animal Farm".to_string(),
                ]
            );
        }

        // ..
        let testing_path = vec![
            "/tmp/adbf/Music/../../adbf/Music/audiobooks/A-D".to_string(),
            "/tmp/adbf/Music/audiobooks".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
        ];
        let return_value = clean_paths(&testing_path);
        if HAS_FS_UP {
            assert_eq!(
                return_value,
                vec!["/tmp/adbf/Music/audiobooks".to_string(),]
            );
        }

        // ..
        let testing_path = vec![
            "/tmp/adbf/Music/../../adbf/Music/audiobooks/A-D".to_string(),
            "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
        ];
        let return_value = clean_paths(&testing_path);
        if HAS_FS_UP {
            assert_eq!(
                return_value,
                vec![
                    "/tmp/adbf/Music/audiobooks/A-D".to_string(),
                    "/tmp/adbf/Music/audiobooks/E-F/George Orwell/Animal Farm".to_string(),
                ]
            );
        }
    }
}
<|endoftext|>


<commit_before>use Prefab;
use calx::{self, IntoPrefab};
use errors::*;
use form::Form;
use ron;
use std::collections::BTreeMap;
use std::fmt;
use std::io;
use terrain::Terrain;

pub fn save_prefab<W: io::Write>(output: &mut W, prefab: &Prefab) -> Result<()> {
    const ALPHABET: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\
                                    abcdefghijklmnopqrstuvwxyz\
                                    0123456789";

    let chars_f = move |x: &(Terrain, Vec<String>)| {
        let &(ref t, ref e) = x;
        if e.is_empty() {
            t.preferred_map_chars()
        } else {
            ""
        }
    };

    let mut legend_builder = calx::LegendBuilder::new(ALPHABET.to_string(), chars_f);

    let prefab: calx::Prefab<_> = prefab.clone().into_iter().map(|(p, e)| (p, legend_builder.add(&e))).collect();
    // Values are still results, we need to check if legend building failed.
    if legend_builder.out_of_alphabet {
        return Err("Unable to build legend, scene too complex?".into());
    }

    // Mustn't have non-errs in the build prefab unless out_of_alphabet was flipped.
    let prefab: calx::Prefab<_> = prefab.into_iter().map(|(p, e)| (p, e.unwrap())).collect();

    let map = format!("{}", String::from(prefab));
    let legend = legend_builder.legend;

    write!(output, "{}", MapSave { map, legend })?;
    Ok(())
}

pub fn load_prefab<I: io::Read>(input: &mut I) -> Result<Prefab> {
    let mut s = String::new();
    input.read_to_string(&mut s)?;
    let save: MapSave = ron::de::from_str(&s)?;

    // Validate the prefab
    for i in save.legend.values() {
        for e in &i.1 {
            if Form::named(e).is_none() {
                return Err(format!("Unknown entity spawn '{}'", e).into());
            }
        }
    }
    for c in save.map.chars() {
        if c.is_whitespace() {
            continue;
        }
        if !save.legend.contains_key(&c) {
            return Err(format!("Unknown map character '{}'", c).into());
        }
    }

    // Turn map into prefab.
    let (map, legend) = (save.map, save.legend);
    let prefab: calx::Prefab<char> = IntoPrefab::try_into(map)?;
    Ok(prefab.into_iter().map(|(p, item)| (p, legend[&item].clone())).collect())
}

#[derive(Debug, Serialize, Deserialize)]
struct MapSave {
    pub map: String,
    pub legend: BTreeMap<char, (Terrain, Vec<String>)>,
}

impl fmt::Display for MapSave {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Custom RON prettyprint that prints prettier than ron::ser::pretty
        writeln!(f, "(\n    map: \"")?;
        for line in self.map.lines() {
            writeln!(f, "{}", line.trim_right())?;
        }
        writeln!(f, "\",\n")?;
        writeln!(f, "    legend: {{")?;
        for (k, v) in &self.legend {
            writeln!(f, "        {:?}: ({:?}, {:?}),", k, v.0, v.1)?;
        }
        writeln!(f, "    }}")?;
        writeln!(f, ")")
    }
}
<commit_msg>Rustfmt<commit_after>use Prefab;
use calx::{self, IntoPrefab};
use errors::*;
use form::Form;
use ron;
use std::collections::BTreeMap;
use std::fmt;
use std::io;
use terrain::Terrain;

pub fn save_prefab<W: io::Write>(output: &mut W, prefab: &Prefab) -> Result<()> {
    const ALPHABET: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\
                                    abcdefghijklmnopqrstuvwxyz\
                                    0123456789";

    let chars_f = move |x: &(Terrain, Vec<String>)| {
        let &(ref t, ref e) = x;
        if e.is_empty() {
            t.preferred_map_chars()
        } else {
            ""
        }
    };

    let mut legend_builder = calx::LegendBuilder::new(ALPHABET.to_string(), chars_f);

    let prefab: calx::Prefab<_> = prefab
        .clone()
        .into_iter()
        .map(|(p, e)| (p, legend_builder.add(&e)))
        .collect();
    // Values are still results, we need to check if legend building failed.
    if legend_builder.out_of_alphabet {
        return Err("Unable to build legend, scene too complex?".into());
    }

    // Mustn't have non-errs in the build prefab unless out_of_alphabet was flipped.
    let prefab: calx::Prefab<_> = prefab.into_iter().map(|(p, e)| (p, e.unwrap())).collect();

    let map = format!("{}", String::from(prefab));
    let legend = legend_builder.legend;

    write!(output, "{}", MapSave { map, legend })?;
    Ok(())
}

pub fn load_prefab<I: io::Read>(input: &mut I) -> Result<Prefab> {
    let mut s = String::new();
    input.read_to_string(&mut s)?;
    let save: MapSave = ron::de::from_str(&s)?;

    // Validate the prefab
    for i in save.legend.values() {
        for e in &i.1 {
            if Form::named(e).is_none() {
                return Err(format!("Unknown entity spawn '{}'", e).into());
            }
        }
    }
    for c in save.map.chars() {
        if c.is_whitespace() {
            continue;
        }
        if !save.legend.contains_key(&c) {
            return Err(format!("Unknown map character '{}'", c).into());
        }
    }

    // Turn map into prefab.
    let (map, legend) = (save.map, save.legend);
    let prefab: calx::Prefab<char> = IntoPrefab::try_into(map)?;
    Ok(prefab
        .into_iter()
        .map(|(p, item)| (p, legend[&item].clone()))
        .collect())
}

#[derive(Debug, Serialize, Deserialize)]
struct MapSave {
    pub map: String,
    pub legend: BTreeMap<char, (Terrain, Vec<String>)>,
}

impl fmt::Display for MapSave {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Custom RON prettyprint that prints prettier than ron::ser::pretty
        writeln!(f, "(\n    map: \"")?;
        for line in self.map.lines() {
            writeln!(f, "{}", line.trim_right())?;
        }
        writeln!(f, "\",\n")?;
        writeln!(f, "    legend: {{")?;
        for (k, v) in &self.legend {
            writeln!(f, "        {:?}: ({:?}, {:?}),", k, v.0, v.1)?;
        }
        writeln!(f, "    }}")?;
        writeln!(f, ")")
    }
}
<|endoftext|>