<commit_before>// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.

use net_gen::ifreq;
use std::fs::File;
use std::io::{Error as IoError, Read, Result as IoResult, Write};
use std::os::raw::*;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use utils::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val};
use utils::{ioctl_expr, ioctl_ioc_nr, ioctl_iow_nr};

// As defined in the Linux UAPI:
// https://elixir.bootlin.com/linux/v4.17/source/include/uapi/linux/if.h#L33
const IFACE_NAME_MAX_LEN: usize = 16;

/// List of errors the tap implementation can throw.
#[derive(Debug)]
pub enum Error {
    /// Unable to create tap interface.
    CreateTap(IoError),
    /// Invalid interface name.
    InvalidIfname,
    /// ioctl failed.
    IoctlError(IoError),
    /// Couldn't open /dev/net/tun.
    OpenTun(IoError),
}

pub type Result<T> = ::std::result::Result<T, Error>;

const TUNTAP: ::std::os::raw::c_uint = 84;
ioctl_iow_nr!(TUNSETIFF, TUNTAP, 202, ::std::os::raw::c_int);
ioctl_iow_nr!(TUNSETOFFLOAD, TUNTAP, 208, ::std::os::raw::c_uint);
ioctl_iow_nr!(TUNSETVNETHDRSZ, TUNTAP, 216, ::std::os::raw::c_int);

/// Handle for a network tap interface.
///
/// For now, this simply wraps the file descriptor for the tap device so methods
/// can run ioctls on the interface. The tap interface fd will be closed when
/// Tap goes out of scope, and the kernel will clean up the interface automatically.
#[derive(Debug)]
pub struct Tap {
    tap_file: File,
    pub(crate) if_name: [u8; IFACE_NAME_MAX_LEN],
}

// Returns a byte vector representing the contents of a null terminated C string which
// contains if_name.
fn build_terminated_if_name(if_name: &str) -> Result<[u8; IFACE_NAME_MAX_LEN]> {
    // Convert the string slice to bytes, and shadow the variable,
    // since we no longer need the &str version.
    let if_name = if_name.as_bytes();

    if if_name.len() >= IFACE_NAME_MAX_LEN {
        return Err(Error::InvalidIfname);
    }

    let mut terminated_if_name = [b'\0'; IFACE_NAME_MAX_LEN];
    terminated_if_name[..if_name.len()].copy_from_slice(if_name);

    Ok(terminated_if_name)
}

pub struct IfReqBuilder(ifreq);

impl IfReqBuilder {
    pub fn new() -> Self {
        Self(Default::default())
    }

    pub fn if_name(mut self, if_name: &[u8; IFACE_NAME_MAX_LEN]) -> Self {
        // Since we don't call as_mut on the same union field more than once, this block is safe.
        let ifrn_name = unsafe { self.0.ifr_ifrn.ifrn_name.as_mut() };
        ifrn_name.copy_from_slice(if_name.as_ref());

        self
    }

    pub(crate) fn flags(mut self, flags: i16) -> Self {
        // Since we don't call as_mut on the same union field more than once, this block is safe.
        let ifru_flags = unsafe { self.0.ifr_ifru.ifru_flags.as_mut() };
        *ifru_flags = flags;

        self
    }

    pub(crate) fn execute<F: AsRawFd>(mut self, socket: &F, ioctl: u64) -> Result<ifreq> {
        // ioctl is safe. Called with a valid socket fd, and we check the return.
        let ret = unsafe { ioctl_with_mut_ref(socket, ioctl, &mut self.0) };
        if ret < 0 {
            return Err(Error::IoctlError(IoError::last_os_error()));
        }

        Ok(self.0)
    }
}

impl Tap {
    /// Create a TUN/TAP device given the interface name.
    /// # Arguments
    ///
    /// * `if_name` - the name of the interface.
    pub fn open_named(if_name: &str) -> Result<Tap> {
        let terminated_if_name = build_terminated_if_name(if_name)?;

        let fd = unsafe {
            // Open calls are safe because we give a constant null-terminated
            // string and verify the result.
            libc::open(
                b"/dev/net/tun\0".as_ptr() as *const c_char,
                libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC,
            )
        };
        if fd < 0 {
            return Err(Error::OpenTun(IoError::last_os_error()));
        }
        // We just checked that the fd is valid.
        let tuntap = unsafe { File::from_raw_fd(fd) };

        let ifreq = IfReqBuilder::new()
            .if_name(&terminated_if_name)
            .flags((net_gen::IFF_TAP | net_gen::IFF_NO_PI | net_gen::IFF_VNET_HDR) as i16)
            .execute(&tuntap, TUNSETIFF())?;

        // Safe since only the name is accessed, and it's cloned out.
        Ok(Tap {
            tap_file: tuntap,
            if_name: unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() },
        })
    }

    pub fn if_name_as_str(&self) -> &str {
        let len = self
            .if_name
            .iter()
            .position(|x| *x == 0)
            .unwrap_or(IFACE_NAME_MAX_LEN);
        std::str::from_utf8(&self.if_name[..len]).unwrap_or("")
    }

    /// Set the offload flags for the tap interface.
    pub fn set_offload(&self, flags: c_uint) -> Result<()> {
        // ioctl is safe. Called with a valid tap fd, and we check the return.
        let ret = unsafe { ioctl_with_val(&self.tap_file, TUNSETOFFLOAD(), c_ulong::from(flags)) };
        if ret < 0 {
            return Err(Error::IoctlError(IoError::last_os_error()));
        }

        Ok(())
    }

    /// Set the size of the vnet hdr.
    pub fn set_vnet_hdr_size(&self, size: c_int) -> Result<()> {
        // ioctl is safe. Called with a valid tap fd, and we check the return.
        let ret = unsafe { ioctl_with_ref(&self.tap_file, TUNSETVNETHDRSZ(), &size) };
        if ret < 0 {
            return Err(Error::IoctlError(IoError::last_os_error()));
        }

        Ok(())
    }
}

impl Read for Tap {
    fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
        self.tap_file.read(buf)
    }
}

impl Write for Tap {
    fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
        self.tap_file.write(&buf)
    }

    fn flush(&mut self) -> IoResult<()> {
        Ok(())
    }
}

impl AsRawFd for Tap {
    fn as_raw_fd(&self) -> RawFd {
        self.tap_file.as_raw_fd()
    }
}

#[cfg(test)]
pub mod tests {
    use std::os::unix::ffi::OsStrExt;

    use super::*;
    use crate::virtio::net::test_utils::{enable, if_index, TapTrafficSimulator};
    use net_gen::ETH_HLEN;

    // The size of the virtio net header
    const VNET_HDR_SIZE: usize = 10;

    const PAYLOAD_SIZE: usize = 512;
    const PACKET_SIZE: usize = 1024;

    #[test]
    fn test_tap_name() {
        // Sanity check that the assumed max iface name length is correct.
        assert_eq!(
            IFACE_NAME_MAX_LEN,
            net_gen::ifreq__bindgen_ty_1::default()
                .bindgen_union_field
                .len()
        );

        // Empty name - The tap should be named "tap0" by default
        let tap = Tap::open_named("").unwrap();
        assert_eq!(b"tap0\0\0\0\0\0\0\0\0\0\0\0\0", &tap.if_name);
        assert_eq!("tap0", tap.if_name_as_str());

        // 16 characters - too long.
        let name = "a123456789abcdef";
        match Tap::open_named(name) {
            Err(Error::InvalidIfname) => (),
            _ => panic!("Expected Error::InvalidIfname"),
        };

        // 15 characters - OK.
        let name = "a123456789abcde";
        let tap = Tap::open_named(name).unwrap();
        assert_eq!(&format!("{}\0", name).as_bytes(), &tap.if_name);
        assert_eq!(name, tap.if_name_as_str());
    }

    #[test]
    fn test_tap_exclusive_open() {
        let _tap1 = Tap::open_named("exclusivetap").unwrap();
        // Opening same tap device a second time should not be permitted.
        Tap::open_named("exclusivetap").unwrap_err();
    }

    #[test]
    fn test_set_options() {
        // This line will fail to provide an initialized FD if the test is not run as root.
        let tap = Tap::open_named("").unwrap();
        tap.set_vnet_hdr_size(16).unwrap();
        tap.set_offload(0).unwrap();

        let faulty_tap = Tap {
            tap_file: unsafe { File::from_raw_fd(-1) },
            if_name: [0x01; 16],
        };
        assert!(faulty_tap.set_vnet_hdr_size(16).is_err());
        assert!(faulty_tap.set_offload(0).is_err());
    }

    #[test]
    fn test_raw_fd() {
        let tap = Tap::open_named("").unwrap();
        assert_eq!(tap.as_raw_fd(), tap.tap_file.as_raw_fd());
    }

    #[test]
    fn test_read() {
        let mut tap = Tap::open_named("").unwrap();
        enable(&tap);
        let tap_traffic_simulator = TapTrafficSimulator::new(if_index(&tap));

        let packet = utils::rand::rand_alphanumerics(PAYLOAD_SIZE);
        tap_traffic_simulator.push_tx_packet(packet.as_bytes());

        let mut buf = [0u8; PACKET_SIZE];
        assert!(tap.read(&mut buf).is_ok());
        assert_eq!(
            &buf[VNET_HDR_SIZE..packet.len() + VNET_HDR_SIZE],
            packet.as_bytes()
        );
    }

    #[test]
    fn test_write() {
        let mut tap = Tap::open_named("").unwrap();
        enable(&tap);
        let tap_traffic_simulator = TapTrafficSimulator::new(if_index(&tap));

        let mut packet = [0u8; PACKET_SIZE];
        let payload = utils::rand::rand_alphanumerics(PAYLOAD_SIZE);
        packet[ETH_HLEN as usize..payload.len() + ETH_HLEN as usize]
            .copy_from_slice(payload.as_bytes());
        assert!(tap.write(&packet).is_ok());

        let mut read_buf = [0u8; PACKET_SIZE];
        assert!(tap_traffic_simulator.pop_rx_packet(&mut read_buf));
        assert_eq!(
            &read_buf[..PACKET_SIZE - VNET_HDR_SIZE],
            &packet[VNET_HDR_SIZE..]
        );
    }
}
<commit_msg>[tap] fix unit test<commit_after>// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.

use net_gen::ifreq;
use std::fs::File;
use std::io::{Error as IoError, Read, Result as IoResult, Write};
use std::os::raw::*;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use utils::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val};
use utils::{ioctl_expr, ioctl_ioc_nr, ioctl_iow_nr};

// As defined in the Linux UAPI:
// https://elixir.bootlin.com/linux/v4.17/source/include/uapi/linux/if.h#L33
const IFACE_NAME_MAX_LEN: usize = 16;

/// List of errors the tap implementation can throw.
#[derive(Debug)]
pub enum Error {
    /// Unable to create tap interface.
    CreateTap(IoError),
    /// Invalid interface name.
    InvalidIfname,
    /// ioctl failed.
    IoctlError(IoError),
    /// Couldn't open /dev/net/tun.
    OpenTun(IoError),
}

pub type Result<T> = ::std::result::Result<T, Error>;

const TUNTAP: ::std::os::raw::c_uint = 84;
ioctl_iow_nr!(TUNSETIFF, TUNTAP, 202, ::std::os::raw::c_int);
ioctl_iow_nr!(TUNSETOFFLOAD, TUNTAP, 208, ::std::os::raw::c_uint);
ioctl_iow_nr!(TUNSETVNETHDRSZ, TUNTAP, 216, ::std::os::raw::c_int);

