<commit_before>/*
 * Copyright (c) 2013, David Renshaw (dwrenshaw@gmail.com)
 *
 * See the LICENSE file in the capnproto-rust root directory.
 */

use std;
use common::*;
use endian::*;
use message::*;

pub mod InputStreamMessageReader {

    use std;
    use endian::*;
    use message::*;

    pub fn new<U : std::rt::io::Reader, T>(inputStream : &mut U,
                                           options : ReaderOptions,
                                           cont : &fn(v : &mut MessageReader) -> T) -> T {

        let firstWord = inputStream.read_bytes(8);

        let segmentCount : u32 =
            unsafe {let p : *WireValue<u32> = std::cast::transmute(firstWord.unsafe_ref(0));
                    (*p).get() + 1
                   };

        let segment0Size =
            if (segmentCount == 0) { 0 } else {
            unsafe {let p : *WireValue<u32> = std::cast::transmute(firstWord.unsafe_ref(4));
                    (*p).get()
                   }
            };

        let mut totalWords = segment0Size;

        if (segmentCount >= 512) {
            fail!("too many segments");
        }

        let mut moreSizes : ~[u32] = std::vec::from_elem((segmentCount & !1) as uint, 0u32);

        if (segmentCount > 1) {
            let moreSizesRaw = inputStream.read_bytes((4 * (segmentCount & !1)) as uint);
            for ii in range(0, segmentCount as uint - 1) {
                moreSizes[ii] = unsafe {
                    let p : *WireValue<u32> =
                        std::cast::transmute(moreSizesRaw.unsafe_ref(ii * 4));
                    (*p).get()
                };
                totalWords += moreSizes[ii];
            }
        }

        //# Don't accept a message which the receiver couldn't possibly
        //# traverse without hitting the traversal limit. Without this
        //# check, a malicious client could transmit a very large
        //# segment size to make the receiver allocate excessive space
        //# and possibly crash.
        assert!(totalWords as u64 <= options.traversalLimitInWords);

        // TODO Is this guaranteed to be word-aligned?
        let ownedSpace : ~[u8] = inputStream.read_bytes(8 * totalWords as uint);

        // TODO lazy reading like in capnp-c++. Is that possible
        // within the std::io::Reader interface?

        let segment0 : &[u8] = ownedSpace.slice(0, 8 * segment0Size as uint);

        let mut segments : ~[&[u8]] = ~[segment0];

        if (segmentCount > 1) {
            let mut offset = segment0Size;

            for ii in range(0, segmentCount as uint - 1) {
                segments.push(ownedSpace.slice(offset as uint * 8,
                                               (offset + moreSizes[ii]) as uint * 8));
                offset += moreSizes[ii];
            }
        }

        let mut result = ~MessageReader::<'a> {
            segments : segments,
            options : options
        };

        cont(result)

    }
}

pub fn writeMessage<T: std::rt::io::Writer>(outputStream : &mut T,
                                            message : &MessageBuilder) {

    let tableSize : uint = ((message.segments.len() + 2) & (!1)) * (BYTES_PER_WORD / 2);

    let mut table : ~[u8] = std::vec::from_elem(tableSize, 0u8);

    WireValue::getFromBufMut(table, 0).set((message.segments.len() - 1) as u32);

    for i in range(0, message.segments.len()) {
        WireValue::getFromBufMut(table, (i + 1) * 4).set(
            message.segmentBuilders[i].pos as u32);
    }
    if (message.segments.len() % 2 == 0) {
        // Set padding.
        WireValue::<u32>::getFromBufMut(table, (message.segments.len() + 1) * 4).set( 0 );
    }

    outputStream.write(table);

    for i in range(0, message.segments.len()) {
        let slice = message.segments[i].slice(0, message.segmentBuilders[i].pos * BYTES_PER_WORD);
        outputStream.write(slice);
    }
}
<commit_msg>explicitly annotated some other WireValues just to be safe<commit_after>/*
 * Copyright (c) 2013, David Renshaw (dwrenshaw@gmail.com)
 *
 * See the LICENSE file in the capnproto-rust root directory.
 */

use std;
use common::*;
use endian::*;
use message::*;

pub mod InputStreamMessageReader {

    use std;
    use endian::*;
    use message::*;

    pub fn new<U : std::rt::io::Reader, T>(inputStream : &mut U,
                                           options : ReaderOptions,
                                           cont : &fn(v : &mut MessageReader) -> T) -> T {

        let firstWord = inputStream.read_bytes(8);

        let segmentCount : u32 =
            unsafe {let p : *WireValue<u32> = std::cast::transmute(firstWord.unsafe_ref(0));
                    (*p).get() + 1
                   };

        let segment0Size =
            if (segmentCount == 0) { 0 } else {
            unsafe {let p : *WireValue<u32> = std::cast::transmute(firstWord.unsafe_ref(4));
                    (*p).get()
                   }
            };

        let mut totalWords = segment0Size;

        if (segmentCount >= 512) {
            fail!("too many segments");
        }

        let mut moreSizes : ~[u32] = std::vec::from_elem((segmentCount & !1) as uint, 0u32);

        if (segmentCount > 1) {
            let moreSizesRaw = inputStream.read_bytes((4 * (segmentCount & !1)) as uint);
            for ii in range(0, segmentCount as uint - 1) {
                moreSizes[ii] = unsafe {
                    let p : *WireValue<u32> =
                        std::cast::transmute(moreSizesRaw.unsafe_ref(ii * 4));
                    (*p).get()
                };
                totalWords += moreSizes[ii];
            }
        }

        //# Don't accept a message which the receiver couldn't possibly
        //# traverse without hitting the traversal limit. Without this
        //# check, a malicious client could transmit a very large
        //# segment size to make the receiver allocate excessive space
        //# and possibly crash.
        assert!(totalWords as u64 <= options.traversalLimitInWords);

        // TODO Is this guaranteed to be word-aligned?
        let ownedSpace : ~[u8] = inputStream.read_bytes(8 * totalWords as uint);

        // TODO lazy reading like in capnp-c++. Is that possible
        // within the std::io::Reader interface?

        let segment0 : &[u8] = ownedSpace.slice(0, 8 * segment0Size as uint);

        let mut segments : ~[&[u8]] = ~[segment0];

        if (segmentCount > 1) {
            let mut offset = segment0Size;

            for ii in range(0, segmentCount as uint - 1) {
                segments.push(ownedSpace.slice(offset as uint * 8,
                                               (offset + moreSizes[ii]) as uint * 8));
                offset += moreSizes[ii];
            }
        }

        let mut result = ~MessageReader::<'a> {
            segments : segments,
            options : options
        };

        cont(result)

    }
}

pub fn writeMessage<T: std::rt::io::Writer>(outputStream : &mut T,
                                            message : &MessageBuilder) {

    let tableSize : uint = ((message.segments.len() + 2) & (!1)) * (BYTES_PER_WORD / 2);

    let mut table : ~[u8] = std::vec::from_elem(tableSize, 0u8);

    WireValue::<u32>::getFromBufMut(table, 0).set((message.segments.len() - 1) as u32);

    for i in range(0, message.segments.len()) {
        WireValue::<u32>::getFromBufMut(table, (i + 1) * 4).set(
            message.segmentBuilders[i].pos as u32);
    }
    if (message.segments.len() % 2 == 0) {
        // Set padding.
        WireValue::<u32>::getFromBufMut(table, (message.segments.len() + 1) * 4).set( 0 );
    }

    outputStream.write(table);

    for i in range(0, message.segments.len()) {
        let slice = message.segments[i].slice(0, message.segmentBuilders[i].pos * BYTES_PER_WORD);
        outputStream.write(slice);
    }
}
<|endoftext|>


<commit_before>//!
//! Provides `udev` related functionality for automated device scanning.
//!
//! This module mainly provides the [`UdevBackend`](::backend::udev::UdevBackend), which constantly monitors available DRM devices
//! and notifies a user supplied [`UdevHandler`](::backend::udev::UdevHandler) of any changes.
//!
//! Additionally this contains some utility functions related to scanning.
//!
//! See also `anvil/src/udev.rs` for pure hardware backed example of a compositor utilizing this
//! backend.

use nix::sys::stat::{dev_t, stat};
use std::{
    collections::HashSet,
    ffi::OsString,
    io::Result as IoResult,
    os::unix::io::{AsRawFd, RawFd},
    path::{Path, PathBuf},
};
use udev::{Enumerator, EventType, MonitorBuilder, MonitorSocket};

use calloop::{
    generic::{Generic, SourceFd},
    mio::Interest,
    InsertError, LoopHandle, Source,
};

/// Backend to monitor available drm devices.
///
/// Provides a way to automatically scan for available gpus and notifies the
/// given handler of any changes. Can be used to provide hot-plug functionality for gpus and
/// attached monitors.
pub struct UdevBackend<T: UdevHandler + 'static> {
    devices: HashSet<dev_t>,
    monitor: MonitorSocket,
    handler: T,
    logger: ::slog::Logger,
}

impl<T: UdevHandler + 'static> AsRawFd for UdevBackend<T> {
    fn as_raw_fd(&self) -> RawFd {
        self.monitor.as_raw_fd()
    }
}