/// Handle for a network tap interface.
///
/// For now, this simply wraps the file descriptor for the tap device so methods
/// can run ioctls on the interface. The tap interface fd will be closed when
/// Tap goes out of scope, and the kernel will clean up the interface automatically.
#[derive(Debug)]
pub struct Tap {
    tap_file: File,
    pub(crate) if_name: [u8; IFACE_NAME_MAX_LEN],
}

// Returns a byte vector representing the contents of a null terminated C string which
// contains if_name.
fn build_terminated_if_name(if_name: &str) -> Result<[u8; IFACE_NAME_MAX_LEN]> {
    // Convert the string slice to bytes, and shadow the variable,
    // since we no longer need the &str version.
    let if_name = if_name.as_bytes();

    if if_name.len() >= IFACE_NAME_MAX_LEN {
        return Err(Error::InvalidIfname);
    }

    let mut terminated_if_name = [b'\0'; IFACE_NAME_MAX_LEN];
    terminated_if_name[..if_name.len()].copy_from_slice(if_name);

    Ok(terminated_if_name)
}

pub struct IfReqBuilder(ifreq);

impl IfReqBuilder {
    pub fn new() -> Self {
        Self(Default::default())
    }

    pub fn if_name(mut self, if_name: &[u8; IFACE_NAME_MAX_LEN]) -> Self {
        // Since we don't call as_mut on the same union field more than once, this block is safe.
        let ifrn_name = unsafe { self.0.ifr_ifrn.ifrn_name.as_mut() };
        ifrn_name.copy_from_slice(if_name.as_ref());

        self
    }

    pub(crate) fn flags(mut self, flags: i16) -> Self {
        // Since we don't call as_mut on the same union field more than once, this block is safe.
        let ifru_flags = unsafe { self.0.ifr_ifru.ifru_flags.as_mut() };
        *ifru_flags = flags;

        self
    }

    pub(crate) fn execute<F: AsRawFd>(mut self, socket: &F, ioctl: u64) -> Result<ifreq> {
        // ioctl is safe. Called with a valid socket fd, and we check the return.
        let ret = unsafe { ioctl_with_mut_ref(socket, ioctl, &mut self.0) };
        if ret < 0 {
            return Err(Error::IoctlError(IoError::last_os_error()));
        }

        Ok(self.0)
    }
}

impl Tap {
    /// Create a TUN/TAP device given the interface name.
    /// # Arguments
    ///
    /// * `if_name` - the name of the interface.
    pub fn open_named(if_name: &str) -> Result<Tap> {
        let terminated_if_name = build_terminated_if_name(if_name)?;

        let fd = unsafe {
            // Open calls are safe because we give a constant null-terminated
            // string and verify the result.
            libc::open(
                b"/dev/net/tun\0".as_ptr() as *const c_char,
                libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC,
            )
        };
        if fd < 0 {
            return Err(Error::OpenTun(IoError::last_os_error()));
        }
        // We just checked that the fd is valid.
        let tuntap = unsafe { File::from_raw_fd(fd) };

        let ifreq = IfReqBuilder::new()
            .if_name(&terminated_if_name)
            .flags((net_gen::IFF_TAP | net_gen::IFF_NO_PI | net_gen::IFF_VNET_HDR) as i16)
            .execute(&tuntap, TUNSETIFF())?;

        // Safe since only the name is accessed, and it's cloned out.
        Ok(Tap {
            tap_file: tuntap,
            if_name: unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() },
        })
    }

    pub fn if_name_as_str(&self) -> &str {
        let len = self
            .if_name
            .iter()
            .position(|x| *x == 0)
            .unwrap_or(IFACE_NAME_MAX_LEN);
        std::str::from_utf8(&self.if_name[..len]).unwrap_or("")
    }

    /// Set the offload flags for the tap interface.
    pub fn set_offload(&self, flags: c_uint) -> Result<()> {
        // ioctl is safe. Called with a valid tap fd, and we check the return.
        let ret = unsafe { ioctl_with_val(&self.tap_file, TUNSETOFFLOAD(), c_ulong::from(flags)) };
        if ret < 0 {
            return Err(Error::IoctlError(IoError::last_os_error()));
        }

        Ok(())
    }

    /// Set the size of the vnet hdr.
    pub fn set_vnet_hdr_size(&self, size: c_int) -> Result<()> {
        // ioctl is safe. Called with a valid tap fd, and we check the return.
        let ret = unsafe { ioctl_with_ref(&self.tap_file, TUNSETVNETHDRSZ(), &size) };
        if ret < 0 {
            return Err(Error::IoctlError(IoError::last_os_error()));
        }

        Ok(())
    }
}

impl Read for Tap {
    fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
        self.tap_file.read(buf)
    }
}

impl Write for Tap {
    fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
        self.tap_file.write(&buf)
    }

    fn flush(&mut self) -> IoResult<()> {
        Ok(())
    }
}

impl AsRawFd for Tap {
    fn as_raw_fd(&self) -> RawFd {
        self.tap_file.as_raw_fd()
    }
}

#[cfg(test)]
pub mod tests {
    use std::os::unix::ffi::OsStrExt;

    use super::*;
    use crate::virtio::net::test_utils::{enable, if_index, TapTrafficSimulator};
    use net_gen::ETH_HLEN;

    // The size of the virtio net header
    const VNET_HDR_SIZE: usize = 10;

    const PAYLOAD_SIZE: usize = 512;
    const PACKET_SIZE: usize = 1024;

    #[test]
    fn test_tap_name() {
        // Sanity check that the assumed max iface name length is correct.
        assert_eq!(
            IFACE_NAME_MAX_LEN,
            net_gen::ifreq__bindgen_ty_1::default()
                .bindgen_union_field
                .len()
        );

        // Empty name - The tap should be named "tap0" by default
        let tap = Tap::open_named("").unwrap();
        assert_eq!(b"tap0\0\0\0\0\0\0\0\0\0\0\0\0", &tap.if_name);
        assert_eq!("tap0", tap.if_name_as_str());

        // 16 characters - too long.
        let name = "a123456789abcdef";
        match Tap::open_named(name) {
            Err(Error::InvalidIfname) => (),
            _ => panic!("Expected Error::InvalidIfname"),
        };

        // 15 characters - OK.
        let name = "a123456789abcde";
        let tap = Tap::open_named(name).unwrap();
        assert_eq!(&format!("{}\0", name).as_bytes(), &tap.if_name);
        assert_eq!(name, tap.if_name_as_str());
    }

    #[test]
    fn test_tap_exclusive_open() {
        let _tap1 = Tap::open_named("exclusivetap").unwrap();
        // Opening same tap device a second time should not be permitted.
        Tap::open_named("exclusivetap").unwrap_err();
    }

    #[test]
    fn test_set_options() {
        // This line will fail to provide an initialized FD if the test is not run as root.
        let tap = Tap::open_named("").unwrap();
        tap.set_vnet_hdr_size(16).unwrap();
        tap.set_offload(0).unwrap();

        let faulty_tap = Tap {
            tap_file: unsafe { File::from_raw_fd(-2) },
            if_name: [0x01; 16],
        };
        assert!(faulty_tap.set_vnet_hdr_size(16).is_err());
        assert!(faulty_tap.set_offload(0).is_err());
    }

    #[test]
    fn test_raw_fd() {
        let tap = Tap::open_named("").unwrap();
        assert_eq!(tap.as_raw_fd(), tap.tap_file.as_raw_fd());
    }

    #[test]
    fn test_read() {
        let mut tap = Tap::open_named("").unwrap();
        enable(&tap);
        let tap_traffic_simulator = TapTrafficSimulator::new(if_index(&tap));

        let packet = utils::rand::rand_alphanumerics(PAYLOAD_SIZE);
        tap_traffic_simulator.push_tx_packet(packet.as_bytes());

        let mut buf = [0u8; PACKET_SIZE];
        assert!(tap.read(&mut buf).is_ok());
        assert_eq!(
            &buf[VNET_HDR_SIZE..packet.len() + VNET_HDR_SIZE],
            packet.as_bytes()
        );
    }

    #[test]
    fn test_write() {
        let mut tap = Tap::open_named("").unwrap();
        enable(&tap);
        let tap_traffic_simulator = TapTrafficSimulator::new(if_index(&tap));

        let mut packet = [0u8; PACKET_SIZE];
        let payload = utils::rand::rand_alphanumerics(PAYLOAD_SIZE);
        packet[ETH_HLEN as usize..payload.len() + ETH_HLEN as usize]
            .copy_from_slice(payload.as_bytes());
        assert!(tap.write(&packet).is_ok());

        let mut read_buf = [0u8; PACKET_SIZE];
        assert!(tap_traffic_simulator.pop_rx_packet(&mut read_buf));
        assert_eq!(
            &read_buf[..PACKET_SIZE - VNET_HDR_SIZE],
            &packet[VNET_HDR_SIZE..]
        );
    }
}
<|endoftext|>


<commit_before>use client::Context;
use model::{
    channel::Message,
    Permissions
};
use std::{
    collections::HashMap,
    fmt,
    fmt::{Debug, Formatter},
    sync::Arc
};
use utils::Colour;
use super::{Args, Configuration, HelpBehaviour};

type CheckFunction = Fn(&mut Context, &Message, &mut Args, &CommandOptions) -> bool
                     + Send
                     + Sync
                     + 'static;

pub struct Check(pub(crate) Box<CheckFunction>);

impl Check {
    pub(crate) fn new<F: Send + Sync + 'static>(f: F) -> Self
        where F: Fn(&mut Context, &Message, &mut Args, &CommandOptions) -> bool
    {
        Check(Box::new(f))
    }
}

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

pub type HelpFunction = fn(&mut Context, &Message, &HelpOptions, HashMap<String, Arc<CommandGroup>>, &Args)
                   -> Result<(), Error>;

pub struct Help(pub HelpFunction, pub Arc<HelpOptions>);

impl Debug for Help {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.debug_struct("Help")
            .field("options", &self.1)
            .finish()
    }
}

impl HelpCommand for Help {
    fn execute(&self, c: &mut Context, m: &Message, ho: &HelpOptions,hm: HashMap<String, Arc<CommandGroup>>, a: &Args) -> Result<(), Error> {
        (self.0)(c, m, ho, hm, a)
    }
}

pub type BeforeHook = Fn(&mut Context, &Message, &str) -> bool + Send + Sync + 'static;
pub type AfterHook = Fn(&mut Context, &Message, &str, Result<(), Error>) + Send + Sync + 'static;
pub type UnrecognisedCommandHook = Fn(&mut Context, &Message, &str) + Send + Sync + 'static;
pub(crate) type InternalCommand = Arc<Command>;
pub type PrefixCheck = Fn(&mut Context, &Message) -> Option<String> + Send + Sync + 'static;

pub enum CommandOrAlias {
    Alias(String),
    Command(InternalCommand),
}

impl fmt::Debug for CommandOrAlias {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CommandOrAlias::Alias(ref s) => f.debug_tuple("CommandOrAlias::Alias").field(&s).finish(),
            CommandOrAlias::Command(ref arc) => f.debug_tuple("CommandOrAlias::Command").field(&arc.options()).finish(),
        }
    }
}

/// An error from a command.
#[derive(Clone, Debug)]
pub struct Error(pub String);

// TODO: Have seperate `From<(&)String>` and `From<&str>` impls via specialization
impl<D: fmt::Display> From<D> for Error {
    fn from(d: D) -> Self {
        Error(d.to_string())
    }
}