impl<T: UdevHandler + 'static> UdevBackend<T> {
    /// Creates a new [`UdevBackend`]
    ///
    /// ## Arguments
    /// `handler` - User-provided handler to respond to any detected changes
    /// `seat`    -
    /// `logger`  - slog Logger to be used by the backend and its `DrmDevices`.
    pub fn new<L, S: AsRef<str>>(mut handler: T, seat: S, logger: L) -> IoResult<UdevBackend<T>>
    where
        L: Into<Option<::slog::Logger>>,
    {
        let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_udev"));

        let devices = all_gpus(seat)?
            .into_iter()
            // Create devices
            .flat_map(|path| match stat(&path) {
                Ok(stat) => {
                    handler.device_added(stat.st_rdev, path);
                    Some(stat.st_rdev)
                }
                Err(err) => {
                    warn!(log, "Unable to get id of {:?}, Error: {:?}. Skipping", path, err);
                    None
                }
            })
            .collect();

        let monitor = MonitorBuilder::new()?.match_subsystem("drm")?.listen()?;

        Ok(UdevBackend {
            devices,
            monitor,
            handler,
            logger: log,
        })
    }
}

impl<T: UdevHandler + 'static> Drop for UdevBackend<T> {
    fn drop(&mut self) {
        for device in &self.devices {
            self.handler.device_removed(*device);
        }
    }
}

/// calloop event source associated with the Udev backend
pub type UdevSource<T> = Generic<SourceFd<UdevBackend<T>>>;

/// Binds a [`UdevBackend`] to a given [`EventLoop`](calloop::EventLoop).
///
/// Allows the backend to receive kernel events and thus to drive the [`UdevHandler`].
/// No runtime functionality can be provided without using this function.
pub fn udev_backend_bind<T: UdevHandler + 'static, Data: 'static>(
    udev: UdevBackend<T>,
    handle: &LoopHandle<Data>,
) -> Result<Source<UdevSource<T>>, InsertError<UdevSource<T>>> {
    let mut source = Generic::from_fd_source(udev);
    source.set_interest(Interest::READABLE);

    handle.insert_source(source, |evt, _| {
        evt.source.borrow_mut().0.process_events();
    })
}

impl<T: UdevHandler + 'static> UdevBackend<T> {
    fn process_events(&mut self) {
        let monitor = self.monitor.clone();
        for event in monitor {
            match event.event_type() {
                // New device
                EventType::Add => {
                    info!(self.logger, "Device Added");
                    if let (Some(path), Some(devnum)) = (event.devnode(), event.devnum()) {
                        if self.devices.insert(devnum) {
                            self.handler.device_added(devnum, path.to_path_buf());
                        }
                    }
                }
                // Device removed
                EventType::Remove => {
                    info!(self.logger, "Device Remove");
                    if let Some(devnum) = event.devnum() {
                        if self.devices.remove(&devnum) {
                            self.handler.device_removed(devnum);
                        }
                    }
                }
                // New connector
                EventType::Change => {
                    info!(self.logger, "Device Changed");
                    if let Some(devnum) = event.devnum() {
                        info!(self.logger, "Devnum: {:b}", devnum);
                        if self.devices.contains(&devnum) {
                            self.handler.device_changed(devnum);
                        } else {
                            info!(self.logger, "changed, but device not tracked by backend");
                        };
                    } else {
                        info!(self.logger, "changed, but no devnum");
                    }
                }
                _ => {}
            }
        }
    }
}

/// Handler for the [`UdevBackend`], allows to open, close and update drm devices as they change during runtime.
pub trait UdevHandler {
    /// Called when a new device is detected.
    fn device_added(&mut self, device: dev_t, path: PathBuf);
    /// Called when an open device is changed.
    ///
    /// This usually indicates that some connectors did become available or were unplugged. The handler
    /// should scan again for connected monitors and mode switch accordingly.
    fn device_changed(&mut self, device: dev_t);
    /// Called when a device was removed.
    fn device_removed(&mut self, device: dev_t);
}

/// Returns the path of the primary GPU device if any
///
/// Might be used for filtering in [`UdevHandler::device_added`] or for manual
/// [`LegacyDrmDevice`](::backend::drm::legacy::LegacyDrmDevice) initialization.
pub fn primary_gpu<S: AsRef<str>>(seat: S) -> IoResult<Option<PathBuf>> {
    let mut enumerator = Enumerator::new()?;
    enumerator.match_subsystem("drm")?;
    enumerator.match_sysname("card[0-9]*")?;

    if let Some(path) = enumerator
        .scan_devices()?
        .filter_map(|device| {
            if device
                .property_value("ID_SEAT")
                .map(|x| x.to_os_string())
                .unwrap_or_else(|| OsString::from("seat0"))
                == *seat.as_ref()
            {
                if let Ok(Some(pci)) = device.parent_with_subsystem(Path::new("pci")) {
                    if let Some(id) = pci.attribute_value("boot_vga") {
                        if id == "1" {
                            return Some(device);
                        }
                    }
                }
            };
            None
        })
        .flat_map(|device| device.devnode().map(PathBuf::from))
        .next()
    {
        Ok(Some(path))
    } else {
        all_gpus(seat).map(|all| all.into_iter().next())
    }
}

/// Returns the paths of all available GPU devices
///
/// Might be used for manual  [`LegacyDrmDevice`](::backend::drm::legacy::LegacyDrmDevice)
/// initialization.
pub fn all_gpus<S: AsRef<str>>(seat: S) -> IoResult<Vec<PathBuf>> {
    let mut enumerator = Enumerator::new()?;
    enumerator.match_subsystem("drm")?;
    enumerator.match_sysname("card[0-9]*")?;
    Ok(enumerator
        .scan_devices()?
        .filter(|device| {
            device
                .property_value("ID_SEAT")
                .map(|x| x.to_os_string())
                .unwrap_or_else(|| OsString::from("seat0"))
                == *seat.as_ref()
        })
        .flat_map(|device| device.devnode().map(PathBuf::from))
        .collect())
}
<commit_msg>Fix clippy::unnecessary::filter-map warning<commit_after>//!
//! Provides `udev` related functionality for automated device scanning.
//!
//! This module mainly provides the [`UdevBackend`](::backend::udev::UdevBackend), which constantly monitors available DRM devices
//! and notifies a user supplied [`UdevHandler`](::backend::udev::UdevHandler) of any changes.
//!
//! Additionally this contains some utility functions related to scanning.
//!
//! See also `anvil/src/udev.rs` for pure hardware backed example of a compositor utilizing this
//! backend.

use nix::sys::stat::{dev_t, stat};
use std::{
    collections::HashSet,
    ffi::OsString,
    io::Result as IoResult,
    os::unix::io::{AsRawFd, RawFd},
    path::{Path, PathBuf},
};
use udev::{Enumerator, EventType, MonitorBuilder, MonitorSocket};

use calloop::{
    generic::{Generic, SourceFd},
    mio::Interest,
    InsertError, LoopHandle, Source,
};

/// Backend to monitor available drm devices.
///
/// Provides a way to automatically scan for available gpus and notifies the
/// given handler of any changes. Can be used to provide hot-plug functionality for gpus and
/// attached monitors.
pub struct UdevBackend<T: UdevHandler + 'static> {
    devices: HashSet<dev_t>,
    monitor: MonitorSocket,
    handler: T,
    logger: ::slog::Logger,
}

impl<T: UdevHandler + 'static> AsRawFd for UdevBackend<T> {
    fn as_raw_fd(&self) -> RawFd {
        self.monitor.as_raw_fd()
    }
}

impl<T: UdevHandler + 'static> UdevBackend<T> {
    /// Creates a new [`UdevBackend`]
    ///
    /// ## Arguments
    /// `handler` - User-provided handler to respond to any detected changes
    /// `seat`    -
    /// `logger`  - slog Logger to be used by the backend and its `DrmDevices`.
    pub fn new<L, S: AsRef<str>>(mut handler: T, seat: S, logger: L) -> IoResult<UdevBackend<T>>
    where
        L: Into<Option<::slog::Logger>>,
    {
        let log = crate::slog_or_stdlog(logger).new(o!("smithay_module" => "backend_udev"));

        let devices = all_gpus(seat)?
            .into_iter()
            // Create devices
            .flat_map(|path| match stat(&path) {
                Ok(stat) => {
                    handler.device_added(stat.st_rdev, path);
                    Some(stat.st_rdev)
                }
                Err(err) => {
                    warn!(log, "Unable to get id of {:?}, Error: {:?}. Skipping", path, err);
                    None
                }
            })
            .collect();

        let monitor = MonitorBuilder::new()?.match_subsystem("drm")?.listen()?;

        Ok(UdevBackend {
            devices,
            monitor,
            handler,
            logger: log,
        })
    }
}

impl<T: UdevHandler + 'static> Drop for UdevBackend<T> {
    fn drop(&mut self) {
        for device in &self.devices {
            self.handler.device_removed(*device);
        }
    }
}

/// calloop event source associated with the Udev backend
pub type UdevSource<T> = Generic<SourceFd<UdevBackend<T>>>;

/// Binds a [`UdevBackend`] to a given [`EventLoop`](calloop::EventLoop).
///
/// Allows the backend to receive kernel events and thus to drive the [`UdevHandler`].
/// No runtime functionality can be provided without using this function.
pub fn udev_backend_bind<T: UdevHandler + 'static, Data: 'static>(
    udev: UdevBackend<T>,
    handle: &LoopHandle<Data>,
) -> Result<Source<UdevSource<T>>, InsertError<UdevSource<T>>> {
    let mut source = Generic::from_fd_source(udev);
    source.set_interest(Interest::READABLE);

    handle.insert_source(source, |evt, _| {
        evt.source.borrow_mut().0.process_events();
    })
}

impl<T: UdevHandler + 'static> UdevBackend<T> {
    fn process_events(&mut self) {
        let monitor = self.monitor.clone();
        for event in monitor {
            match event.event_type() {
                // New device
                EventType::Add => {
                    info!(self.logger, "Device Added");
                    if let (Some(path), Some(devnum)) = (event.devnode(), event.devnum()) {
                        if self.devices.insert(devnum) {
                            self.handler.device_added(devnum, path.to_path_buf());
                        }
                    }
                }
                // Device removed
                EventType::Remove => {
                    info!(self.logger, "Device Remove");
                    if let Some(devnum) = event.devnum() {
                        if self.devices.remove(&devnum) {
                            self.handler.device_removed(devnum);
                        }
                    }
                }
                // New connector
                EventType::Change => {
                    info!(self.logger, "Device Changed");
                    if let Some(devnum) = event.devnum() {
                        info!(self.logger, "Devnum: {:b}", devnum);
                        if self.devices.contains(&devnum) {
                            self.handler.device_changed(devnum);
                        } else {
                            info!(self.logger, "changed, but device not tracked by backend");
                        };
                    } else {
                        info!(self.logger, "changed, but no devnum");
                    }
                }
                _ => {}
            }
        }
    }
}

/// Handler for the [`UdevBackend`], allows to open, close and update drm devices as they change during runtime.
pub trait UdevHandler {
    /// Called when a new device is detected.
    fn device_added(&mut self, device: dev_t, path: PathBuf);
    /// Called when an open device is changed.
    ///
    /// This usually indicates that some connectors did become available or were unplugged. The handler
    /// should scan again for connected monitors and mode switch accordingly.
    fn device_changed(&mut self, device: dev_t);
    /// Called when a device was removed.
    fn device_removed(&mut self, device: dev_t);
}

/// Returns the path of the primary GPU device if any
///
/// Might be used for filtering in [`UdevHandler::device_added`] or for manual
/// [`LegacyDrmDevice`](::backend::drm::legacy::LegacyDrmDevice) initialization.
pub fn primary_gpu<S: AsRef<str>>(seat: S) -> IoResult<Option<PathBuf>> {
    let mut enumerator = Enumerator::new()?;
    enumerator.match_subsystem("drm")?;
    enumerator.match_sysname("card[0-9]*")?;

    if let Some(path) = enumerator
        .scan_devices()?
        .filter(|device| {
            let seat_name = device
                .property_value("ID_SEAT")
                .map(|x| x.to_os_string())
                .unwrap_or_else(|| OsString::from("seat0"));
            if seat_name == *seat.as_ref() {
                if let Ok(Some(pci)) = device.parent_with_subsystem(Path::new("pci")) {
                    if let Some(id) = pci.attribute_value("boot_vga") {
                        return id == "1";
                    }
                }
            }
            false
        })
        .flat_map(|device| device.devnode().map(PathBuf::from))
        .next()
    {
        Ok(Some(path))
    } else {
        all_gpus(seat).map(|all| all.into_iter().next())
    }
}

/// Returns the paths of all available GPU devices
///
/// Might be used for manual  [`LegacyDrmDevice`](::backend::drm::legacy::LegacyDrmDevice)
/// initialization.
pub fn all_gpus<S: AsRef<str>>(seat: S) -> IoResult<Vec<PathBuf>> {
    let mut enumerator = Enumerator::new()?;
    enumerator.match_subsystem("drm")?;
    enumerator.match_sysname("card[0-9]*")?;
    Ok(enumerator
        .scan_devices()?
        .filter(|device| {
            device
                .property_value("ID_SEAT")
                .map(|x| x.to_os_string())
                .unwrap_or_else(|| OsString::from("seat0"))
                == *seat.as_ref()
        })
        .flat_map(|device| device.devnode().map(PathBuf::from))
        .collect())
}
<|endoftext|>


<commit_before>//! `cargo add`
#![warn(
    missing_docs,
    missing_debug_implementations,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unstable_features,
    unused_import_braces,
    unused_qualifications
)]

#[macro_use]
extern crate error_chain;

use crate::args::{Args, Command};
use cargo_edit::{
    find, manifest_from_pkgid, registry_url, update_registry_index, Dependency, Manifest,
};
use std::borrow::Cow;
use std::io::Write;
use std::process;
use structopt::StructOpt;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use toml_edit::Item as TomlItem;

mod args;

mod errors {
    error_chain! {
        errors {
            /// Specified a dependency with both a git URL and a version.
            GitUrlWithVersion(git: String, version: String) {
                description("Specified git URL with version")
                display("Cannot specify a git URL (`{}`) with a version (`{}`).", git, version)
            }
            /// Specified multiple crates with path or git or vers
            MultipleCratesWithGitOrPathOrVers {
                description("Specified multiple crates with path or git or vers")
                display("Cannot specify multiple crates with path or git or vers")
            }
            /// Specified multiple crates with renaming.
            MultipleCratesWithRename {
                description("Specified multiple crates with rename")
                display("Cannot specify multiple crates with rename")
            }
            /// Specified multiple crates with features.
            MultipleCratesWithFeatures {
                description("Specified multiple crates with features")
                display("Cannot specify multiple crates with features")
            }
        }
        links {
            CargoEditLib(::cargo_edit::Error, ::cargo_edit::ErrorKind);
        }
        foreign_links {
            Io(::std::io::Error);
        }
    }
}

use crate::errors::*;

fn print_msg(dep: &Dependency, section: &[String], optional: bool) -> Result<()> {
    let colorchoice = if atty::is(atty::Stream::Stdout) {
        ColorChoice::Auto
    } else {
        ColorChoice::Never
    };
    let mut output = StandardStream::stdout(colorchoice);
    output.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
    write!(output, "{:>12}", "Adding")?;
    output.reset()?;
    write!(output, " {}", dep.name)?;
    if let Some(version) = dep.version() {
        write!(output, " v{}", version)?;
    } else {
        write!(output, " (unknown version)")?;
    }
    write!(output, " to")?;
    if optional {
        write!(output, " optional")?;
    }
    let section = if section.len() == 1 {
        section[0].clone()
    } else {
        format!("{} for target `{}`", &section[2], &section[1])
    };
    write!(output, " {}", section)?;
    if let Some(f) = &dep.features {
        writeln!(output, " with features: {:?}", f)?
    } else {
        writeln!(output)?
    }
    Ok(())
}

// Based on Iterator::is_sorted from nightly std; remove in favor of that when stabilized.
fn is_sorted(mut it: impl Iterator<Item = impl PartialOrd>) -> bool {
    let mut last = match it.next() {
        Some(e) => e,
        None => return true,
    };

    for curr in it {
        if curr < last {
            return false;
        }
        last = curr;
    }

    true
}

fn handle_add(args: &Args) -> Result<()> {
    let manifest_path = if let Some(ref pkgid) = args.pkgid {
        let pkg = manifest_from_pkgid(pkgid)?;
        Cow::Owned(Some(pkg.manifest_path.into_std_path_buf()))
    } else {
        Cow::Borrowed(&args.manifest_path)
    };
    let mut manifest = Manifest::open(&manifest_path)?;
    let deps = &args.parse_dependencies()?;

    if !args.offline && std::env::var("CARGO_IS_TEST").is_err() {
        let url = registry_url(
            &find(&manifest_path)?,
            args.registry.as_ref().map(String::as_ref),
        )?;
        update_registry_index(&url, args.quiet)?;
    }

    let was_sorted = manifest
        .get_table(&args.get_section())
        .map(TomlItem::as_table_mut)
        .map_or(true, |table_option| {
            table_option.map_or(true, |table| is_sorted(table.iter().map(|(name, _)| name)))
        });
    deps.iter()
        .map(|dep| {
            if !args.quiet {
                print_msg(dep, &args.get_section(), args.optional)?;
            }
            manifest
                .insert_into_table(&args.get_section(), dep)
                .map(|_| {
                    manifest
                        .get_table(&args.get_section())
                        .map(TomlItem::as_table_mut)
                        .map(|table_option| {
                            table_option.map(|table| {
                                if was_sorted || args.sort {
                                    table.sort_values();
                                }
                            })
                        })
                })
                .map_err(Into::into)
        })
        .collect::<Result<Vec<_>>>()
        .map_err(|err| {
            eprintln!("Could not edit `Cargo.toml`.\n\nERROR: {}", err);
            err
        })?;

    let mut file = Manifest::find_file(&manifest_path)?;
    manifest.write_to_file(&mut file)?;

    Ok(())
}

fn main() {
    let args: Command = Command::from_args();
    let Command::Add(args) = args;

    if let Err(err) = handle_add(&args) {
        eprintln!("Command failed due to unhandled error: {}\n", err);

        for e in err.iter().skip(1) {
            eprintln!("Caused by: {}", e);
        }

        if let Some(backtrace) = err.backtrace() {
            eprintln!("Backtrace: {:?}", backtrace);
        }

        process::exit(1);
    }
}
<commit_msg>fix(add): Lookup dep versions *after* updating (#483)<commit_after>//! `cargo add`
#![warn(
    missing_docs,
    missing_debug_implementations,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unstable_features,
    unused_import_braces,
    unused_qualifications
)]