#[derive(Debug)]
pub struct CommandGroup {
    pub prefix: Option<String>,
    pub commands: HashMap<String, CommandOrAlias>,
    /// Some fields taken from Command
    pub bucket: Option<String>,
    pub required_permissions: Permissions,
    pub allowed_roles: Vec<String>,
    pub help_available: bool,
    pub dm_only: bool,
    pub guild_only: bool,
    pub owners_only: bool,
    pub help: Option<Arc<Help>>,
}

impl Default for CommandGroup {
    fn default() -> CommandGroup {
        CommandGroup {
            prefix: None,
            commands: HashMap::new(),
            bucket: None,
            required_permissions: Permissions::empty(),
            dm_only: false,
            guild_only: false,
            help_available: true,
            owners_only: false,
            allowed_roles: Vec::new(),
            help: None,
        }
    }
}

#[derive(Debug)]
pub struct CommandOptions {
    /// A set of checks to be called prior to executing the command. The checks
    /// will short-circuit on the first check that returns `false`.
    pub checks: Vec<Check>,
    /// Ratelimit bucket.
    pub bucket: Option<String>,
    /// Command description, used by other commands.
    pub desc: Option<String>,
    /// Example arguments, used by other commands.
    pub example: Option<String>,
    /// Command usage schema, used by other commands.
    pub usage: Option<String>,
    /// Minumum amount of arguments that should be passed.
    pub min_args: Option<i32>,
    /// Maximum amount of arguments that can be passed.
    pub max_args: Option<i32>,
    /// Permissions required to use this command.
    pub required_permissions: Permissions,
    /// Roles allowed to use this command.
    pub allowed_roles: Vec<String>,
    /// Whether command should be displayed in help list or not, used by other commands.
    pub help_available: bool,
    /// Whether command can be used only privately or not.
    pub dm_only: bool,
    /// Whether command can be used only in guilds or not.
    pub guild_only: bool,
    /// Whether command can only be used by owners or not.
    pub owners_only: bool,
    /// Other names that can be used to call this command instead.
    pub aliases: Vec<String>,
}

#[derive(Debug)]
pub struct HelpOptions {
    /// Suggests a command's name.
    pub suggestion_text: String,
    /// If no help is available, this text will be displayed.
    pub no_help_available_text: String,
    /// How to use a command, `{usage_label}: {command_name} {args}`
    pub usage_label: String,
    /// Actual sample label, `{usage_sample_label}: {command_name} {args}`
    pub usage_sample_label: String,
    /// Text labeling ungrouped commands, `{ungrouped_label}: ...`
    pub ungrouped_label: String,
    /// Text labeling the start of the description.
    pub description_label: String,
    /// Text labeling grouped commands, `{grouped_label} {group_name}: ...`
    pub grouped_label: String,
    /// Text labeling a command's alternative names (aliases).
    pub aliases_label: String,
    /// Text specifying that a command is only usable in a guild.
    pub guild_only_text: String,
    /// Text specifying that a command is only usable in via DM.
    pub dm_only_text: String,
    /// Text specifying that a command can be used via DM and in guilds.
    pub dm_and_guild_text: String,
    /// Text expressing that a command is available.
    pub available_text: String,
    /// Error-message once a command could not be found.
    /// Output-example (without whitespace between both substitutions: `{command_not_found_text}{command_name}`
    /// `{command_name}` describes user's input as in: `{prefix}help {command_name}`.
    pub command_not_found_text: String,
    /// Explains the user on how to use access a single command's details.
    pub individual_command_tip: String,
    /// Explains reasoning behind strikethrough-commands, see fields requiring `HelpBehaviour` for further information.
    /// If `HelpBehaviour::Strike` is unused, this field will evaluate to `None` during creation
    /// inside of `CreateHelpCommand`.
    ///
    /// **Note**: Text is only used in direct messages.
    pub striked_commands_tip_in_dm: Option<String>,
    /// Explains reasoning behind strikethrough-commands, see fields requiring `HelpBehaviour` for further information.
    /// If `HelpBehaviour::Strike` is unused, this field will evaluate to `None` during creation
    /// inside of `CreateHelpCommand`.
    ///
    /// **Note**: Text is only used in guilds.
    pub striked_commands_tip_in_guild: Option<String>,
    /// Announcing a group's prefix as in: {group_prefix} {prefix}.
    pub group_prefix: String,
    /// If a user lacks required roles, this will treat how these commands will be displayed.
    pub lacking_role: HelpBehaviour,
    /// If a user lacks permissions, this will treat how these commands will be displayed.
    pub lacking_permissions: HelpBehaviour,
    /// If a user is using the help-command in a channel where a command is not available,
    /// this behaviour will be executed.
    pub wrong_channel: HelpBehaviour,
    /// Colour help-embed will use upon encountering an error.
    pub embed_error_colour: Colour,
    /// Colour help-embed will use if no error occured.
    pub embed_success_colour: Colour,
}

pub trait HelpCommand: Send + Sync + 'static {
    fn execute(&self, &mut Context, &Message, &HelpOptions, HashMap<String, Arc<CommandGroup>>, &Args) -> Result<(), Error>;

    fn options(&self) -> Arc<CommandOptions> {
        Arc::clone(&DEFAULT_OPTIONS)
    }
}

impl HelpCommand for Arc<HelpCommand> {
    fn execute(&self, c: &mut Context, m: &Message, ho: &HelpOptions, hm: HashMap<String, Arc<CommandGroup>>, a: &Args) -> Result<(), Error> {
        (**self).execute(c, m, ho, hm, a)
    }
}

impl Default for HelpOptions {
    fn default() -> HelpOptions {
        HelpOptions {
            suggestion_text: "Did you mean {}?".to_string(),
            no_help_available_text: "**Error**: No help available.".to_string(),
            usage_label: "Usage".to_string(),
            usage_sample_label: "Sample usage".to_string(),
            ungrouped_label: "Ungrouped".to_string(),
            grouped_label: "Group".to_string(),
            aliases_label: "Aliases".to_string(),
            description_label: "Description".to_string(),
            guild_only_text: "Only in guilds".to_string(),
            dm_only_text: "Only in DM".to_string(),
            dm_and_guild_text: "In DM and guilds".to_string(),
            available_text: "Available".to_string(),
            command_not_found_text: "**Error**: Command `{}` not found.".to_string(),
            individual_command_tip: "To get help with an individual command, pass its \
                 name as an argument to this command.".to_string(),
            group_prefix: "Prefix".to_string(),
            striked_commands_tip_in_dm: Some(String::new()),
            striked_commands_tip_in_guild: Some(String::new()),
            lacking_role: HelpBehaviour::Strike,
            lacking_permissions: HelpBehaviour::Strike,
            wrong_channel: HelpBehaviour::Strike,
            embed_error_colour: Colour::dark_red(),
            embed_success_colour: Colour::rosewater(),
        }
    }
}

lazy_static! {
    static ref DEFAULT_OPTIONS: Arc<CommandOptions> = Arc::new(CommandOptions::default());
}

/// A framework command.
pub trait Command: Send + Sync + 'static {
    fn execute(&self, &mut Context, &Message, Args) -> Result<(), Error>;

    fn options(&self) -> Arc<CommandOptions> {
        Arc::clone(&DEFAULT_OPTIONS)
    }

    /// Called when the command gets registered.
    fn init(&self) {}

    /// "before" middleware. Is called alongside the global middleware in the framework.
    fn before(&self, &mut Context, &Message) -> bool { true }

    /// "after" middleware. Is called alongside the global middleware in the framework.
    fn after(&self, &mut Context, &Message, &Result<(), Error>) { }
}

impl Command for Arc<Command> {
    fn execute(&self, c: &mut Context, m: &Message, a: Args) -> Result<(), Error> {
        (**self).execute(c, m, a)
    }

    fn options(&self) -> Arc<CommandOptions> {
        (**self).options()
    }

    fn init(&self) {
        (**self).init()
    }

    fn before(&self, c: &mut Context, m: &Message) -> bool {
        (**self).before(c, m)
    }

    fn after(&self, c: &mut Context, m: &Message, res: &Result<(), Error>) {
        (**self).after(c, m, res)
    }
}

impl<F> Command for F where F: Fn(&mut Context, &Message, Args) -> Result<(), Error>
    + Send
    + Sync
    + ?Sized
    + 'static {
    fn execute(&self, c: &mut Context, m: &Message, a: Args) -> Result<(), Error> {
        (*self)(c, m, a)
    }
}

impl Default for CommandOptions {
    fn default() -> CommandOptions {
        CommandOptions {
            aliases: Vec::new(),
            checks: Vec::default(),
            desc: None,
            usage: None,
            example: None,
            min_args: None,
            bucket: None,
            max_args: None,
            required_permissions: Permissions::empty(),
            dm_only: false,
            guild_only: false,
            help_available: true,
            owners_only: false,
            allowed_roles: Vec::new(),
        }
    }
}

pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Option<Vec<usize>> {
    if !conf.prefixes.is_empty() || conf.dynamic_prefix.is_some() {
        // Find out if they were mentioned. If not, determine if the prefix
        // was used. If not, return None.
        let mut positions: Vec<usize> = vec![];

        if let Some(mention_end) = find_mention_end(&msg.content, conf) {
            positions.push(mention_end);
            return Some(positions);
        }

        // Dynamic prefixes, if present and suitable, always have a higher priority.
        if let Some(x) = conf.dynamic_prefix.as_ref().and_then(|f| f(ctx, msg)) {
            if msg.content.starts_with(&x) {
                positions.push(x.chars().count());
            }
        } else {
            for n in &conf.prefixes {
                if msg.content.starts_with(n) {
                    positions.push(n.chars().count());
                }
            }
        }

        if positions.is_empty() {
            return None;
        }

        let pos = *unsafe { positions.get_unchecked(0) };

        if conf.allow_whitespace {
            positions.insert(0, find_end_of_prefix_with_whitespace(&msg.content, pos).unwrap_or(pos));
        } else if find_end_of_prefix_with_whitespace(&msg.content, pos).is_some() {
            return None;
        }

        Some(positions)
    } else if conf.on_mention.is_some() {
        find_mention_end(&msg.content, conf).map(|mention_end| {
            vec![mention_end] // This can simply be returned without trying to find the end whitespaces as trim will remove it later
        })
    } else {
        None
    }
}

fn find_mention_end(content: &str, conf: &Configuration) -> Option<usize> {
    conf.on_mention.as_ref().and_then(|mentions| {
        mentions
            .iter()
            .find(|mention| content.starts_with(&mention[..]))
            .map(|m| m.len())
    })
}

// Finds the end of the first continuous block of whitespace after the prefix
fn find_end_of_prefix_with_whitespace(content: &str, position: usize) -> Option<usize> {
    let content_len = content.len();
    if position >= content_len { return None; }

    let mut i = 0;
    let chars = content.chars().skip(position);
    for char in chars {
        match char {
            // \t \n \r [space]
            '\t' | '\n' | '\r' | ' ' => i += 1,
            _ => return if i == 0 { None } else { Some(position + i) }
        }
    }
    Some(content.len())
}
<commit_msg>And another (function call) repetion<commit_after>use client::Context;
use model::{
    channel::Message,
    Permissions
};
use std::{
    collections::HashMap,
    fmt,
    fmt::{Debug, Formatter},
    sync::Arc
};
use utils::Colour;
use super::{Args, Configuration, HelpBehaviour};