#[macro_use]
extern crate error_chain;

use crate::args::{Args, Command};
use cargo_edit::{
    find, manifest_from_pkgid, registry_url, update_registry_index, Dependency, Manifest,
};
use std::borrow::Cow;
use std::io::Write;
use std::process;
use structopt::StructOpt;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use toml_edit::Item as TomlItem;

mod args;

mod errors {
    error_chain! {
        errors {
            /// Specified a dependency with both a git URL and a version.
            GitUrlWithVersion(git: String, version: String) {
                description("Specified git URL with version")
                display("Cannot specify a git URL (`{}`) with a version (`{}`).", git, version)
            }
            /// Specified multiple crates with path or git or vers
            MultipleCratesWithGitOrPathOrVers {
                description("Specified multiple crates with path or git or vers")
                display("Cannot specify multiple crates with path or git or vers")
            }
            /// Specified multiple crates with renaming.
            MultipleCratesWithRename {
                description("Specified multiple crates with rename")
                display("Cannot specify multiple crates with rename")
            }
            /// Specified multiple crates with features.
            MultipleCratesWithFeatures {
                description("Specified multiple crates with features")
                display("Cannot specify multiple crates with features")
            }
        }
        links {
            CargoEditLib(::cargo_edit::Error, ::cargo_edit::ErrorKind);
        }
        foreign_links {
            Io(::std::io::Error);
        }
    }
}

use crate::errors::*;

fn print_msg(dep: &Dependency, section: &[String], optional: bool) -> Result<()> {
    let colorchoice = if atty::is(atty::Stream::Stdout) {
        ColorChoice::Auto
    } else {
        ColorChoice::Never
    };
    let mut output = StandardStream::stdout(colorchoice);
    output.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
    write!(output, "{:>12}", "Adding")?;
    output.reset()?;
    write!(output, " {}", dep.name)?;
    if let Some(version) = dep.version() {
        write!(output, " v{}", version)?;
    } else {
        write!(output, " (unknown version)")?;
    }
    write!(output, " to")?;
    if optional {
        write!(output, " optional")?;
    }
    let section = if section.len() == 1 {
        section[0].clone()
    } else {
        format!("{} for target `{}`", &section[2], &section[1])
    };
    write!(output, " {}", section)?;
    if let Some(f) = &dep.features {
        writeln!(output, " with features: {:?}", f)?
    } else {
        writeln!(output)?
    }
    Ok(())
}

// Based on Iterator::is_sorted from nightly std; remove in favor of that when stabilized.
fn is_sorted(mut it: impl Iterator<Item = impl PartialOrd>) -> bool {
    let mut last = match it.next() {
        Some(e) => e,
        None => return true,
    };

    for curr in it {
        if curr < last {
            return false;
        }
        last = curr;
    }

    true
}

fn handle_add(args: &Args) -> Result<()> {
    let manifest_path = if let Some(ref pkgid) = args.pkgid {
        let pkg = manifest_from_pkgid(pkgid)?;
        Cow::Owned(Some(pkg.manifest_path.into_std_path_buf()))
    } else {
        Cow::Borrowed(&args.manifest_path)
    };
    let mut manifest = Manifest::open(&manifest_path)?;

    if !args.offline && std::env::var("CARGO_IS_TEST").is_err() {
        let url = registry_url(
            &find(&manifest_path)?,
            args.registry.as_ref().map(String::as_ref),
        )?;
        update_registry_index(&url, args.quiet)?;
    }
    let deps = &args.parse_dependencies()?;

    let was_sorted = manifest
        .get_table(&args.get_section())
        .map(TomlItem::as_table_mut)
        .map_or(true, |table_option| {
            table_option.map_or(true, |table| is_sorted(table.iter().map(|(name, _)| name)))
        });
    deps.iter()
        .map(|dep| {
            if !args.quiet {
                print_msg(dep, &args.get_section(), args.optional)?;
            }
            manifest
                .insert_into_table(&args.get_section(), dep)
                .map(|_| {
                    manifest
                        .get_table(&args.get_section())
                        .map(TomlItem::as_table_mut)
                        .map(|table_option| {
                            table_option.map(|table| {
                                if was_sorted || args.sort {
                                    table.sort_values();
                                }
                            })
                        })
                })
                .map_err(Into::into)
        })
        .collect::<Result<Vec<_>>>()
        .map_err(|err| {
            eprintln!("Could not edit `Cargo.toml`.\n\nERROR: {}", err);
            err
        })?;

    let mut file = Manifest::find_file(&manifest_path)?;
    manifest.write_to_file(&mut file)?;

    Ok(())
}

fn main() {
    let args: Command = Command::from_args();
    let Command::Add(args) = args;

    if let Err(err) = handle_add(&args) {
        eprintln!("Command failed due to unhandled error: {}\n", err);

        for e in err.iter().skip(1) {
            eprintln!("Caused by: {}", e);
        }

        if let Some(backtrace) = err.backtrace() {
            eprintln!("Backtrace: {:?}", backtrace);
        }

        process::exit(1);
    }
}
<|endoftext|>


<commit_before>//! Command-line iterface to substudy.

extern crate rustc_serialize;
extern crate docopt;
extern crate env_logger;

extern crate substudy;

use docopt::Docopt;
use std::path::Path;
use std::process::exit;

use substudy::err::Result;
use substudy::srt::SubtitleFile;
use substudy::align::combine_files;
use substudy::video;
use substudy::export;

const USAGE: &'static str = "
Subtitle processing tools for students of foreign languages

Usage: substudy clean <subs>
       substudy combine <foreign-subs> <native-subs>
       substudy export csv <video> <foreign-subs> [<native-subs>]
       substudy export review <video> <foreign-subs> [<native-subs>]
       substudy export tracks <video> <foreign-subs>
       substudy list tracks <video>
       substudy --help

For now, all subtitles must be in *.srt format. Many common encodings
will be automatically detected, but try converting to UTF-8 if you
have problems.
";

#[derive(Debug, RustcDecodable)]
struct Args {
    cmd_clean: bool,
    cmd_combine: bool,
    cmd_export: bool,
    cmd_csv: bool,
    cmd_review: bool,
    cmd_tracks: bool,
    arg_subs: String,
    arg_foreign_subs: String,
    arg_native_subs: Option<String>,
    arg_video: String,
}

fn export_type(args: &Args) -> &str {
    match *args {
        Args{cmd_csv: true, ..} => "csv",
        Args{cmd_review: true, ..} => "review",
        Args{cmd_tracks: true, ..} => "tracks",
        _ => panic!("Cannot determine export type: {:?}", args),
    }
}

// Choose and run the appropriate command.
fn run(args: &Args) -> Result<()> {
    match *args {
        Args{cmd_clean: true, arg_subs: ref path, ..} =>
            cmd_clean(&Path::new(path)),
        Args{cmd_combine: true, arg_foreign_subs: ref path1,
             arg_native_subs: Some(ref path2), ..} =>
            cmd_combine(&Path::new(path1), &Path::new(path2)),
        Args{cmd_export: true, arg_video: ref video_path,
             arg_foreign_subs: ref foreign_path,
             arg_native_subs: ref native_path, ..} =>
        {
            cmd_export(export_type(args), &Path::new(video_path),
                       &Path::new(foreign_path),
                       native_path.as_ref().map(|p| Path::new(p)))
        }
        Args{cmd_tracks: true, arg_video: ref path, ..} =>
            cmd_tracks(&Path::new(path)),
        _ => panic!("Unexpected argument combination: {:?}", args)
    }
}

fn cmd_clean(path: &Path) -> Result<()> {
    let file1 = try!(SubtitleFile::cleaned_from_path(path));
    print!("{}", file1.to_string());
    Ok(())
}

fn cmd_combine(path1: &Path, path2: &Path) -> Result<()> {
    let file1 = try!(SubtitleFile::cleaned_from_path(path1));
    let file2 = try!(SubtitleFile::cleaned_from_path(path2));
    print!("{}", combine_files(&file1, &file2).to_string());
    Ok(())
}

fn cmd_tracks(path: &Path) -> Result<()> {
    let v = try!(video::Video::new(path));
    for stream in v.streams() {
        let lang = stream.language();
        let lang_str = lang.map(|l| l.as_str().to_owned())
            .unwrap_or("??".to_owned());
        println!("#{} {} {:?}", stream.index, &lang_str, stream.codec_type);
    }
    Ok(())
}

fn cmd_export(kind: &str, video_path: &Path, foreign_sub_path: &Path,
              native_sub_path: Option<&Path>) ->
    Result<()>
{
    // Load our input files.
    let video = try!(video::Video::new(video_path));
    let foreign_subs = try!(SubtitleFile::cleaned_from_path(foreign_sub_path));
    let native_subs = match native_sub_path {
        None => None,
        Some(p) => Some(try!(SubtitleFile::cleaned_from_path(p))),
    };

    let mut exporter =
        try!(export::Exporter::new(video, foreign_subs, native_subs, kind));
    match kind {
        "csv" => try!(export::export_csv(&mut exporter)),
        "review" => try!(export::export_review(&mut exporter)),
        "tracks" => try!(export::export_tracks(&mut exporter)),
        _ => panic!("Uknown export type: {}", kind),
    }

    Ok(())
}

fn main() {
    env_logger::init().unwrap();

    // Parse our command-line arguments using docopt (very shiny).
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    // Decide which command to run, and run it.
    if let Err(ref err) = run(&args) {
        // Print any error and exit with an error status.
        println!("{}", err);
        exit(1);
    }
}
<commit_msg>Add --version flag to substudy<commit_after>//! Command-line iterface to substudy.

extern crate rustc_serialize;
extern crate docopt;
extern crate env_logger;

extern crate substudy;

use docopt::Docopt;
use std::path::Path;
use std::process::exit;

use substudy::err::Result;
use substudy::srt::SubtitleFile;
use substudy::align::combine_files;
use substudy::video;
use substudy::export;

const USAGE: &'static str = "
Subtitle processing tools for students of foreign languages

Usage: substudy clean <subs>
       substudy combine <foreign-subs> <native-subs>
       substudy export csv <video> <foreign-subs> [<native-subs>]
       substudy export review <video> <foreign-subs> [<native-subs>]
       substudy export tracks <video> <foreign-subs>
       substudy list tracks <video>
       substudy --help
       substudy --version

For now, all subtitles must be in *.srt format. Many common encodings
will be automatically detected, but try converting to UTF-8 if you
have problems.
";

#[derive(Debug, RustcDecodable)]
struct Args {
    cmd_clean: bool,
    cmd_combine: bool,
    cmd_export: bool,
    cmd_csv: bool,
    cmd_review: bool,
    cmd_tracks: bool,
    arg_subs: String,
    arg_foreign_subs: String,
    arg_native_subs: Option<String>,
    arg_video: String,
    flag_version: bool,
}

fn export_type(args: &Args) -> &str {
    match *args {
        Args{cmd_csv: true, ..} => "csv",
        Args{cmd_review: true, ..} => "review",
        Args{cmd_tracks: true, ..} => "tracks",
        _ => panic!("Cannot determine export type: {:?}", args),
    }
}

// Choose and run the appropriate command.
fn run(args: &Args) -> Result<()> {
    match *args {
        Args{flag_version: true, ..} =>
            cmd_version(),
        Args{cmd_clean: true, arg_subs: ref path, ..} =>
            cmd_clean(&Path::new(path)),
        Args{cmd_combine: true, arg_foreign_subs: ref path1,
             arg_native_subs: Some(ref path2), ..} =>
            cmd_combine(&Path::new(path1), &Path::new(path2)),
        Args{cmd_export: true, arg_video: ref video_path,
             arg_foreign_subs: ref foreign_path,
             arg_native_subs: ref native_path, ..} =>
        {
            cmd_export(export_type(args), &Path::new(video_path),
                       &Path::new(foreign_path),
                       native_path.as_ref().map(|p| Path::new(p)))
        }
        Args{cmd_tracks: true, arg_video: ref path, ..} =>
            cmd_tracks(&Path::new(path)),
        _ => panic!("Unexpected argument combination: {:?}", args)
    }
}

fn cmd_version() -> Result<()> {
    println!("substudy {}", env!("CARGO_PKG_VERSION"));
    Ok(())
}

fn cmd_clean(path: &Path) -> Result<()> {
    let file1 = try!(SubtitleFile::cleaned_from_path(path));
    print!("{}", file1.to_string());
    Ok(())
}

fn cmd_combine(path1: &Path, path2: &Path) -> Result<()> {
    let file1 = try!(SubtitleFile::cleaned_from_path(path1));
    let file2 = try!(SubtitleFile::cleaned_from_path(path2));
    print!("{}", combine_files(&file1, &file2).to_string());
    Ok(())
}

fn cmd_tracks(path: &Path) -> Result<()> {
    let v = try!(video::Video::new(path));
    for stream in v.streams() {
        let lang = stream.language();
        let lang_str = lang.map(|l| l.as_str().to_owned())
            .unwrap_or("??".to_owned());
        println!("#{} {} {:?}", stream.index, &lang_str, stream.codec_type);
    }
    Ok(())
}

fn cmd_export(kind: &str, video_path: &Path, foreign_sub_path: &Path,
              native_sub_path: Option<&Path>) ->
    Result<()>
{
    // Load our input files.
    let video = try!(video::Video::new(video_path));
    let foreign_subs = try!(SubtitleFile::cleaned_from_path(foreign_sub_path));
    let native_subs = match native_sub_path {
        None => None,
        Some(p) => Some(try!(SubtitleFile::cleaned_from_path(p))),
    };

    let mut exporter =
        try!(export::Exporter::new(video, foreign_subs, native_subs, kind));
    match kind {
        "csv" => try!(export::export_csv(&mut exporter)),
        "review" => try!(export::export_review(&mut exporter)),
        "tracks" => try!(export::export_tracks(&mut exporter)),
        _ => panic!("Uknown export type: {}", kind),
    }

    Ok(())
}

fn main() {
    env_logger::init().unwrap();

    // Parse our command-line arguments using docopt (very shiny).
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    // Decide which command to run, and run it.
    if let Err(ref err) = run(&args) {
        // Print any error and exit with an error status.
        println!("{}", err);
        exit(1);
    }
}
<|endoftext|>


<commit_before>#![feature(test)]
extern crate test;
extern crate tera;

extern crate rendering;
extern crate config;
extern crate front_matter;

use std::collections::HashMap;

use tera::Tera;
use rendering::{RenderContext, render_content, render_shortcodes};
use front_matter::InsertAnchor;
use config::Config;

static CONTENT: &'static str = r#"
# Modus cognitius profanam ne duae virtutis mundi

## Ut vita