type CheckFunction = Fn(&mut Context, &Message, &mut Args, &CommandOptions) -> bool
                     + Send
                     + Sync
                     + 'static;

pub struct Check(pub(crate) Box<CheckFunction>);

impl Check {
    pub(crate) fn new<F: Send + Sync + 'static>(f: F) -> Self
        where F: Fn(&mut Context, &Message, &mut Args, &CommandOptions) -> bool
    {
        Check(Box::new(f))
    }
}

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

pub type HelpFunction = fn(&mut Context, &Message, &HelpOptions, HashMap<String, Arc<CommandGroup>>, &Args)
                   -> Result<(), Error>;

pub struct Help(pub HelpFunction, pub Arc<HelpOptions>);

impl Debug for Help {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.debug_struct("Help")
            .field("options", &self.1)
            .finish()
    }
}

impl HelpCommand for Help {
    fn execute(&self, c: &mut Context, m: &Message, ho: &HelpOptions,hm: HashMap<String, Arc<CommandGroup>>, a: &Args) -> Result<(), Error> {
        (self.0)(c, m, ho, hm, a)
    }
}

pub type BeforeHook = Fn(&mut Context, &Message, &str) -> bool + Send + Sync + 'static;
pub type AfterHook = Fn(&mut Context, &Message, &str, Result<(), Error>) + Send + Sync + 'static;
pub type UnrecognisedCommandHook = Fn(&mut Context, &Message, &str) + Send + Sync + 'static;
pub(crate) type InternalCommand = Arc<Command>;
pub type PrefixCheck = Fn(&mut Context, &Message) -> Option<String> + Send + Sync + 'static;

pub enum CommandOrAlias {
    Alias(String),
    Command(InternalCommand),
}

impl fmt::Debug for CommandOrAlias {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CommandOrAlias::Alias(ref s) => f.debug_tuple("CommandOrAlias::Alias").field(&s).finish(),
            CommandOrAlias::Command(ref arc) => f.debug_tuple("CommandOrAlias::Command").field(&arc.options()).finish(),
        }
    }
}

/// An error from a command.
#[derive(Clone, Debug)]
pub struct Error(pub String);

// TODO: Have seperate `From<(&)String>` and `From<&str>` impls via specialization
impl<D: fmt::Display> From<D> for Error {
    fn from(d: D) -> Self {
        Error(d.to_string())
    }
}

#[derive(Debug)]
pub struct CommandGroup {
    pub prefix: Option<String>,
    pub commands: HashMap<String, CommandOrAlias>,
    /// Some fields taken from Command
    pub bucket: Option<String>,
    pub required_permissions: Permissions,
    pub allowed_roles: Vec<String>,
    pub help_available: bool,
    pub dm_only: bool,
    pub guild_only: bool,
    pub owners_only: bool,
    pub help: Option<Arc<Help>>,
}

impl Default for CommandGroup {
    fn default() -> CommandGroup {
        CommandGroup {
            prefix: None,
            commands: HashMap::new(),
            bucket: None,
            required_permissions: Permissions::empty(),
            dm_only: false,
            guild_only: false,
            help_available: true,
            owners_only: false,
            allowed_roles: Vec::new(),
            help: None,
        }
    }
}

#[derive(Debug)]
pub struct CommandOptions {
    /// A set of checks to be called prior to executing the command. The checks
    /// will short-circuit on the first check that returns `false`.
    pub checks: Vec<Check>,
    /// Ratelimit bucket.
    pub bucket: Option<String>,
    /// Command description, used by other commands.
    pub desc: Option<String>,
    /// Example arguments, used by other commands.
    pub example: Option<String>,
    /// Command usage schema, used by other commands.
    pub usage: Option<String>,
    /// Minumum amount of arguments that should be passed.
    pub min_args: Option<i32>,
    /// Maximum amount of arguments that can be passed.
    pub max_args: Option<i32>,
    /// Permissions required to use this command.
    pub required_permissions: Permissions,
    /// Roles allowed to use this command.
    pub allowed_roles: Vec<String>,
    /// Whether command should be displayed in help list or not, used by other commands.
    pub help_available: bool,
    /// Whether command can be used only privately or not.
    pub dm_only: bool,
    /// Whether command can be used only in guilds or not.
    pub guild_only: bool,
    /// Whether command can only be used by owners or not.
    pub owners_only: bool,
    /// Other names that can be used to call this command instead.
    pub aliases: Vec<String>,
}

#[derive(Debug)]
pub struct HelpOptions {
    /// Suggests a command's name.
    pub suggestion_text: String,
    /// If no help is available, this text will be displayed.
    pub no_help_available_text: String,
    /// How to use a command, `{usage_label}: {command_name} {args}`
    pub usage_label: String,
    /// Actual sample label, `{usage_sample_label}: {command_name} {args}`
    pub usage_sample_label: String,
    /// Text labeling ungrouped commands, `{ungrouped_label}: ...`
    pub ungrouped_label: String,
    /// Text labeling the start of the description.
    pub description_label: String,
    /// Text labeling grouped commands, `{grouped_label} {group_name}: ...`
    pub grouped_label: String,
    /// Text labeling a command's alternative names (aliases).
    pub aliases_label: String,
    /// Text specifying that a command is only usable in a guild.
    pub guild_only_text: String,
    /// Text specifying that a command is only usable in via DM.
    pub dm_only_text: String,
    /// Text specifying that a command can be used via DM and in guilds.
    pub dm_and_guild_text: String,
    /// Text expressing that a command is available.
    pub available_text: String,
    /// Error-message once a command could not be found.
    /// Output-example (without whitespace between both substitutions: `{command_not_found_text}{command_name}`
    /// `{command_name}` describes user's input as in: `{prefix}help {command_name}`.
    pub command_not_found_text: String,
    /// Explains the user on how to use access a single command's details.
    pub individual_command_tip: String,
    /// Explains reasoning behind strikethrough-commands, see fields requiring `HelpBehaviour` for further information.
    /// If `HelpBehaviour::Strike` is unused, this field will evaluate to `None` during creation
    /// inside of `CreateHelpCommand`.
    ///
    /// **Note**: Text is only used in direct messages.
    pub striked_commands_tip_in_dm: Option<String>,
    /// Explains reasoning behind strikethrough-commands, see fields requiring `HelpBehaviour` for further information.
    /// If `HelpBehaviour::Strike` is unused, this field will evaluate to `None` during creation
    /// inside of `CreateHelpCommand`.
    ///
    /// **Note**: Text is only used in guilds.
    pub striked_commands_tip_in_guild: Option<String>,
    /// Announcing a group's prefix as in: {group_prefix} {prefix}.
    pub group_prefix: String,
    /// If a user lacks required roles, this will treat how these commands will be displayed.
    pub lacking_role: HelpBehaviour,
    /// If a user lacks permissions, this will treat how these commands will be displayed.
    pub lacking_permissions: HelpBehaviour,
    /// If a user is using the help-command in a channel where a command is not available,
    /// this behaviour will be executed.
    pub wrong_channel: HelpBehaviour,
    /// Colour help-embed will use upon encountering an error.
    pub embed_error_colour: Colour,
    /// Colour help-embed will use if no error occured.
    pub embed_success_colour: Colour,
}

pub trait HelpCommand: Send + Sync + 'static {
    fn execute(&self, &mut Context, &Message, &HelpOptions, HashMap<String, Arc<CommandGroup>>, &Args) -> Result<(), Error>;

    fn options(&self) -> Arc<CommandOptions> {
        Arc::clone(&DEFAULT_OPTIONS)
    }
}

impl HelpCommand for Arc<HelpCommand> {
    fn execute(&self, c: &mut Context, m: &Message, ho: &HelpOptions, hm: HashMap<String, Arc<CommandGroup>>, a: &Args) -> Result<(), Error> {
        (**self).execute(c, m, ho, hm, a)
    }
}

impl Default for HelpOptions {
    fn default() -> HelpOptions {
        HelpOptions {
            suggestion_text: "Did you mean {}?".to_string(),
            no_help_available_text: "**Error**: No help available.".to_string(),
            usage_label: "Usage".to_string(),
            usage_sample_label: "Sample usage".to_string(),
            ungrouped_label: "Ungrouped".to_string(),
            grouped_label: "Group".to_string(),
            aliases_label: "Aliases".to_string(),
            description_label: "Description".to_string(),
            guild_only_text: "Only in guilds".to_string(),
            dm_only_text: "Only in DM".to_string(),
            dm_and_guild_text: "In DM and guilds".to_string(),
            available_text: "Available".to_string(),
            command_not_found_text: "**Error**: Command `{}` not found.".to_string(),
            individual_command_tip: "To get help with an individual command, pass its \
                 name as an argument to this command.".to_string(),
            group_prefix: "Prefix".to_string(),
            striked_commands_tip_in_dm: Some(String::new()),
            striked_commands_tip_in_guild: Some(String::new()),
            lacking_role: HelpBehaviour::Strike,
            lacking_permissions: HelpBehaviour::Strike,
            wrong_channel: HelpBehaviour::Strike,
            embed_error_colour: Colour::dark_red(),
            embed_success_colour: Colour::rosewater(),
        }
    }
}

lazy_static! {
    static ref DEFAULT_OPTIONS: Arc<CommandOptions> = Arc::new(CommandOptions::default());
}

/// A framework command.
pub trait Command: Send + Sync + 'static {
    fn execute(&self, &mut Context, &Message, Args) -> Result<(), Error>;

    fn options(&self) -> Arc<CommandOptions> {
        Arc::clone(&DEFAULT_OPTIONS)
    }

    /// Called when the command gets registered.
    fn init(&self) {}

    /// "before" middleware. Is called alongside the global middleware in the framework.
    fn before(&self, &mut Context, &Message) -> bool { true }

    /// "after" middleware. Is called alongside the global middleware in the framework.
    fn after(&self, &mut Context, &Message, &Result<(), Error>) { }
}

impl Command for Arc<Command> {
    fn execute(&self, c: &mut Context, m: &Message, a: Args) -> Result<(), Error> {
        (**self).execute(c, m, a)
    }

    fn options(&self) -> Arc<CommandOptions> {
        (**self).options()
    }

    fn init(&self) {
        (**self).init()
    }

    fn before(&self, c: &mut Context, m: &Message) -> bool {
        (**self).before(c, m)
    }

    fn after(&self, c: &mut Context, m: &Message, res: &Result<(), Error>) {
        (**self).after(c, m, res)
    }
}

impl<F> Command for F where F: Fn(&mut Context, &Message, Args) -> Result<(), Error>
    + Send
    + Sync
    + ?Sized
    + 'static {
    fn execute(&self, c: &mut Context, m: &Message, a: Args) -> Result<(), Error> {
        (*self)(c, m, a)
    }
}

impl Default for CommandOptions {
    fn default() -> CommandOptions {
        CommandOptions {
            aliases: Vec::new(),
            checks: Vec::default(),
            desc: None,
            usage: None,
            example: None,
            min_args: None,
            bucket: None,
            max_args: None,
            required_permissions: Permissions::empty(),
            dm_only: false,
            guild_only: false,
            help_available: true,
            owners_only: false,
            allowed_roles: Vec::new(),
        }
    }
}

pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Option<Vec<usize>> {
    if !conf.prefixes.is_empty() || conf.dynamic_prefix.is_some() {
        // Find out if they were mentioned. If not, determine if the prefix
        // was used. If not, return None.
        let mut positions: Vec<usize> = vec![];

        if let Some(mention_end) = find_mention_end(&msg.content, conf) {
            positions.push(mention_end);
            return Some(positions);
        }

        // Dynamic prefixes, if present and suitable, always have a higher priority.
        if let Some(x) = conf.dynamic_prefix.as_ref().and_then(|f| f(ctx, msg)) {
            if msg.content.starts_with(&x) {
                positions.push(x.chars().count());
            }
        } else {
            for n in &conf.prefixes {
                if msg.content.starts_with(n) {
                    positions.push(n.chars().count());
                }
            }
        }

        if positions.is_empty() {
            return None;
        }

        let pos = *unsafe { positions.get_unchecked(0) };

        let with_whitespace = find_end_of_prefix_with_whitespace(&msg.content, pos);

        if conf.allow_whitespace {
            positions.insert(0, with_whitespace.unwrap_or(pos));
        } else if with_whitespace.is_some() {
            return None;
        }

        Some(positions)
    } else if conf.on_mention.is_some() {
        find_mention_end(&msg.content, conf).map(|mention_end| {
            vec![mention_end] // This can simply be returned without trying to find the end whitespaces as trim will remove it later
        })
    } else {
        None
    }
}

fn find_mention_end(content: &str, conf: &Configuration) -> Option<usize> {
    conf.on_mention.as_ref().and_then(|mentions| {
        mentions
            .iter()
            .find(|mention| content.starts_with(&mention[..]))
            .map(|m| m.len())
    })
}

// Finds the end of the first continuous block of whitespace after the prefix
fn find_end_of_prefix_with_whitespace(content: &str, position: usize) -> Option<usize> {
    let content_len = content.len();
    if position >= content_len { return None; }

    let mut i = 0;
    let chars = content.chars().skip(position);
    for char in chars {
        match char {
            // \t \n \r [space]
            '\t' | '\n' | '\r' | ' ' => i += 1,
            _ => return if i == 0 { None } else { Some(position + i) }
        }
    }
    Some(content.len())
}
<|endoftext|>


<commit_before>use mime::Mime;

header! {
    #[doc="`Content-Type` header, defined in"]
    #[doc="[RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5)"]
    #[doc=""]
    #[doc="The `Content-Type` header field indicates the media type of the"]
    #[doc="associated representation: either the representation enclosed in the"]
    #[doc="message payload or the selected representation, as determined by the"]
    #[doc="message semantics.  The indicated media type defines both the data"]
    #[doc="format and how that data is intended to be processed by a recipient,"]
    #[doc="within the scope of the received message semantics, after any content"]
    #[doc="codings indicated by Content-Encoding are decoded."]
    #[doc=""]
    #[doc="# ABNF"]
    #[doc="```plain"]
    #[doc="Content-Type = media-type"]
    #[doc="```"]
    #[doc=""]
    #[doc="# Example values"]
    #[doc="* `text/html; charset=ISO-8859-4`"]
    #[doc=""]
    #[doc="# Examples"]
    #[doc="```"]
    #[doc="use hyper::header::{Headers, ContentType};"]
    #[doc="use hyper::mime::{Mime, TopLevel, SubLevel};"]
    #[doc=""]
    #[doc="let mut headers = Headers::new();"]
    #[doc=""]
    #[doc="headers.set("]
    #[doc="    ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![]))"]
    #[doc=");"]
    #[doc="```"]
    #[doc="```"]
    #[doc="use hyper::header::{Headers, ContentType};"]
    #[doc="use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};"]
    #[doc=""]
    #[doc="let mut headers = Headers::new();"]
    #[doc="headers.set("]
    #[doc="    ContentType(Mime(TopLevel::Application, SubLevel::Json,"]
    #[doc="                     vec![(Attr::Charset, Value::Utf8)]))"]
    #[doc=""]
    #[doc=");"]
    #[doc="```"]
    (ContentType, "Content-Type") => [Mime]

    test_content_type {
        test_header!(
            test1,
            // FIXME: Should be b"text/html; charset=ISO-8859-4" but mime crate lowercases
            // the whole value so parsing and formatting the value gives a different result
            vec![b"text/html; charset=iso-8859-4"],
            Some(HeaderField(Mime(
                TopLevel::Text,
                SubLevel::Html,
                vec![(Attr::Charset, Value::Ext("iso-8859-4".to_owned()))]))));
    }
}

impl ContentType {
    /// A constructor  to easily create a `Content-Type: application/json; charset=utf-8` header.
    #[inline]
    pub fn json() -> ContentType {
        ContentType(mime!(Application/Json; Charset=Utf8))
    }

    /// A constructor  to easily create a `Content-Type: text/plain; charset=utf-8` header.
    #[inline]
    pub fn plaintext() -> ContentType {
        ContentType(mime!(Text/Plain; Charset=Utf8))
    }

    /// A constructor  to easily create a `Content-Type: text/html; charset=utf-8` header.
    #[inline]
    pub fn html() -> ContentType {
        ContentType(mime!(Text/Html; Charset=Utf8))
    }

    /// A constructor  to easily create a `Content-Type: application/www-form-url-encoded` header.
    #[inline]
    pub fn form_url_encoded() -> ContentType {
        ContentType(mime!(Application/WwwFormUrlEncoded))
    }
    /// A constructor  to easily create a `Content-Type: image/jpeg` header.
    #[inline]
    pub fn jpeg() -> ContentType {
        ContentType(mime!(Image/Jpeg))
    }

    /// A constructor  to easily create a `Content-Type: image/png` header.
    #[inline]
    pub fn png() -> ContentType {
        ContentType(mime!(Image/Png))
    }
}

bench_header!(bench, ContentType, { vec![b"application/json; charset=utf-8".to_vec()] });
<commit_msg>Doc: moved blank line in right place.<commit_after>use mime::Mime;

header! {
    #[doc="`Content-Type` header, defined in"]
    #[doc="[RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5)"]
    #[doc=""]
    #[doc="The `Content-Type` header field indicates the media type of the"]
    #[doc="associated representation: either the representation enclosed in the"]
    #[doc="message payload or the selected representation, as determined by the"]
    #[doc="message semantics.  The indicated media type defines both the data"]
    #[doc="format and how that data is intended to be processed by a recipient,"]
    #[doc="within the scope of the received message semantics, after any content"]
    #[doc="codings indicated by Content-Encoding are decoded."]
    #[doc=""]
    #[doc="# ABNF"]
    #[doc="```plain"]
    #[doc="Content-Type = media-type"]
    #[doc="```"]
    #[doc=""]
    #[doc="# Example values"]
    #[doc="* `text/html; charset=ISO-8859-4`"]
    #[doc=""]
    #[doc="# Examples"]
    #[doc="```"]
    #[doc="use hyper::header::{Headers, ContentType};"]
    #[doc="use hyper::mime::{Mime, TopLevel, SubLevel};"]
    #[doc=""]
    #[doc="let mut headers = Headers::new();"]
    #[doc=""]
    #[doc="headers.set("]
    #[doc="    ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![]))"]
    #[doc=");"]
    #[doc="```"]
    #[doc="```"]
    #[doc="use hyper::header::{Headers, ContentType};"]
    #[doc="use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};"]
    #[doc=""]
    #[doc="let mut headers = Headers::new();"]
    #[doc=""]
    #[doc="headers.set("]
    #[doc="    ContentType(Mime(TopLevel::Application, SubLevel::Json,"]
    #[doc="                     vec![(Attr::Charset, Value::Utf8)]))"]
    #[doc=");"]
    #[doc="```"]
    (ContentType, "Content-Type") => [Mime]

    test_content_type {
        test_header!(
            test1,
            // FIXME: Should be b"text/html; charset=ISO-8859-4" but mime crate lowercases
            // the whole value so parsing and formatting the value gives a different result
            vec![b"text/html; charset=iso-8859-4"],
            Some(HeaderField(Mime(
                TopLevel::Text,
                SubLevel::Html,
                vec![(Attr::Charset, Value::Ext("iso-8859-4".to_owned()))]))));
    }
}

impl ContentType {
    /// A constructor  to easily create a `Content-Type: application/json; charset=utf-8` header.
    #[inline]
    pub fn json() -> ContentType {
        ContentType(mime!(Application/Json; Charset=Utf8))
    }

    /// A constructor  to easily create a `Content-Type: text/plain; charset=utf-8` header.
    #[inline]
    pub fn plaintext() -> ContentType {
        ContentType(mime!(Text/Plain; Charset=Utf8))
    }

    /// A constructor  to easily create a `Content-Type: text/html; charset=utf-8` header.
    #[inline]
    pub fn html() -> ContentType {
        ContentType(mime!(Text/Html; Charset=Utf8))
    }

    /// A constructor  to easily create a `Content-Type: application/www-form-url-encoded` header.
    #[inline]
    pub fn form_url_encoded() -> ContentType {
        ContentType(mime!(Application/WwwFormUrlEncoded))
    }
    /// A constructor  to easily create a `Content-Type: image/jpeg` header.
    #[inline]
    pub fn jpeg() -> ContentType {
        ContentType(mime!(Image/Jpeg))
    }

    /// A constructor  to easily create a `Content-Type: image/png` header.
    #[inline]
    pub fn png() -> ContentType {
        ContentType(mime!(Image/Png))
    }
}

bench_header!(bench, ContentType, { vec![b"application/json; charset=utf-8".to_vec()] });
<|endoftext|>


<commit_before>use lexer::new;
use lexer::Lexer;
use lexer::StateFunction;
use lexer::Token;
use lexer::Category;

fn initial_state(lexer: &mut Lexer) -> Option<StateFunction> {
    if lexer.has_more_data() {
        // TODO: Replace char_at with something UTF8 compatible.
        match lexer.data.char_at(lexer.token_position) {
            '{' => {
                lexer.tokenize_next(1, Category::Brace);
            },
            '[' => {
                lexer.tokenize_next(1, Category::Bracket);
            },
            ' ' | '\n' => {
                lexer.tokenize(Category::Text);
                lexer.advance();
                return Some(StateFunction(whitespace));
            },
            '"' => {
                lexer.tokenize(Category::Text);
                lexer.advance();
                return Some(StateFunction(inside_string));
            },
            ':' => {
                lexer.tokenize_next(1, Category::AssignmentOperator);
            },
            '}' => {
                lexer.tokenize_next(1, Category::Brace);
            },
            ']' => {
                lexer.tokenize_next(1, Category::Bracket);
            },
            _ => {
                if lexer.token_position == lexer.token_start {
                    let remaining_data = lexer.data
                        .slice_from(lexer.token_position).to_string();

                    if remaining_data.starts_with("true") {
                        lexer.tokenize_next(4, Category::Boolean);
                    } else if remaining_data.starts_with("false") {
                        lexer.tokenize_next(5, Category::Boolean);
                    } else if remaining_data.starts_with("null") {
                        lexer.tokenize_next(4, Category::Keyword);
                    } else {
                        lexer.advance();
                    }
                } else {
                    lexer.advance();
                }
            }
        }
        Some(StateFunction(initial_state))
    } else {
        lexer.tokenize(Category::Text);
        None
    }
}

fn inside_string(lexer: &mut Lexer) -> Option<StateFunction> {
    if lexer.has_more_data() {
        match lexer.data.char_at(lexer.token_position) {
            '"' => {
                lexer.advance();
                lexer.tokenize(Category::String);
                Some(StateFunction(initial_state))
            },
            '\\' => {
                lexer.advance();
                lexer.advance();
                Some(StateFunction(inside_string))
            }
            _ => {
                lexer.advance();
                Some(StateFunction(inside_string))
            }
        }
    } else {
        lexer.tokenize(Category::String);
        None
    }
}