Lorem markdownum litora, care ponto nomina, et ut aspicit gelidas sui et
purpureo genuit. Tamen colla venientis [delphina](http://nil-sol.com/ecquis)
Tusci et temptata citaeque curam isto ubi vult vulnere reppulit.

- Seque vidit flendoque de quodam
- Dabit minimos deiecto caputque noctis pluma
- Leti coniunx est Helicen
- Illius pulvereumque Icare inpositos
- Vivunt pereo pluvio tot ramos Olenios gelidis
- Quater teretes natura inde

### A subsection

Protinus dicunt, breve per, et vivacis genus Orphei munere. Me terram [dimittere
casside](http://corpus.org/) pervenit saxo primoque frequentat genuum sorori
praeferre causas Libys. Illud in serpit adsuetam utrimque nunc haberent,
**terrae si** veni! Hectoreis potes sumite [Mavortis retusa](http://tua.org/)
granum captantur potuisse Minervae, frugum.

> Clivo sub inprovisoque nostrum minus fama est, discordia patrem petebat precatur
absumitur, poena per sit. Foramina *tamen cupidine* memor supplex tollentes
dictum unam orbem, Anubis caecae. Viderat formosior tegebat satis, Aethiopasque
sit submisso coniuge tristis ubi!

## Praeceps Corinthus totidem quem crus vultum cape

```rs
#[derive(Debug)]
pub struct Site {
    /// The base path of the gutenberg site
    pub base_path: PathBuf,
    /// The parsed config for the site
    pub config: Config,
    pub pages: HashMap<PathBuf, Page>,
    pub sections: HashMap<PathBuf, Section>,
    pub tera: Tera,
    live_reload: bool,
    output_path: PathBuf,
    static_path: PathBuf,
    pub tags: Option<Taxonomy>,
    pub categories: Option<Taxonomy>,
    /// A map of all .md files (section and pages) and their permalink
    /// We need that if there are relative links in the content that need to be resolved
    pub permalinks: HashMap<String, String>,
}
```

## More stuff
And a shortcode:

{{ youtube(id="my_youtube_id") }}

### Another subsection
Gotta make the toc do a little bit of work

# A big title

- hello
- world
- !

```py
if __name__ == "__main__":
    gen_site("basic-blog", [""], 250, paginate=True)
```
"#;

#[bench]
fn bench_render_content_with_highlighting(b: &mut test::Bencher) {
    let mut tera = Tera::default();
    tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
    let permalinks_ctx = HashMap::new();
    let config = Config::default();
    let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
    b.iter(|| render_content(CONTENT, &context).unwrap());
}

#[bench]
fn bench_render_content_without_highlighting(b: &mut test::Bencher) {
    let mut tera = Tera::default();
    tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
    let permalinks_ctx = HashMap::new();
    let mut config = Config::default();
    config.highlight_code = false;
    let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
    b.iter(|| render_content(CONTENT, &context).unwrap());
}

#[bench]
fn bench_render_content_no_shortcode(b: &mut test::Bencher) {
    let tera = Tera::default();
    let content2 = CONTENT.replace(r#"{{ youtube(id="my_youtube_id") }}"#, "");
    let mut config = Config::default();
    config.highlight_code = false;
    let permalinks_ctx = HashMap::new();
    let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);

    b.iter(|| render_content(&content2, &context).unwrap());
}

#[bench]
fn bench_render_shortcodes_one_present(b: &mut test::Bencher) {
    let mut tera = Tera::default();
    tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();

    b.iter(|| render_shortcodes(CONTENT, &tera, &Config::default()));
}

<commit_msg>Fix rendering benchmark<commit_after>#![feature(test)]
extern crate test;
extern crate tera;

extern crate rendering;
extern crate config;
extern crate front_matter;

use std::collections::HashMap;

use tera::Tera;
use rendering::{RenderContext, render_content, render_shortcodes};
use front_matter::InsertAnchor;
use config::Config;

static CONTENT: &'static str = r#"
# Modus cognitius profanam ne duae virtutis mundi

## Ut vita

Lorem markdownum litora, care ponto nomina, et ut aspicit gelidas sui et
purpureo genuit. Tamen colla venientis [delphina](http://nil-sol.com/ecquis)
Tusci et temptata citaeque curam isto ubi vult vulnere reppulit.

- Seque vidit flendoque de quodam
- Dabit minimos deiecto caputque noctis pluma
- Leti coniunx est Helicen
- Illius pulvereumque Icare inpositos
- Vivunt pereo pluvio tot ramos Olenios gelidis
- Quater teretes natura inde

### A subsection

Protinus dicunt, breve per, et vivacis genus Orphei munere. Me terram [dimittere
casside](http://corpus.org/) pervenit saxo primoque frequentat genuum sorori
praeferre causas Libys. Illud in serpit adsuetam utrimque nunc haberent,
**terrae si** veni! Hectoreis potes sumite [Mavortis retusa](http://tua.org/)
granum captantur potuisse Minervae, frugum.

> Clivo sub inprovisoque nostrum minus fama est, discordia patrem petebat precatur
absumitur, poena per sit. Foramina *tamen cupidine* memor supplex tollentes
dictum unam orbem, Anubis caecae. Viderat formosior tegebat satis, Aethiopasque
sit submisso coniuge tristis ubi!

## Praeceps Corinthus totidem quem crus vultum cape

```rs
#[derive(Debug)]
pub struct Site {
    /// The base path of the gutenberg site
    pub base_path: PathBuf,
    /// The parsed config for the site
    pub config: Config,
    pub pages: HashMap<PathBuf, Page>,
    pub sections: HashMap<PathBuf, Section>,
    pub tera: Tera,
    live_reload: bool,
    output_path: PathBuf,
    static_path: PathBuf,
    pub tags: Option<Taxonomy>,
    pub categories: Option<Taxonomy>,
    /// A map of all .md files (section and pages) and their permalink
    /// We need that if there are relative links in the content that need to be resolved
    pub permalinks: HashMap<String, String>,
}
```

## More stuff
And a shortcode:

{{ youtube(id="my_youtube_id") }}

### Another subsection
Gotta make the toc do a little bit of work

# A big title

- hello
- world
- !

```py
if __name__ == "__main__":
    gen_site("basic-blog", [""], 250, paginate=True)
```
"#;

#[bench]
fn bench_render_content_with_highlighting(b: &mut test::Bencher) {
    let mut tera = Tera::default();
    tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
    let permalinks_ctx = HashMap::new();
    let config = Config::default();
    let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
    b.iter(|| render_content(CONTENT, &context).unwrap());
}

#[bench]
fn bench_render_content_without_highlighting(b: &mut test::Bencher) {
    let mut tera = Tera::default();
    tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
    let permalinks_ctx = HashMap::new();
    let mut config = Config::default();
    config.highlight_code = false;
    let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);
    b.iter(|| render_content(CONTENT, &context).unwrap());
}

#[bench]
fn bench_render_content_no_shortcode(b: &mut test::Bencher) {
    let tera = Tera::default();
    let content2 = CONTENT.replace(r#"{{ youtube(id="my_youtube_id") }}"#, "");
    let mut config = Config::default();
    config.highlight_code = false;
    let permalinks_ctx = HashMap::new();
    let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);

    b.iter(|| render_content(&content2, &context).unwrap());
}

#[bench]
fn bench_render_shortcodes_one_present(b: &mut test::Bencher) {
    let mut tera = Tera::default();
    tera.add_raw_template("shortcodes/youtube.html", "{{id}}").unwrap();
    let config = Config::default();
    let permalinks_ctx = HashMap::new();
    let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);

    b.iter(|| render_shortcodes(CONTENT, &context));
}

<|endoftext|>


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

use std::env;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::sync::Mutex;

use slog::{self, Drain, OwnedKVList, Record};
use time;

use tikv;

/// A logger that add a test case tag before each line of log.
struct CaseTraceLogger {
    f: Option<Mutex<File>>,
}

impl Drain for CaseTraceLogger {
    type Ok = ();
    type Err = slog::Never;
    fn log(&self, record: &Record<'_>, _: &OwnedKVList) -> Result<Self::Ok, Self::Err> {
        let tag = tikv::util::get_tag_from_thread_name().map_or_else(|| "".into(), |s| s + " ");

        let t = time::now();
        let time_str = time::strftime("%Y/%m/%d %H:%M:%S.%f", &t).unwrap();
        // todo allow formatter to be configurable.
        let message = format!(
            "{}{} {}:{}: [{}] {}\n",
            tag,
            &time_str[..time_str.len() - 6],
            record.file().rsplit('/').nth(0).unwrap(),
            record.line(),
            record.level(),
            record.msg(),
        );

        if let Some(ref out) = self.f {
            let mut w = out.lock().unwrap();
            let _ = w.write(message.as_bytes());
        } else {
            let mut w = io::stderr();
            let _ = w.write(message.as_bytes());
        }
        Ok(())
    }
}

impl Drop for CaseTraceLogger {
    fn drop(&mut self) {
        if let Some(ref w) = self.f {
            w.lock().unwrap().flush().unwrap();
        }
    }
}

// A help function to initial logger.
pub fn init_log_for_test() {
    let output = env::var("LOG_FILE").ok();
    let level = tikv::util::logger::get_level_by_string(
        &env::var("LOG_LEVEL").unwrap_or_else(|_| "debug".to_owned()),
    )
    .unwrap();
    let writer = output.map(|f| Mutex::new(File::create(f).unwrap()));
    // we don't mind set it multiple times.
    let drain = CaseTraceLogger { f: writer };

    // Collects following targets.
    const ENABLED_TARGETS: &[&str] = &[
        "tikv::",
        "tests::",
        "benches::",
        "integrations::",
        "failpoints::",
        "raft::",
        // Collects logs for test components.
        "test_",
    ];
    let filtered = drain.filter(|record| {
        ENABLED_TARGETS
            .iter()
            .any(|target| record.module().starts_with(target))
    });

    // CaseTraceLogger relies on test's thread name, however slog_async has
    // its own thread, and the name is "".
    // TODO: Enable the slog_async when the [Custom test frameworks][1] is mature,
    //       and hook the slog_async logger to every test cases.
    //
    // [1]: https://github.com/rust-lang/rfcs/blob/master/text/2318-custom-test-frameworks.md
    tikv::util::logger::init_log(
        filtered, level, false, // disable async drainer
        true,  // init std log
    )
    .unwrap()
}
<commit_msg>Serialize KVs in CI log (#4371)<commit_after>// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

use std::env;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::sync::Mutex;

use slog::{self, Drain, OwnedKVList, Record};
use time;

use tikv;

struct Serializer<'a>(&'a mut dyn std::io::Write);

impl<'a> slog::ser::Serializer for Serializer<'a> {
    fn emit_arguments(&mut self, key: slog::Key, val: &std::fmt::Arguments<'_>) -> slog::Result {
        write!(self.0, ", {}: {}", key, val)?;
        Ok(())
    }
}

/// A logger that add a test case tag before each line of log.
struct CaseTraceLogger {
    f: Option<Mutex<File>>,
}

impl CaseTraceLogger {
    fn write_log(
        w: &mut dyn ::std::io::Write,
        record: &Record<'_>,
        values: &OwnedKVList,
    ) -> Result<(), ::std::io::Error> {
        use slog::KV;

        let tag = tikv::util::get_tag_from_thread_name().map_or_else(|| "".to_owned(), |s| s + " ");
        let t = time::now();
        let time_str = time::strftime("%Y/%m/%d %H:%M:%S.%f", &t).unwrap();
        write!(
            w,
            "{}{} {}:{}: [{}] {}",
            tag,
            &time_str[..time_str.len() - 6],
            record.file().rsplit('/').nth(0).unwrap(),
            record.line(),
            record.level(),
            record.msg(),
        )?;
        {
            let mut s = Serializer(w);
            record.kv().serialize(record, &mut s)?;
            values.serialize(record, &mut s)?;
        }
        writeln!(w)?;
        w.flush()?;
        Ok(())
    }
}

impl Drain for CaseTraceLogger {
    type Ok = ();
    type Err = slog::Never;
    fn log(&self, record: &Record<'_>, values: &OwnedKVList) -> Result<Self::Ok, Self::Err> {
        if let Some(ref out) = self.f {
            let mut w = out.lock().unwrap();
            let _ = Self::write_log(&mut *w, record, values);
        } else {
            let mut w = io::stderr();
            let _ = Self::write_log(&mut w, record, values);
        }
        Ok(())
    }
}

impl Drop for CaseTraceLogger {
    fn drop(&mut self) {
        if let Some(ref w) = self.f {
            w.lock().unwrap().flush().unwrap();
        }
    }
}

// A help function to initial logger.
pub fn init_log_for_test() {
    let output = env::var("LOG_FILE").ok();
    let level = tikv::util::logger::get_level_by_string(
        &env::var("LOG_LEVEL").unwrap_or_else(|_| "debug".to_owned()),
    )
    .unwrap();
    let writer = output.map(|f| Mutex::new(File::create(f).unwrap()));
    // we don't mind set it multiple times.
    let drain = CaseTraceLogger { f: writer };

    // Collects following targets.
    const ENABLED_TARGETS: &[&str] = &[
        "tikv::",
        "tests::",
        "benches::",
        "integrations::",
        "failpoints::",
        "raft::",
        // Collects logs for test components.
        "test_",
    ];
    let filtered = drain.filter(|record| {
        ENABLED_TARGETS
            .iter()
            .any(|target| record.module().starts_with(target))
    });

    // CaseTraceLogger relies on test's thread name, however slog_async has
    // its own thread, and the name is "".
    // TODO: Enable the slog_async when the [Custom test frameworks][1] is mature,
    //       and hook the slog_async logger to every test cases.
    //
    // [1]: https://github.com/rust-lang/rfcs/blob/master/text/2318-custom-test-frameworks.md
    tikv::util::logger::init_log(
        filtered, level, false, // disable async drainer
        true,  // init std log
    )
    .unwrap()
}
<|endoftext|>


<commit_before>//! Pull API handler.
use std::str;
use std::sync::Arc;

use futures::{Finished, finished};
use tokio_service::Service;
use tokio_core::net::TcpStream;
use tk_bufstream::IoBuf;
use minihttp::{Error, Request};
use minihttp::{ResponseFn, Status};
use minihttp::enums::Method;
use rustc_serialize::json::{self, Json};

use {Pickler};
use config::ConfigCell;
use response::DebugInfo;
use default_error_page::write_error_page;
use intern::{Topic, Lattice};

use super::{parse_cid, ProcessorPool};
use super::processor::{Action, Delta};

/// Chat Backend Http handler.
#[derive(Clone)]
pub struct ChatBackend {
    pub config: ConfigCell,
    pub chat_pool: ProcessorPool,
}

impl Service for ChatBackend {
    type Request = Request;
    type Response = ResponseFn<Finished<IoBuf<TcpStream>, Error>, TcpStream>;
    type Error = Error;
    type Future = Finished<Self::Response, Error>;

    fn call(&self, req: Request) -> Self::Future {
        let cfg = self.config.get();
        let status = self.serve(&req);
        finished(ResponseFn::new(move |res| {
            let res = Pickler(res, cfg.clone(), DebugInfo::new(&req));
            write_error_page(status, res).done()
        }))
    }
}

impl ChatBackend {

    fn serve(&self, req: &Request) -> Status {
        use self::ChatRoute::*;

        let route = match match_route(&req.method, req.path.as_str()) {
            Some(r) => r,
            None => return Status::NotFound
        };
        let payload = match req.body {
            Some(ref body) => {
                str::from_utf8(&body.data[..]).ok()
                    .and_then(|s| Json::from_str(s).ok())
            }
            None => None
        };
        if route.expect_data() && payload.is_none() {
            return Status::BadRequest;
        }
        let action = match route {
            TopicSubscribe(client_id, topic) => {
                let cid = parse_cid(client_id);
                Action::Subscribe {
                    conn_id: cid,
                    topic: topic,
                }
            }
            TopicUnsubscribe(client_id, topic) => {
                let cid = parse_cid(client_id);
                Action::Unsubscribe {
                    conn_id: cid,
                    topic: topic,
                }
            }
            LatticeSubscribe(client_id, namespace) => {
                let cid = parse_cid(client_id);
                Action::Attach {
                    namespace: namespace,
                    conn_id: cid,
                }
            }
            LatticeUnsubscribe(client_id, namespace) => {
                let cid = parse_cid(client_id);
                let delta = match decode_delta(req) {
                    Ok(delta) => delta,
                    Err(_) => return Status::BadRequest,
                };
                self.chat_pool.send(Action::Lattice {
                    namespace: namespace.clone(),
                    delta: delta,
                });
                Action::Detach {
                    namespace: namespace,
                    conn_id: cid,
                }
            }
            TopicPublish(topic) => {
                let data = Arc::new(payload.unwrap());
                Action::Publish {
                    topic: topic,
                    data: data,
                }
            }
            LatticeUpdate(namespace) => {
                let delta = match decode_delta(req) {
                    Ok(delta) => delta,
                    Err(_) => return Status::BadRequest,
                };
                Action::Lattice {
                    namespace: namespace,
                    delta: delta,
                }
            }
        };
        self.chat_pool.send(action);
        Status::NoContent
    }

}

fn decode_delta(req: &Request) -> Result<Delta, json::DecoderError>
{
    let body = req.body.as_ref().unwrap();
    let body = str::from_utf8(&body.data[..]).unwrap();
    json::decode::<Delta>(body)
}

fn match_route(method: &Method, path: &str) -> Option<ChatRoute> {
    use minihttp::enums::Method::*;
    use self::ChatRoute::*;

    if !path.starts_with("/v1/") {
        return None
    }
    let mut it = path.split("/").skip(2);   // skip '' & 'v1'
    let route = match (method, it.next()) {
        (&Post, Some(kind)) => {
            if !it.next().map(|x| x.len() > 0).unwrap_or(false) {
                return None
            }
            match kind {
                "lattice" => {
                    let (_, namespace) = path.split_at("/v1/lattice/".len());
                    let namespace = namespace.replace("/", ".")
                        .parse().unwrap();
                    ChatRoute::LatticeUpdate(namespace)
                }
                "publish" => {
                    let (_, topic) = path.split_at("/v1/publish/".len());
                    let topic = topic.replace("/", ".")
                        .parse().unwrap();
                    TopicPublish(topic)
                }
                _ => return None
            }
        }
        (&Put, Some("connection")) |
        (&Delete, Some("connection")) => {
            let id = match it.next() {
                Some(id) if id.len() > 0 => id,
                _ => return None,
            };
            let kind = it.next();
            if !it.next().map(|x| x.len() > 0).unwrap_or(false) {
                return None
            }
            match kind {
                Some("subscriptions") => {
                    let (_, topic) = path.split_at(
                        "/v1/connection//subscriptions/".len() + id.len());
                    let topic = topic.replace("/", ".")
                        .parse().unwrap();
                    if method == &Put {
                        TopicSubscribe(id.to_string(), topic)
                    } else {
                        TopicUnsubscribe(id.to_string(), topic)
                    }
                }
                Some("lattices") => {
                    let (_, namespace) = path.split_at(
                        "/v1/connection//lattices/".len() + id.len());
                    let namespace = namespace.replace("/", ".")
                        .parse().unwrap();
                    if method == &Put {
                        LatticeSubscribe(
                            id.to_string(), namespace)
                    } else {
                        LatticeUnsubscribe(
                            id.to_string(), namespace)
                    }
                }
                _ => return None
            }
        }
        _ => return None
    };
    Some(route)
}

#[derive(Debug, PartialEq)]
pub enum ChatRoute {
    TopicSubscribe(String, Topic),
    TopicUnsubscribe(String, Topic),
    LatticeSubscribe(String, Lattice),
    LatticeUnsubscribe(String, Lattice),
    TopicPublish(Topic),
    LatticeUpdate(Lattice),
}

impl ChatRoute {
    fn expect_data(&self) -> bool {
        use self::ChatRoute::*;
        match *self {
            TopicSubscribe(_, _) => false,
            TopicUnsubscribe(_, _) => false,
            LatticeSubscribe(_, _) => true,
            LatticeUnsubscribe(_, _) => false,
            TopicPublish(_) => true,
            LatticeUpdate(_) => true,
        }
    }
}

#[cfg(test)]
mod test {
    use minihttp::enums::Method;
    use string_intern::Symbol;

    use super::match_route;
    use super::ChatRoute::*;

    #[test]
    fn match_topic_publish() {
        let path = "/v1/publish/test-chat/room1";
        let route = match_route(&Method::Post, path).unwrap();
        assert_eq!(route, TopicPublish(Symbol::from("test-chat.room1")));
    }

    #[test]
    fn match_lattice_update() {
        let path = "/v1/lattice/test-chat/rooms";
        let route = match_route(&Method::Post, path).unwrap();
        assert_eq!(route, LatticeUpdate(Symbol::from("test-chat.rooms")));
    }

    #[test]
    fn match_topic_subscribe() {
        let path = "/v1/connection/abcde/subscriptions/test-chat/room1";
        let route = match_route(&Method::Put, path).unwrap();
        assert_eq!(route, TopicSubscribe(
            "abcde".to_string(), Symbol::from("test-chat.room1")));
    }

    #[test]
    fn match_topic_unsubscribe() {
        let path = "/v1/connection/abcde/subscriptions/test-chat/room1";
        let route = match_route(&Method::Delete, path).unwrap();
        assert_eq!(route, TopicUnsubscribe(
            "abcde".to_string(), Symbol::from("test-chat.room1")));
    }

    #[test]
    fn match_lattice_subscribe() {
        let path = "/v1/connection/abcde/lattices/test-chat/room1";
        let route = match_route(&Method::Put, path).unwrap();
        assert_eq!(route, LatticeSubscribe(
            "abcde".to_string(), Symbol::from("test-chat.room1")));
    }

    #[test]
    fn match_lattice_unsubscribe() {
        let path = "/v1/connection/abcde/lattices/test-chat/room1";
        let route = match_route(&Method::Delete, path).unwrap();
        assert_eq!(route, LatticeUnsubscribe(
            "abcde".to_string(), Symbol::from("test-chat.room1")));
    }

    #[test]
    fn no_matches() {
        let path = "/v/";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/publish";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/publish/";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/publish/test-chat/room1";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/lattice/test-chat/rooms";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/connections//subscriptions/";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/connections/abc/subscriptions/";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());
    }
}
<commit_msg>fix backend lattice subscribe<commit_after>//! Pull API handler.
use std::str;
use std::sync::Arc;

use futures::{Finished, finished};
use tokio_service::Service;
use tokio_core::net::TcpStream;
use tk_bufstream::IoBuf;
use minihttp::{Error, Request};
use minihttp::{ResponseFn, Status};
use minihttp::enums::Method;
use rustc_serialize::json::{self, Json};

use {Pickler};
use config::ConfigCell;
use response::DebugInfo;
use default_error_page::write_error_page;
use intern::{Topic, Lattice};

use super::{parse_cid, ProcessorPool};
use super::processor::{Action, Delta};

/// Chat Backend Http handler.
#[derive(Clone)]
pub struct ChatBackend {
    pub config: ConfigCell,
    pub chat_pool: ProcessorPool,
}

impl Service for ChatBackend {
    type Request = Request;
    type Response = ResponseFn<Finished<IoBuf<TcpStream>, Error>, TcpStream>;
    type Error = Error;
    type Future = Finished<Self::Response, Error>;

    fn call(&self, req: Request) -> Self::Future {
        let cfg = self.config.get();
        let status = self.serve(&req);
        finished(ResponseFn::new(move |res| {
            let res = Pickler(res, cfg.clone(), DebugInfo::new(&req));
            write_error_page(status, res).done()
        }))
    }
}

impl ChatBackend {

    fn serve(&self, req: &Request) -> Status {
        use self::ChatRoute::*;

        let route = match match_route(&req.method, req.path.as_str()) {
            Some(r) => r,
            None => return Status::NotFound
        };
        let payload = match req.body {
            Some(ref body) => {
                str::from_utf8(&body.data[..]).ok()
                    .and_then(|s| Json::from_str(s).ok())
            }
            None => None
        };
        if route.expect_data() && payload.is_none() {
            return Status::BadRequest;
        }
        let action = match route {
            TopicSubscribe(client_id, topic) => {
                let cid = parse_cid(client_id);
                Action::Subscribe {
                    conn_id: cid,
                    topic: topic,
                }
            }
            TopicUnsubscribe(client_id, topic) => {
                let cid = parse_cid(client_id);
                Action::Unsubscribe {
                    conn_id: cid,
                    topic: topic,
                }
            }
            LatticeSubscribe(client_id, namespace) => {
                let cid = parse_cid(client_id);
                let delta = match decode_delta(req) {
                    Ok(delta) => delta,
                    Err(err) => {
                        info!("Error {:?}", err);
                        return Status::BadRequest
                    }
                };
                self.chat_pool.send(Action::Lattice {
                    namespace: namespace.clone(),
                    delta: delta,
                });
                Action::Attach {
                    namespace: namespace,
                    conn_id: cid,
                }
            }
            LatticeUnsubscribe(client_id, namespace) => {
                let cid = parse_cid(client_id);
                Action::Detach {
                    namespace: namespace,
                    conn_id: cid,
                }
            }
            TopicPublish(topic) => {
                let data = Arc::new(payload.unwrap());
                Action::Publish {
                    topic: topic,
                    data: data,
                }
            }
            LatticeUpdate(namespace) => {
                let delta = match decode_delta(req) {
                    Ok(delta) => delta,
                    Err(err) => {
                        info!("Error {:?}", err);
                        return Status::BadRequest
                    }
                };
                Action::Lattice {
                    namespace: namespace,
                    delta: delta,
                }
            }
        };
        self.chat_pool.send(action);
        Status::NoContent
    }

}

fn decode_delta(req: &Request) -> Result<Delta, json::DecoderError>
{
    let body = req.body.as_ref().unwrap();
    let body = str::from_utf8(&body.data[..]).unwrap();
    json::decode::<Delta>(body)
}

fn match_route(method: &Method, path: &str) -> Option<ChatRoute> {
    use minihttp::enums::Method::*;
    use self::ChatRoute::*;

    if !path.starts_with("/v1/") {
        return None
    }
    let mut it = path.split("/").skip(2);   // skip '' & 'v1'
    let route = match (method, it.next()) {
        (&Post, Some(kind)) => {
            if !it.next().map(|x| x.len() > 0).unwrap_or(false) {
                return None
            }
            match kind {
                "lattice" => {
                    let (_, namespace) = path.split_at("/v1/lattice/".len());
                    let namespace = namespace.replace("/", ".")
                        .parse().unwrap();
                    ChatRoute::LatticeUpdate(namespace)
                }
                "publish" => {
                    let (_, topic) = path.split_at("/v1/publish/".len());
                    let topic = topic.replace("/", ".")
                        .parse().unwrap();
                    TopicPublish(topic)
                }
                _ => return None
            }
        }
        (&Put, Some("connection")) |
        (&Delete, Some("connection")) => {
            let id = match it.next() {
                Some(id) if id.len() > 0 => id,
                _ => return None,
            };
            let kind = it.next();
            if !it.next().map(|x| x.len() > 0).unwrap_or(false) {
                return None
            }
            match kind {
                Some("subscriptions") => {
                    let (_, topic) = path.split_at(
                        "/v1/connection//subscriptions/".len() + id.len());
                    let topic = topic.replace("/", ".")
                        .parse().unwrap();
                    if method == &Put {
                        TopicSubscribe(id.to_string(), topic)
                    } else {
                        TopicUnsubscribe(id.to_string(), topic)
                    }
                }
                Some("lattices") => {
                    let (_, namespace) = path.split_at(
                        "/v1/connection//lattices/".len() + id.len());
                    let namespace = namespace.replace("/", ".")
                        .parse().unwrap();
                    if method == &Put {
                        LatticeSubscribe(
                            id.to_string(), namespace)
                    } else {
                        LatticeUnsubscribe(
                            id.to_string(), namespace)
                    }
                }
                _ => return None
            }
        }
        _ => return None
    };
    Some(route)
}

#[derive(Debug, PartialEq)]
pub enum ChatRoute {
    TopicSubscribe(String, Topic),
    TopicUnsubscribe(String, Topic),
    LatticeSubscribe(String, Lattice),
    LatticeUnsubscribe(String, Lattice),
    TopicPublish(Topic),
    LatticeUpdate(Lattice),
}

impl ChatRoute {
    fn expect_data(&self) -> bool {
        use self::ChatRoute::*;
        match *self {
            TopicSubscribe(_, _) => false,
            TopicUnsubscribe(_, _) => false,
            LatticeSubscribe(_, _) => true,
            LatticeUnsubscribe(_, _) => false,
            TopicPublish(_) => true,
            LatticeUpdate(_) => true,
        }
    }
}

#[cfg(test)]
mod test {
    use minihttp::enums::Method;
    use string_intern::Symbol;

    use super::match_route;
    use super::ChatRoute::*;

    #[test]
    fn match_topic_publish() {
        let path = "/v1/publish/test-chat/room1";
        let route = match_route(&Method::Post, path).unwrap();
        assert_eq!(route, TopicPublish(Symbol::from("test-chat.room1")));
    }

    #[test]
    fn match_lattice_update() {
        let path = "/v1/lattice/test-chat/rooms";
        let route = match_route(&Method::Post, path).unwrap();
        assert_eq!(route, LatticeUpdate(Symbol::from("test-chat.rooms")));
    }

    #[test]
    fn match_topic_subscribe() {
        let path = "/v1/connection/abcde/subscriptions/test-chat/room1";
        let route = match_route(&Method::Put, path).unwrap();
        assert_eq!(route, TopicSubscribe(
            "abcde".to_string(), Symbol::from("test-chat.room1")));
    }

    #[test]
    fn match_topic_unsubscribe() {
        let path = "/v1/connection/abcde/subscriptions/test-chat/room1";
        let route = match_route(&Method::Delete, path).unwrap();
        assert_eq!(route, TopicUnsubscribe(
            "abcde".to_string(), Symbol::from("test-chat.room1")));
    }

    #[test]
    fn match_lattice_subscribe() {
        let path = "/v1/connection/abcde/lattices/test-chat/room1";
        let route = match_route(&Method::Put, path).unwrap();
        assert_eq!(route, LatticeSubscribe(
            "abcde".to_string(), Symbol::from("test-chat.room1")));
    }

    #[test]
    fn match_lattice_unsubscribe() {
        let path = "/v1/connection/abcde/lattices/test-chat/room1";
        let route = match_route(&Method::Delete, path).unwrap();
        assert_eq!(route, LatticeUnsubscribe(
            "abcde".to_string(), Symbol::from("test-chat.room1")));
    }

    #[test]
    fn no_matches() {
        let path = "/v/";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/publish";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/publish/";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/publish/test-chat/room1";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/lattice/test-chat/rooms";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/connections//subscriptions/";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());

        let path = "/v1/connections/abc/subscriptions/";
        assert!(match_route(&Method::Get, path).is_none());
        assert!(match_route(&Method::Put, path).is_none());
        assert!(match_route(&Method::Post, path).is_none());
        assert!(match_route(&Method::Patch, path).is_none());
        assert!(match_route(&Method::Delete, path).is_none());
    }
}
<|endoftext|>