fn whitespace(lexer: &mut Lexer) -> Option<StateFunction> {
    if lexer.has_more_data() {
        match lexer.data.char_at(lexer.token_position) {
            ' ' | '\n' => {
                lexer.advance();
                Some(StateFunction(whitespace))
            },
            _ => {
                lexer.tokenize(Category::Whitespace);
                Some(StateFunction(initial_state))
            }
        }
    } else {
        lexer.tokenize(Category::Whitespace);
        None
    }
}

fn lex(data: &str) -> Vec<Token> {
    let mut lexer = new(data);
    let mut state_function = StateFunction(initial_state);
    loop {
        let StateFunction(actual_function) = state_function;
        match actual_function(&mut lexer) {
            Some(f) => state_function = f,
            None => return lexer.tokens,
        }
    }
}

mod tests {
    use super::lex;
    use lexer::Token;
    use lexer::Category;
    use std::old_io::{File, Open, Read};

    #[test]
    fn it_works() {
        let data = File::open_mode(&Path::new("test_data/data.json"), Open, Read)
            .unwrap().read_to_string().unwrap();
        let tokens = lex(&data);
        let expected_tokens = vec![
            Token{ lexeme: "{".to_string(), category: Category::Brace },
            Token{ lexeme: "\n  ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "\"key\"".to_string(), category: Category::String },
            Token{ lexeme: ":".to_string(), category: Category::AssignmentOperator },
            Token{ lexeme: " ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "\"4032\"".to_string(), category: Category::String },
            Token{ lexeme: ",".to_string(), category: Category::Text },
            Token{ lexeme: "\n  ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "'single'".to_string(), category: Category::Text },
            Token{ lexeme: ":".to_string(), category: Category::AssignmentOperator },
            Token{ lexeme: " ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "'quotes\\'',".to_string(), category: Category::Text },
            Token{ lexeme: "\n  ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "\"literals\"".to_string(), category: Category::String },
            Token{ lexeme: ":".to_string(), category: Category::AssignmentOperator },
            Token{ lexeme: " ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "[".to_string(), category: Category::Bracket },
            Token{ lexeme: "\n    ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "true".to_string(), category: Category::Boolean },
            Token{ lexeme: ",".to_string(), category: Category::Text },
            Token{ lexeme: "\n    ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "false".to_string(), category: Category::Boolean },
            Token{ lexeme: ",".to_string(), category: Category::Text },
            Token{ lexeme: "\n    ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "null".to_string(), category: Category::Keyword },
            Token{ lexeme: "\n  ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "]".to_string(), category: Category::Bracket },
            Token{ lexeme: "\n".to_string(), category: Category::Whitespace },
            Token{ lexeme: "}".to_string(), category: Category::Brace },
            Token{ lexeme: "\n".to_string(), category: Category::Whitespace },
        ];

        for (index, token) in tokens.iter().enumerate() {
            assert_eq!(*token, expected_tokens[index]);
        }
    }

    #[test]
    fn it_can_handle_garbage() {
        let tokens = lex("} adwyx123&*_ ");
        let expected_tokens = vec![
            Token{ lexeme: "}".to_string(), category: Category::Brace },
            Token{ lexeme: " ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "adwyx123&*_".to_string(), category: Category::Text },
            Token{ lexeme: " ".to_string(), category: Category::Whitespace },
        ];

        for (index, token) in tokens.iter().enumerate() {
            assert_eq!(*token, expected_tokens[index]);
        }
    }

    #[test]
    fn it_can_handle_open_strings() {
        let tokens = lex("\"open!");
        let expected_tokens = vec![
            Token{ lexeme: "\"open!".to_string(), category: Category::String },
        ];

        for (index, token) in tokens.iter().enumerate() {
            assert_eq!(*token, expected_tokens[index]);
        }
    }
}
<commit_msg>Add unicode support.<commit_after>use lexer::new;
use lexer::Lexer;
use lexer::StateFunction;
use lexer::Token;
use lexer::Category;

fn initial_state(lexer: &mut Lexer) -> Option<StateFunction> {
    if lexer.has_more_data() {
        match lexer.data.chars().nth(lexer.token_position).unwrap() {
            '{' => {
                lexer.tokenize_next(1, Category::Brace);
            },
            '[' => {
                lexer.tokenize_next(1, Category::Bracket);
            },
            ' ' | '\n' => {
                lexer.tokenize(Category::Text);
                lexer.advance();
                return Some(StateFunction(whitespace));
            },
            '"' => {
                lexer.tokenize(Category::Text);
                lexer.advance();
                return Some(StateFunction(inside_string));
            },
            ':' => {
                lexer.tokenize_next(1, Category::AssignmentOperator);
            },
            '}' => {
                lexer.tokenize_next(1, Category::Brace);
            },
            ']' => {
                lexer.tokenize_next(1, Category::Bracket);
            },
            _ => {
                if lexer.token_position == lexer.token_start {
                    let remaining_data = lexer.data
                        .slice_from(lexer.token_position).to_string();

                    if remaining_data.starts_with("true") {
                        lexer.tokenize_next(4, Category::Boolean);
                    } else if remaining_data.starts_with("false") {
                        lexer.tokenize_next(5, Category::Boolean);
                    } else if remaining_data.starts_with("null") {
                        lexer.tokenize_next(4, Category::Keyword);
                    } else {
                        lexer.advance();
                    }
                } else {
                    lexer.advance();
                }
            }
        }
        Some(StateFunction(initial_state))
    } else {
        lexer.tokenize(Category::Text);
        None
    }
}

fn inside_string(lexer: &mut Lexer) -> Option<StateFunction> {
    if lexer.has_more_data() {
        match lexer.data.chars().nth(lexer.token_position).unwrap() {
            '"' => {
                lexer.advance();
                lexer.tokenize(Category::String);
                Some(StateFunction(initial_state))
            },
            '\\' => {
                lexer.advance();
                lexer.advance();
                Some(StateFunction(inside_string))
            }
            _ => {
                lexer.advance();
                Some(StateFunction(inside_string))
            }
        }
    } else {
        lexer.tokenize(Category::String);
        None
    }
}

fn whitespace(lexer: &mut Lexer) -> Option<StateFunction> {
    if lexer.has_more_data() {
        match lexer.data.chars().nth(lexer.token_position).unwrap() {
            ' ' | '\n' => {
                lexer.advance();
                Some(StateFunction(whitespace))
            },
            _ => {
                lexer.tokenize(Category::Whitespace);
                Some(StateFunction(initial_state))
            }
        }
    } else {
        lexer.tokenize(Category::Whitespace);
        None
    }
}

fn lex(data: &str) -> Vec<Token> {
    let mut lexer = new(data);
    let mut state_function = StateFunction(initial_state);
    loop {
        let StateFunction(actual_function) = state_function;
        match actual_function(&mut lexer) {
            Some(f) => state_function = f,
            None => return lexer.tokens,
        }
    }
}

mod tests {
    use super::lex;
    use lexer::Token;
    use lexer::Category;
    use std::old_io::{File, Open, Read};

    #[test]
    fn it_works() {
        let data = File::open_mode(&Path::new("test_data/data.json"), Open, Read)
            .unwrap().read_to_string().unwrap();
        let tokens = lex(&data);
        let expected_tokens = vec![
            Token{ lexeme: "{".to_string(), category: Category::Brace },
            Token{ lexeme: "\n  ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "\"key\"".to_string(), category: Category::String },
            Token{ lexeme: ":".to_string(), category: Category::AssignmentOperator },
            Token{ lexeme: " ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "\"4032\"".to_string(), category: Category::String },
            Token{ lexeme: ",".to_string(), category: Category::Text },
            Token{ lexeme: "\n  ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "'single'".to_string(), category: Category::Text },
            Token{ lexeme: ":".to_string(), category: Category::AssignmentOperator },
            Token{ lexeme: " ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "'quotes\\'',".to_string(), category: Category::Text },
            Token{ lexeme: "\n  ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "\"literals\"".to_string(), category: Category::String },
            Token{ lexeme: ":".to_string(), category: Category::AssignmentOperator },
            Token{ lexeme: " ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "[".to_string(), category: Category::Bracket },
            Token{ lexeme: "\n    ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "true".to_string(), category: Category::Boolean },
            Token{ lexeme: ",".to_string(), category: Category::Text },
            Token{ lexeme: "\n    ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "false".to_string(), category: Category::Boolean },
            Token{ lexeme: ",".to_string(), category: Category::Text },
            Token{ lexeme: "\n    ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "null".to_string(), category: Category::Keyword },
            Token{ lexeme: "\n  ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "]".to_string(), category: Category::Bracket },
            Token{ lexeme: "\n".to_string(), category: Category::Whitespace },
            Token{ lexeme: "}".to_string(), category: Category::Brace },
            Token{ lexeme: "\n".to_string(), category: Category::Whitespace },
        ];

        for (index, token) in tokens.iter().enumerate() {
            assert_eq!(*token, expected_tokens[index]);
        }
    }

    #[test]
    fn it_can_handle_garbage() {
        let tokens = lex("} adwyx123&*_ ");
        let expected_tokens = vec![
            Token{ lexeme: "}".to_string(), category: Category::Brace },
            Token{ lexeme: " ".to_string(), category: Category::Whitespace },
            Token{ lexeme: "adwyx123&*_".to_string(), category: Category::Text },
            Token{ lexeme: " ".to_string(), category: Category::Whitespace },
        ];

        for (index, token) in tokens.iter().enumerate() {
            assert_eq!(*token, expected_tokens[index]);
        }
    }

    #[test]
    fn it_can_handle_open_strings() {
        let tokens = lex("\"open!");
        let expected_tokens = vec![
            Token{ lexeme: "\"open!".to_string(), category: Category::String },
        ];

        for (index, token) in tokens.iter().enumerate() {
            assert_eq!(*token, expected_tokens[index]);
        }
    }

    #[test]
    fn it_can_handle_utf8_data() {
        let tokens = lex("différent");
        let expected_tokens = vec![
            Token{ lexeme: "différent".to_string(), category: Category::Text },
        ];

        for (index, token) in tokens.iter().enumerate() {
            assert_eq!(*token, expected_tokens[index]);
        }
    }
}
<|endoftext|>


<commit_before>use super::{
    super::pipelines::{self, Pipeline},
    case,
    functions::{collect_arguments, parse_function},
};
use crate::{
    lexers::{assignment_lexer, ArgumentSplitter},
    shell::{
        flow_control::{Case, ElseIf, ExportAction, LocalAction, Statement},
        status::FAILURE,
    },
};
use small;
use std::char;

fn collect<F>(arguments: &str, statement: F) -> Statement
where
    F: Fn(Pipeline) -> Statement,
{
    match pipelines::Collector::run(arguments) {
        Ok(pipeline) => statement(pipeline),
        Err(err) => {
            eprintln!("ion: syntax error: {}", err);
            Statement::Default
        }
    }
}

pub fn is_valid_name(name: &str) -> bool {
    if !(name.as_bytes()[0] as char).is_numeric() {
        !name.chars().any(|c| !(c.is_alphanumeric() || c == '_'))
    } else {
        false
    }
}

pub(crate) fn parse(code: &str) -> Statement {
    let cmd = code.trim();
    match cmd {
        "end" => Statement::End,
        "break" => Statement::Break,
        "continue" => Statement::Continue,
        "for" | "match" | "case" => {
            eprintln!("ion: syntax error: incomplete control flow statement");
            Statement::Error(FAILURE)
        }
        "let" => Statement::Let(LocalAction::List),
        _ if cmd.starts_with("let ") => {
            // Split the let expression and ensure that the statement is valid.
            let (keys, op, vals) = assignment_lexer(cmd[4..].trim_start());
            match vals {
                Some(vals) => {
                    // If the values exist, then the keys and operator also exists.
                    Statement::Let(LocalAction::Assign(
                        keys.unwrap().into(),
                        op.unwrap(),
                        vals.into(),
                    ))
                }
                None => {
                    if op.is_none() {
                        eprintln!("ion: assignment error: no operator supplied.");
                    } else {
                        eprintln!("ion: assignment error: no values supplied.");
                    }
                    Statement::Error(FAILURE)
                }
            }
        }
        "export" => Statement::Export(ExportAction::List),
        _ if cmd.starts_with("export ") => {
            // Split the let expression and ensure that the statement is valid.
            let (keys, op, vals) = assignment_lexer(cmd[7..].trim_start());
            match vals {
                Some(vals) => {
                    // If the values exist, then the keys and operator also exists.
                    Statement::Export(ExportAction::Assign(
                        keys.unwrap().into(),
                        op.unwrap(),
                        vals.into(),
                    ))
                }
                None => {
                    if keys.is_none() {
                        eprintln!("ion: assignment error: no keys supplied.")
                    } else if op.is_some() {
                        eprintln!("ion: assignment error: no values supplied.")
                    } else {
                        return Statement::Export(ExportAction::LocalExport(keys.unwrap().into()));
                    }
                    Statement::Error(FAILURE)
                }
            }
        }
        _ if cmd.starts_with("if ") => Statement::If {
            expression: vec![parse(cmd[3..].trim_start())],
            success:    Vec::new(),
            else_if:    Vec::new(),
            failure:    Vec::new(),
            mode:       0,
        },
        "else" => Statement::Else,
        _ if cmd.starts_with("else") => {
            let cmd = cmd[4..].trim_start();
            if !cmd.is_empty() && cmd.starts_with("if ") {
                Statement::ElseIf(ElseIf {
                    expression: vec![parse(cmd[3..].trim_start())],
                    success:    Vec::new(),
                })
            } else {
                Statement::Else
            }
        }
        _ if cmd.starts_with("while ") => {
            collect(cmd[6..].trim_start(), |pipeline| Statement::While {
                expression: vec![Statement::Pipeline(pipeline)],
                statements: Vec::new(),
            })
        }
        _ if cmd.starts_with("for ") => {
            let mut cmd = cmd[4..].trim_start();
            let mut variables = None;

            if cmd.len() > 5 {
                let cmdb = cmd.as_bytes();
                for start in 0..cmd.len() - 4 {
                    if &cmdb[start..start + 4] == b" in " {
                        variables = Some(cmd[..start].split_whitespace().map(Into::into).collect());

                        cmd = cmd[start + 3..].trim();

                        break;
                    }
                }
            }

            match variables {
                Some(variables) => Statement::For {
                    variables,
                    values: ArgumentSplitter::new(cmd).map(small::String::from).collect(),
                    statements: Vec::new(),
                },
                None => {
                    eprintln!("ion: syntax error: for loop lacks the `in` keyword");
                    Statement::Error(FAILURE)
                }
            }
        }
        _ if cmd.starts_with("case ") => {
            let (value, binding, conditional) = match cmd[5..].trim_start() {
                "_" => (None, None, None),
                value => {
                    let (value, binding, conditional) = match case::parse_case(value) {
                        Ok(values) => values,
                        Err(why) => {
                            eprintln!("ion: case error: {}", why);
                            return Statement::Error(FAILURE);
                        }
                    };
                    let binding = binding.map(Into::into);
                    match value {
                        Some("_") => (None, binding, conditional),
                        Some(value) => (Some(value.into()), binding, conditional),
                        None => (None, binding, conditional),
                    }
                }
            };

            Statement::Case(Case { value, binding, conditional, statements: Vec::new() })
        }
        _ if cmd.starts_with("match ") => {
            Statement::Match { expression: cmd[6..].trim_start().into(), cases: Vec::new() }
        }
        _ if cmd.starts_with("fn ") => {
            let cmd = cmd[3..].trim_start();
            let pos = cmd.find(char::is_whitespace).unwrap_or_else(|| cmd.len());
            let name = &cmd[..pos];
            if !is_valid_name(name) {
                eprintln!(
                    "ion: syntax error: '{}' is not a valid function name\n     Function names \
                     may only contain alphanumeric characters",
                    name
                );
                return Statement::Error(FAILURE);
            }

            let (args, description) = parse_function(&cmd[pos..]);
            match collect_arguments(args) {
                Ok(args) => Statement::Function {
                    description: description.map(small::String::from),
                    name: name.into(),
                    args,
                    statements: Vec::new(),
                },
                Err(why) => {
                    eprintln!("ion: function argument error: {}", why);
                    Statement::Error(FAILURE)
                }
            }
        }
        _ if cmd.starts_with("time ") => {
            // Ignore embedded time calls
            let mut timed = cmd[4..].trim_start();
            loop {
                if timed.starts_with("time ") {
                    timed = timed[4..].trim_start();
                    continue;
                }
                break;
            }
            Statement::Time(Box::new(parse(timed)))
        }
        _ if cmd.eq("time") => Statement::Time(Box::new(Statement::Default)),
        _ if cmd.starts_with("and ") => Statement::And(Box::new(parse(cmd[3..].trim_start()))),
        _ if cmd.eq("and") => Statement::And(Box::new(Statement::Default)),
        _ if cmd.starts_with("or ") => Statement::Or(Box::new(parse(cmd[2..].trim_start()))),
        _ if cmd.eq("or") => Statement::Or(Box::new(Statement::Default)),
        _ if cmd.starts_with("not ") => Statement::Not(Box::new(parse(cmd[3..].trim_start()))),
        _ if cmd.starts_with("! ") => Statement::Not(Box::new(parse(cmd[1..].trim_start()))),
        _ if cmd.eq("not") | cmd.eq("!") => Statement::Not(Box::new(Statement::Default)),
        _ if cmd.is_empty() || cmd.starts_with('#') => Statement::Default,
        _ => collect(cmd, Statement::Pipeline),
    }
}

#[cfg(test)]
mod tests {
    use self::pipelines::PipeItem;
    use super::*;
    use crate::{
        lexers::assignments::{KeyBuf, Primitive},
        shell::{flow_control::Statement, Job, JobKind},
    };

    #[test]
    fn parsing_for() {
        assert_eq!(
            parse("for x y z in 1..=10"),
            Statement::For {
                variables:  vec!["x", "y", "z"].into_iter().map(Into::into).collect(),
                values:     vec!["1..=10"].into_iter().map(Into::into).collect(),
                statements: Vec::new(),
            }
        );

        assert_eq!(
            parse("for  x  in  {1..=10} {1..=10}"),
            Statement::For {
                variables:  vec!["x"].into_iter().map(Into::into).collect(),
                values:     vec!["{1..=10}", "{1..=10}"].into_iter().map(Into::into).collect(),
                statements: Vec::new(),
            }
        );
    }

    #[test]
    fn parsing_ifs() {
        // Default case where spaced normally
        let parsed_if = parse("if test 1 -eq 2");
        let correct_parse = Statement::If {
            expression: vec![Statement::Pipeline(Pipeline {
                items: vec![PipeItem {
                    job:     Job::new(
                        vec!["test".into(), "1".into(), "-eq".into(), "2".into()]
                            .into_iter()
                            .collect(),
                        JobKind::Last,
                    ),
                    outputs: Vec::new(),
                    inputs:  Vec::new(),
                }],
            })],
            success:    vec![],
            else_if:    vec![],
            failure:    vec![],
            mode:       0,
        };
        assert_eq!(correct_parse, parsed_if);

        // Trailing spaces after final value
        let parsed_if = parse("if test 1 -eq 2         ");
        assert_eq!(correct_parse, parsed_if);
    }

    #[test]
    fn parsing_elses() {
        // Default case where spaced normally
        let mut parsed_if = parse("else");
        let correct_parse = Statement::Else;
        assert_eq!(correct_parse, parsed_if);

        // Trailing spaces after final value
        parsed_if = parse("else         ");
        assert_eq!(correct_parse, parsed_if);

        // Leading spaces after final value
        parsed_if = parse("         else");
        assert_eq!(correct_parse, parsed_if);
    }

    #[test]
    fn parsing_ends() {
        // Default case where spaced normally
        let parsed_if = parse("end");
        let correct_parse = Statement::End;
        assert_eq!(correct_parse, parsed_if);

        // Trailing spaces after final value
        let parsed_if = parse("end         ");
        let correct_parse = Statement::End;
        assert_eq!(correct_parse, parsed_if);

        // Leading spaces after final value
        let parsed_if = parse("         end");
        let correct_parse = Statement::End;
        assert_eq!(correct_parse, parsed_if);
    }

    #[test]
    fn parsing_functions() {
        // Default case where spaced normally
        let parsed_if = parse("fn bob");
        let correct_parse = Statement::Function {
            description: None,
            name:        "bob".into(),
            args:        Default::default(),
            statements:  Default::default(),
        };
        assert_eq!(correct_parse, parsed_if);

        // Trailing spaces after final value
        let parsed_if = parse("fn bob        ");
        assert_eq!(correct_parse, parsed_if);

        // Leading spaces after final value
        let parsed_if = parse("         fn bob");
        assert_eq!(correct_parse, parsed_if);

        // Default case where spaced normally
        let parsed_if = parse("fn bob a b");
        let correct_parse = Statement::Function {
            description: None,
            name:        "bob".into(),
            args:        vec![
                KeyBuf { name: "a".into(), kind: Primitive::Str },
                KeyBuf { name: "b".into(), kind: Primitive::Str },
            ],
            statements:  Default::default(),
        };
        assert_eq!(correct_parse, parsed_if);

        // Trailing spaces after final value
        let parsed_if = parse("fn bob a b       ");
        assert_eq!(correct_parse, parsed_if);

        let parsed_if = parse("fn bob a b --bob is a nice function");
        let correct_parse = Statement::Function {
            description: Some("bob is a nice function".into()),
            name:        "bob".into(),
            args:        vec![
                KeyBuf { name: "a".into(), kind: Primitive::Str },
                KeyBuf { name: "b".into(), kind: Primitive::Str },
            ],
            statements:  vec![],
        };
        assert_eq!(correct_parse, parsed_if);
        let parsed_if = parse("fn bob a b --          bob is a nice function");
        assert_eq!(correct_parse, parsed_if);
        let parsed_if = parse("fn bob a b      --bob is a nice function");
        assert_eq!(correct_parse, parsed_if);
    }
}
<commit_msg>Check for non ascii alphanumeric variable names<commit_after>use super::{
    super::pipelines::{self, Pipeline},
    case,
    functions::{collect_arguments, parse_function},
};
use crate::{
    lexers::{assignment_lexer, ArgumentSplitter},
    shell::{
        flow_control::{Case, ElseIf, ExportAction, LocalAction, Statement},
        status::FAILURE,
    },
};
use small;
use std::char;

fn collect<F>(arguments: &str, statement: F) -> Statement
where
    F: Fn(Pipeline) -> Statement,
{
    match pipelines::Collector::run(arguments) {
        Ok(pipeline) => statement(pipeline),
        Err(err) => {
            eprintln!("ion: syntax error: {}", err);
            Statement::Default
        }
    }
}

pub fn is_valid_name(name: &str) -> bool {
    let mut bytes = name.bytes();
    bytes.next().map_or(false, |b| b.is_ascii_alphabetic())
        && bytes.all(|b| b.is_ascii_alphanumeric() || b == b'_')
}

pub(crate) fn parse(code: &str) -> Statement {
    let cmd = code.trim();
    match cmd {
        "end" => Statement::End,
        "break" => Statement::Break,
        "continue" => Statement::Continue,
        "for" | "match" | "case" => {
            eprintln!("ion: syntax error: incomplete control flow statement");
            Statement::Error(FAILURE)
        }
        "let" => Statement::Let(LocalAction::List),
        _ if cmd.starts_with("let ") => {
            // Split the let expression and ensure that the statement is valid.
            let (keys, op, vals) = assignment_lexer(cmd[4..].trim_start());
            match vals {
                Some(vals) => {
                    // If the values exist, then the keys and operator also exists.
                    Statement::Let(LocalAction::Assign(
                        keys.unwrap().into(),
                        op.unwrap(),
                        vals.into(),
                    ))
                }
                None => {
                    if op.is_none() {
                        eprintln!("ion: assignment error: no operator supplied.");
                    } else {
                        eprintln!("ion: assignment error: no values supplied.");
                    }
                    Statement::Error(FAILURE)
                }
            }
        }
        "export" => Statement::Export(ExportAction::List),
        _ if cmd.starts_with("export ") => {
            // Split the let expression and ensure that the statement is valid.
            let (keys, op, vals) = assignment_lexer(cmd[7..].trim_start());
            match vals {
                Some(vals) => {
                    // If the values exist, then the keys and operator also exists.
                    Statement::Export(ExportAction::Assign(
                        keys.unwrap().into(),
                        op.unwrap(),
                        vals.into(),
                    ))
                }
                None => {
                    if keys.is_none() {
                        eprintln!("ion: assignment error: no keys supplied.")
                    } else if op.is_some() {
                        eprintln!("ion: assignment error: no values supplied.")
                    } else {
                        return Statement::Export(ExportAction::LocalExport(keys.unwrap().into()));
                    }
                    Statement::Error(FAILURE)
                }
            }
        }
        _ if cmd.starts_with("if ") => Statement::If {
            expression: vec![parse(cmd[3..].trim_start())],
            success:    Vec::new(),
            else_if:    Vec::new(),
            failure:    Vec::new(),
            mode:       0,
        },
        "else" => Statement::Else,
        _ if cmd.starts_with("else") => {
            let cmd = cmd[4..].trim_start();
            if !cmd.is_empty() && cmd.starts_with("if ") {
                Statement::ElseIf(ElseIf {
                    expression: vec![parse(cmd[3..].trim_start())],
                    success:    Vec::new(),
                })
            } else {
                Statement::Else
            }
        }
        _ if cmd.starts_with("while ") => {
            collect(cmd[6..].trim_start(), |pipeline| Statement::While {
                expression: vec![Statement::Pipeline(pipeline)],
                statements: Vec::new(),
            })
        }
        _ if cmd.starts_with("for ") => {
            let mut cmd = cmd[4..].trim_start();
            let mut variables = None;

            if cmd.len() > 5 {
                let cmdb = cmd.as_bytes();
                for start in 0..cmd.len() - 4 {
                    if &cmdb[start..start + 4] == b" in " {
                        variables = Some(cmd[..start].split_whitespace().map(Into::into).collect());

                        cmd = cmd[start + 3..].trim();

                        break;
                    }
                }
            }

            match variables {
                Some(variables) => Statement::For {
                    variables,
                    values: ArgumentSplitter::new(cmd).map(small::String::from).collect(),
                    statements: Vec::new(),
                },
                None => {
                    eprintln!("ion: syntax error: for loop lacks the `in` keyword");
                    Statement::Error(FAILURE)
                }
            }
        }
        _ if cmd.starts_with("case ") => {
            let (value, binding, conditional) = match cmd[5..].trim_start() {
                "_" => (None, None, None),
                value => {
                    let (value, binding, conditional) = match case::parse_case(value) {
                        Ok(values) => values,
                        Err(why) => {
                            eprintln!("ion: case error: {}", why);
                            return Statement::Error(FAILURE);
                        }
                    };
                    let binding = binding.map(Into::into);
                    match value {
                        Some("_") => (None, binding, conditional),
                        Some(value) => (Some(value.into()), binding, conditional),
                        None => (None, binding, conditional),
                    }
                }
            };

            Statement::Case(Case { value, binding, conditional, statements: Vec::new() })
        }
        _ if cmd.starts_with("match ") => {
            Statement::Match { expression: cmd[6..].trim_start().into(), cases: Vec::new() }
        }
        _ if cmd.starts_with("fn ") => {
            let cmd = cmd[3..].trim_start();
            let pos = cmd.find(char::is_whitespace).unwrap_or_else(|| cmd.len());
            let name = &cmd[..pos];
            if !is_valid_name(name) {
                eprintln!(
                    "ion: syntax error: '{}' is not a valid function name\n     Function names \
                     may only contain alphanumeric characters",
                    name
                );
                return Statement::Error(FAILURE);
            }

            let (args, description) = parse_function(&cmd[pos..]);
            match collect_arguments(args) {
                Ok(args) => Statement::Function {
                    description: description.map(small::String::from),
                    name: name.into(),
                    args,
                    statements: Vec::new(),
                },
                Err(why) => {
                    eprintln!("ion: function argument error: {}", why);
                    Statement::Error(FAILURE)
                }
            }
        }
        _ if cmd.starts_with("time ") => {
            // Ignore embedded time calls
            let mut timed = cmd[4..].trim_start();
            loop {
                if timed.starts_with("time ") {
                    timed = timed[4..].trim_start();
                    continue;
                }
                break;
            }
            Statement::Time(Box::new(parse(timed)))
        }
        _ if cmd.eq("time") => Statement::Time(Box::new(Statement::Default)),
        _ if cmd.starts_with("and ") => Statement::And(Box::new(parse(cmd[3..].trim_start()))),
        _ if cmd.eq("and") => Statement::And(Box::new(Statement::Default)),
        _ if cmd.starts_with("or ") => Statement::Or(Box::new(parse(cmd[2..].trim_start()))),
        _ if cmd.eq("or") => Statement::Or(Box::new(Statement::Default)),
        _ if cmd.starts_with("not ") => Statement::Not(Box::new(parse(cmd[3..].trim_start()))),
        _ if cmd.starts_with("! ") => Statement::Not(Box::new(parse(cmd[1..].trim_start()))),
        _ if cmd.eq("not") | cmd.eq("!") => Statement::Not(Box::new(Statement::Default)),
        _ if cmd.is_empty() || cmd.starts_with('#') => Statement::Default,
        _ => collect(cmd, Statement::Pipeline),
    }
}

#[cfg(test)]
mod tests {
    use self::pipelines::PipeItem;
    use super::*;
    use crate::{
        lexers::assignments::{KeyBuf, Primitive},
        shell::{flow_control::Statement, Job, JobKind},
    };

    #[test]
    fn parsing_for() {
        assert_eq!(
            parse("for x y z in 1..=10"),
            Statement::For {
                variables:  vec!["x", "y", "z"].into_iter().map(Into::into).collect(),
                values:     vec!["1..=10"].into_iter().map(Into::into).collect(),
                statements: Vec::new(),
            }
        );

        assert_eq!(
            parse("for  x  in  {1..=10} {1..=10}"),
            Statement::For {
                variables:  vec!["x"].into_iter().map(Into::into).collect(),
                values:     vec!["{1..=10}", "{1..=10}"].into_iter().map(Into::into).collect(),
                statements: Vec::new(),
            }
        );
    }

    #[test]
    fn parsing_ifs() {
        // Default case where spaced normally
        let parsed_if = parse("if test 1 -eq 2");
        let correct_parse = Statement::If {
            expression: vec![Statement::Pipeline(Pipeline {
                items: vec![PipeItem {
                    job:     Job::new(
                        vec!["test".into(), "1".into(), "-eq".into(), "2".into()]
                            .into_iter()
                            .collect(),
                        JobKind::Last,
                    ),
                    outputs: Vec::new(),
                    inputs:  Vec::new(),
                }],
            })],
            success:    vec![],
            else_if:    vec![],
            failure:    vec![],
            mode:       0,
        };
        assert_eq!(correct_parse, parsed_if);

        // Trailing spaces after final value
        let parsed_if = parse("if test 1 -eq 2         ");
        assert_eq!(correct_parse, parsed_if);
    }

    #[test]
    fn parsing_elses() {
        // Default case where spaced normally
        let mut parsed_if = parse("else");
        let correct_parse = Statement::Else;
        assert_eq!(correct_parse, parsed_if);

        // Trailing spaces after final value
        parsed_if = parse("else         ");
        assert_eq!(correct_parse, parsed_if);

        // Leading spaces after final value
        parsed_if = parse("         else");
        assert_eq!(correct_parse, parsed_if);
    }

    #[test]
    fn parsing_ends() {
        // Default case where spaced normally
        let parsed_if = parse("end");
        let correct_parse = Statement::End;
        assert_eq!(correct_parse, parsed_if);

        // Trailing spaces after final value
        let parsed_if = parse("end         ");
        let correct_parse = Statement::End;
        assert_eq!(correct_parse, parsed_if);

        // Leading spaces after final value
        let parsed_if = parse("         end");
        let correct_parse = Statement::End;
        assert_eq!(correct_parse, parsed_if);
    }

    #[test]
    fn parsing_functions() {
        // Default case where spaced normally
        let parsed_if = parse("fn bob");
        let correct_parse = Statement::Function {
            description: None,
            name:        "bob".into(),
            args:        Default::default(),
            statements:  Default::default(),
        };
        assert_eq!(correct_parse, parsed_if);

        // Trailing spaces after final value
        let parsed_if = parse("fn bob        ");
        assert_eq!(correct_parse, parsed_if);

        // Leading spaces after final value
        let parsed_if = parse("         fn bob");
        assert_eq!(correct_parse, parsed_if);

        // Default case where spaced normally
        let parsed_if = parse("fn bob a b");
        let correct_parse = Statement::Function {
            description: None,
            name:        "bob".into(),
            args:        vec![
                KeyBuf { name: "a".into(), kind: Primitive::Str },
                KeyBuf { name: "b".into(), kind: Primitive::Str },
            ],
            statements:  Default::default(),
        };
        assert_eq!(correct_parse, parsed_if);

        // Trailing spaces after final value
        let parsed_if = parse("fn bob a b       ");
        assert_eq!(correct_parse, parsed_if);

        let parsed_if = parse("fn bob a b --bob is a nice function");
        let correct_parse = Statement::Function {
            description: Some("bob is a nice function".into()),
            name:        "bob".into(),
            args:        vec![
                KeyBuf { name: "a".into(), kind: Primitive::Str },
                KeyBuf { name: "b".into(), kind: Primitive::Str },
            ],
            statements:  vec![],
        };
        assert_eq!(correct_parse, parsed_if);
        let parsed_if = parse("fn bob a b --          bob is a nice function");
        assert_eq!(correct_parse, parsed_if);
        let parsed_if = parse("fn bob a b      --bob is a nice function");
        assert_eq!(correct_parse, parsed_if);
    }
}
<|endoftext|>