<commit_before>//! # Lucet Runtime for Sandboxed WebAssembly Applications
//!
//! This crate runs programs that were compiled with the `lucetc` WebAssembly to native code
//! compiler. It provides an interface for modules to be loaded from shared object files (see
//! `DlModule`), and for hosts to provide specialized functionality to guests (see
//! `Instance::embed_ctx()`).
//!
//! The runtime is a critical part of the safety and security story for Lucet. While the semantics
//! of WebAssembly and the `lucetc` compiler provide many guarantees, the runtime must be correct in
//! order for the assumptions of those guarantees to hold. For example, the runtime uses guard pages
//! to ensure that any attempts by guest programs to access memory past the end of the guest heap are
//! safely caught.
//!
//! The runtime is also extensible, and some of the key types are defined as traits for
//! flexibility. See the `lucet-runtime-internals` crate for details.
//!
//! ## Running a Lucet Program
//!
//! There are a few essential types for using the runtime:
//!
//! - [`Instance`](struct.Instance.html): a Lucet program, together with its dedicated memory and
//! signal handlers. Users of this API never own an `Instance` directly, but can own the
//! [`InstanceHandle`](struct.InstanceHandle.html) smart pointer.
//!
//! - [`Region`](trait.Region.html): the memory from which instances are created. This crate
//! includes [`MmapRegion`](struct.MmapRegion.html), an implementation backed by `mmap`.
//!
//! - [`Limits`](struct.Limits.html): upper bounds for the resources a Lucet instance may
//! consume. These may be larger or smaller than the limits described in the WebAssembly module
//! itself; the smaller limit is always enforced.
//!
//! - [`Module`](trait.Module.html): the read-only parts of a Lucet program, including its code and
//! initial heap configuration. This crate includes [`DlModule`](struct.DlModule.html), an
//! implementation backed by dynamic loading of shared objects.
//!
//! - [`Val`](enum.Val.html): an enum describing values in WebAssembly, used to provide
//! arguments. These can be created using `From` implementations of primitive types, for example
//! `5u64.into()` in the example below.
//!
//! - [`UntypedRetVal`](struct.UntypedRetVal.html): values returned from WebAssembly
//! functions. These must be interpreted at the correct type by the user via `From` implementations
//! or `retval.as_T()` methods, for example `u64::from(retval)` in the example below.
//!
//! To run a Lucet program, you start by creating a region, capable of backing a number of
//! instances. You then load a module and then create a new instance using the region and the
//! module. You can then run any of the functions that the Lucet program exports, retrieve return
//! values from those functions, and access the linear memory of the guest.
//!
//! ```no_run
//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region};
//!
//! let module = DlModule::load("/my/lucet/module.so").unwrap();
//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
//! let mut inst = region.new_instance(module).unwrap();
//!
//! let retval = inst.run(b"factorial", &[5u64.into()]).unwrap();
//! assert_eq!(u64::from(retval), 120u64);
//! ```
//!
//! ## Embedding With Hostcalls
//!
//! A "hostcall" is a function called by WebAssembly that is not defined in WebAssembly. Since
//! WebAssembly is such a minimal language, hostcalls are required for Lucet programs to do anything
//! interesting with the outside world. For example, in Fastly's [Terrarium
//! demo](https://wasm.fastly-labs.com/), hostcalls are provided for manipulating HTTP requests,
//! accessing a key/value store, etc.
//!
//! Some simple hostcalls can be implemented simply as an exported C function that takes an opaque
//! pointer argument (usually called `vmctx`). Hostcalls that require access to some underlying
//! state, such as the key/value store in Terrarium, can access a custom embedder context through
//! `vmctx`. For example, to make a `u32` available to hostcalls:
//!
//! ```no_run
//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region};
//! use lucet_runtime::vmctx::{Vmctx, lucet_vmctx};
//!
//! struct MyContext { x: u32 }
//!
//! #[no_mangle]
//! unsafe extern "C" fn foo(vmctx: *mut lucet_vmctx) {
//!     let mut vmctx = Vmctx::from_raw(vmctx);
//!     let hostcall_context = vmctx
//!         .get_embed_ctx_mut::<MyContext>();
//!     hostcall_context.x = 42;
//! }
//!
//! let module = DlModule::load("/my/lucet/module.so").unwrap();
//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
//! let mut inst = region
//!     .new_instance_builder(module)
//!     .with_embed_ctx(MyContext { x: 0 })
//!     .build()
//!     .unwrap();
//!
//! inst.run(b"call_foo", &[]).unwrap();
//!
//! let context_after = inst.get_embed_ctx::<MyContext>().unwrap();
//! assert_eq!(context_after.x, 42);
//! ```
//!
//! The embedder context is backed by a structure that can hold a single value of any type. Rust
//! embedders should add their own custom state type (like `MyContext` above) for any context they
//! require, rather than using a common type (such as the `u32`) from the standard library. This
//! avoids collisions between libraries, and allows for easy composition of embeddings.
//!
//! For C-based embedders, the type `*mut libc::c_void` is privileged as the only type that the C
//! API provides. The following example shows how a Rust embedder can initialize a C-compatible
//! context:
//!
//! ```no_run
//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region};
//!
//! let module = DlModule::load("/my/lucet/module.so").unwrap();
//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
//! struct PretendLibcRuntime { _unused: u8 };
//! let mut libc = PretendLibcRuntime { _unused: 0 };
//! let mut inst = region
//!     .new_instance_builder(module)
//!     .with_embed_ctx(libc as *mut libc::c_void)
//!     .build()
//!     .unwrap();
//!
//! inst.run(b"main", &[]).unwrap();
//!
//! // clean up embedder context
//! drop(inst);
//! ```
//!
//! ## Custom Signal Handlers
//!
//! Since Lucet programs are run as native machine code, signals such as `SIGSEGV` and `SIGFPE` can
//! arise during execution. Rather than letting these signals bring down the entire process, the
//! Lucet runtime installs alternate signal handlers that limit the effects to just the instance
//! that raised the signal.
//!
//! By default, the signal handler sets the instance state to `State::Fault` and returns early from
//! the call to `Instance::run()`. You can, however, implement custom error recovery and logging
//! behavior by defining new signal handlers on a per-instance basis. For example, the following
//! signal handler increments a counter of signals it has seen before setting the fault state:
//!
//! ```no_run
//! use lucet_runtime::{
//!     DlModule, Error, Instance, Limits, MmapRegion, Region, SignalBehavior, TrapCode
//! };
//! use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
//!
//! static SIGNAL_COUNT: AtomicUsize = ATOMIC_USIZE_INIT;
//!
//! fn signal_handler_count(
//!     _inst: &Instance,
//!     _trapcode: &TrapCode,
//!     _signum: libc::c_int,
//!     _siginfo_ptr: *const libc::siginfo_t,
//!     _ucontext_ptr: *const libc::c_void,
//! ) -> SignalBehavior {
//!     SIGNAL_COUNT.fetch_add(1, Ordering::SeqCst);
//!     SignalBehavior::Default
//! }
//!
//! let module = DlModule::load("/my/lucet/module.so").unwrap();
//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
//! let mut inst = region.new_instance(module).unwrap();
//!
//! // install the handler
//! inst.set_signal_handler(signal_handler_count);
//!
//! match inst.run(b"raise_a_signal", &[]) {
//!     Err(Error::RuntimeFault(_)) => {
//!         println!("I've now handled {} signals!", SIGNAL_COUNT.load(Ordering::SeqCst));
//!     }
//!     res => panic!("unexpected result: {:?}", res),
//! }
//! ```
//!
//! When implementing custom signal handlers for the Lucet runtime, the usual caveats about signal
//! safety apply: see
//! [`signal-safety(7)`](http://man7.org/linux/man-pages/man7/signal-safety.7.html).
//!
//! ## Interaction With Host Signal Handlers
//!
//! Great care must be taken if host application installs or otherwise modifies signal handlers
//! anywhere in the process. Lucet installs handlers for `SIGBUS`, `SIGFPE`, `SIGILL`, and `SIGSEGV`
//! when the first Lucet instance begins running, and restores the preëxisting handlers when the
//! last Lucet instance terminates. During this time, other threads in the host process *must not*
//! modify those signal handlers, since signal handlers can only be installed on a process-wide
//! basis.
//!
//! Despite this limitation, Lucet is designed to compose with other signal handlers in the host
//! program. If one of the above signals is caught by the Lucet signal handler, but that thread is
//! not currently running a Lucet instance, the saved host signal handler is called. This means
//! that, for example, a `SIGSEGV` on a non-Lucet thread of a host program will still likely abort
//! the entire process.

pub use lucet_runtime_internals::alloc::Limits;
pub use lucet_runtime_internals::error::Error;
pub use lucet_runtime_internals::instance::{
    FaultDetails, Instance, InstanceHandle, SignalBehavior, TerminationDetails,
};
pub use lucet_runtime_internals::module::{DlModule, Module};
pub use lucet_runtime_internals::region::mmap::MmapRegion;
pub use lucet_runtime_internals::region::{InstanceBuilder, Region};
pub use lucet_runtime_internals::trapcode::{TrapCode, TrapCodeType};
pub use lucet_runtime_internals::val::{UntypedRetVal, Val};
pub use lucet_runtime_internals::WASM_PAGE_SIZE;

pub mod vmctx {
    //! Functions for manipulating instances from hostcalls.
    //!
    //! The Lucet compiler inserts an extra `*mut lucet_vmctx` argument to all functions defined and
    //! called by WebAssembly code. Through this pointer, code running in the guest context can
    //! access and manipulate the instance and its structures. These functions are intended for use
    //! in hostcall implementations, and must only be used from within a running guest.
    //!
    //! # Panics
    //!
    //! All of the `Vmctx` methods will panic if the `Vmctx` was not created from a valid pointer
    //! associated with a running instance. This should never occur if run in guest code on the
    //! pointer argument inserted by the compiler.
    pub use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx};
}

/// Call this if you're having trouble with `lucet_*` symbols not being exported.
///
/// This is pretty hackish; we will hopefully be able to avoid this altogether once [this
/// issue](https://github.com/rust-lang/rust/issues/58037) is addressed.
#[no_mangle]
pub extern "C" fn lucet_internal_ensure_linked() {
    lucet_runtime_internals::vmctx::vmctx_capi_init();
}
<commit_msg>lucet-runtime: fix doc test<commit_after>//! # Lucet Runtime for Sandboxed WebAssembly Applications
//!
//! This crate runs programs that were compiled with the `lucetc` WebAssembly to native code
//! compiler. It provides an interface for modules to be loaded from shared object files (see
//! `DlModule`), and for hosts to provide specialized functionality to guests (see
//! `Instance::embed_ctx()`).
//!
//! The runtime is a critical part of the safety and security story for Lucet. While the semantics
//! of WebAssembly and the `lucetc` compiler provide many guarantees, the runtime must be correct in
//! order for the assumptions of those guarantees to hold. For example, the runtime uses guard pages
//! to ensure that any attempts by guest programs to access memory past the end of the guest heap are
//! safely caught.
//!
//! The runtime is also extensible, and some of the key types are defined as traits for
//! flexibility. See the `lucet-runtime-internals` crate for details.
//!
//! ## Running a Lucet Program
//!
//! There are a few essential types for using the runtime:
//!
//! - [`Instance`](struct.Instance.html): a Lucet program, together with its dedicated memory and
//! signal handlers. Users of this API never own an `Instance` directly, but can own the
//! [`InstanceHandle`](struct.InstanceHandle.html) smart pointer.
//!
//! - [`Region`](trait.Region.html): the memory from which instances are created. This crate
//! includes [`MmapRegion`](struct.MmapRegion.html), an implementation backed by `mmap`.
//!
//! - [`Limits`](struct.Limits.html): upper bounds for the resources a Lucet instance may
//! consume. These may be larger or smaller than the limits described in the WebAssembly module
//! itself; the smaller limit is always enforced.
//!
//! - [`Module`](trait.Module.html): the read-only parts of a Lucet program, including its code and
//! initial heap configuration. This crate includes [`DlModule`](struct.DlModule.html), an
//! implementation backed by dynamic loading of shared objects.
//!
//! - [`Val`](enum.Val.html): an enum describing values in WebAssembly, used to provide
//! arguments. These can be created using `From` implementations of primitive types, for example
//! `5u64.into()` in the example below.
//!
//! - [`UntypedRetVal`](struct.UntypedRetVal.html): values returned from WebAssembly
//! functions. These must be interpreted at the correct type by the user via `From` implementations
//! or `retval.as_T()` methods, for example `u64::from(retval)` in the example below.
//!
//! To run a Lucet program, you start by creating a region, capable of backing a number of
//! instances. You then load a module and then create a new instance using the region and the
//! module. You can then run any of the functions that the Lucet program exports, retrieve return
//! values from those functions, and access the linear memory of the guest.
//!
//! ```no_run
//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region};
//!
//! let module = DlModule::load("/my/lucet/module.so").unwrap();
//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
//! let mut inst = region.new_instance(module).unwrap();
//!
//! let retval = inst.run(b"factorial", &[5u64.into()]).unwrap();
//! assert_eq!(u64::from(retval), 120u64);
//! ```
//!
//! ## Embedding With Hostcalls
//!
//! A "hostcall" is a function called by WebAssembly that is not defined in WebAssembly. Since
//! WebAssembly is such a minimal language, hostcalls are required for Lucet programs to do anything
//! interesting with the outside world. For example, in Fastly's [Terrarium
//! demo](https://wasm.fastly-labs.com/), hostcalls are provided for manipulating HTTP requests,
//! accessing a key/value store, etc.
//!
//! Some simple hostcalls can be implemented simply as an exported C function that takes an opaque
//! pointer argument (usually called `vmctx`). Hostcalls that require access to some underlying
//! state, such as the key/value store in Terrarium, can access a custom embedder context through
//! `vmctx`. For example, to make a `u32` available to hostcalls:
//!
//! ```no_run
//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region};
//! use lucet_runtime::vmctx::{Vmctx, lucet_vmctx};
//!
//! struct MyContext { x: u32 }
//!
//! #[no_mangle]
//! unsafe extern "C" fn foo(vmctx: *mut lucet_vmctx) {
//!     let mut vmctx = Vmctx::from_raw(vmctx);
//!     let hostcall_context = vmctx
//!         .get_embed_ctx_mut::<MyContext>();
//!     hostcall_context.x = 42;
//! }
//!
//! let module = DlModule::load("/my/lucet/module.so").unwrap();
//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
//! let mut inst = region
//!     .new_instance_builder(module)
//!     .with_embed_ctx(MyContext { x: 0 })
//!     .build()
//!     .unwrap();
//!
//! inst.run(b"call_foo", &[]).unwrap();
//!
//! let context_after = inst.get_embed_ctx::<MyContext>().unwrap();
//! assert_eq!(context_after.x, 42);
//! ```
//!
//! The embedder context is backed by a structure that can hold a single value of any type. Rust
//! embedders should add their own custom state type (like `MyContext` above) for any context they
//! require, rather than using a common type (such as the `u32`) from the standard library. This
//! avoids collisions between libraries, and allows for easy composition of embeddings.
//!
//! For C-based embedders, the type `*mut libc::c_void` is privileged as the only type that the C
//! API provides. The following example shows how a Rust embedder can initialize a C-compatible
//! context:
//!
//! ```no_run
//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region};
//!
//! let module = DlModule::load("/my/lucet/module.so").unwrap();
//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
//! struct PretendLibcRuntime { _unused: u8 };
//! let mut libc = PretendLibcRuntime { _unused: 0 };
//! let mut inst = region
//!     .new_instance_builder(module)
//!     .with_embed_ctx(libc)
//!     .build()
//!     .unwrap();
//!
//! inst.run(b"main", &[]).unwrap();
//!
//! // clean up embedder context
//! drop(inst);
//! ```
//!
//! ## Custom Signal Handlers
//!
//! Since Lucet programs are run as native machine code, signals such as `SIGSEGV` and `SIGFPE` can
//! arise during execution. Rather than letting these signals bring down the entire process, the
//! Lucet runtime installs alternate signal handlers that limit the effects to just the instance
//! that raised the signal.
//!
//! By default, the signal handler sets the instance state to `State::Fault` and returns early from
//! the call to `Instance::run()`. You can, however, implement custom error recovery and logging
//! behavior by defining new signal handlers on a per-instance basis. For example, the following
//! signal handler increments a counter of signals it has seen before setting the fault state:
//!
//! ```no_run
//! use lucet_runtime::{
//!     DlModule, Error, Instance, Limits, MmapRegion, Region, SignalBehavior, TrapCode
//! };
//! use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
//!
//! static SIGNAL_COUNT: AtomicUsize = ATOMIC_USIZE_INIT;
//!
//! fn signal_handler_count(
//!     _inst: &Instance,
//!     _trapcode: &TrapCode,
//!     _signum: libc::c_int,
//!     _siginfo_ptr: *const libc::siginfo_t,
//!     _ucontext_ptr: *const libc::c_void,
//! ) -> SignalBehavior {
//!     SIGNAL_COUNT.fetch_add(1, Ordering::SeqCst);
//!     SignalBehavior::Default
//! }
//!
//! let module = DlModule::load("/my/lucet/module.so").unwrap();
//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
//! let mut inst = region.new_instance(module).unwrap();
//!
//! // install the handler
//! inst.set_signal_handler(signal_handler_count);
//!
//! match inst.run(b"raise_a_signal", &[]) {
//!     Err(Error::RuntimeFault(_)) => {
//!         println!("I've now handled {} signals!", SIGNAL_COUNT.load(Ordering::SeqCst));
//!     }
//!     res => panic!("unexpected result: {:?}", res),
//! }
//! ```
//!
//! When implementing custom signal handlers for the Lucet runtime, the usual caveats about signal
//! safety apply: see
//! [`signal-safety(7)`](http://man7.org/linux/man-pages/man7/signal-safety.7.html).
//!
//! ## Interaction With Host Signal Handlers
//!
//! Great care must be taken if host application installs or otherwise modifies signal handlers
//! anywhere in the process. Lucet installs handlers for `SIGBUS`, `SIGFPE`, `SIGILL`, and `SIGSEGV`
//! when the first Lucet instance begins running, and restores the preëxisting handlers when the
//! last Lucet instance terminates. During this time, other threads in the host process *must not*
//! modify those signal handlers, since signal handlers can only be installed on a process-wide
//! basis.
//!
//! Despite this limitation, Lucet is designed to compose with other signal handlers in the host
//! program. If one of the above signals is caught by the Lucet signal handler, but that thread is
//! not currently running a Lucet instance, the saved host signal handler is called. This means
//! that, for example, a `SIGSEGV` on a non-Lucet thread of a host program will still likely abort
//! the entire process.

pub use lucet_runtime_internals::alloc::Limits;
pub use lucet_runtime_internals::error::Error;
pub use lucet_runtime_internals::instance::{
    FaultDetails, Instance, InstanceHandle, SignalBehavior, TerminationDetails,
};
pub use lucet_runtime_internals::module::{DlModule, Module};
pub use lucet_runtime_internals::region::mmap::MmapRegion;
pub use lucet_runtime_internals::region::{InstanceBuilder, Region};
pub use lucet_runtime_internals::trapcode::{TrapCode, TrapCodeType};
pub use lucet_runtime_internals::val::{UntypedRetVal, Val};
pub use lucet_runtime_internals::WASM_PAGE_SIZE;

pub mod vmctx {
    //! Functions for manipulating instances from hostcalls.
    //!
    //! The Lucet compiler inserts an extra `*mut lucet_vmctx` argument to all functions defined and
    //! called by WebAssembly code. Through this pointer, code running in the guest context can
    //! access and manipulate the instance and its structures. These functions are intended for use
    //! in hostcall implementations, and must only be used from within a running guest.
    //!
    //! # Panics
    //!
    //! All of the `Vmctx` methods will panic if the `Vmctx` was not created from a valid pointer
    //! associated with a running instance. This should never occur if run in guest code on the
    //! pointer argument inserted by the compiler.
    pub use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx};
}

/// Call this if you're having trouble with `lucet_*` symbols not being exported.
///
/// This is pretty hackish; we will hopefully be able to avoid this altogether once [this
/// issue](https://github.com/rust-lang/rust/issues/58037) is addressed.
#[no_mangle]
pub extern "C" fn lucet_internal_ensure_linked() {
    lucet_runtime_internals::vmctx::vmctx_capi_init();
}
<|endoftext|>


<commit_before>
use super::DocBuilder;
use super::crates::crates_from_path;
use DocBuilderError;
use utils::{get_package, source_path, copy_dir, copy_doc_dir, update_sources};
use db::{connect_db, add_package_into_database, add_build_into_database, add_path_into_database};
use cargo::core::Package;
use std::process::{Command, Output};
use std::path::PathBuf;
use postgres::Connection;
use rustc_serialize::json::Json;

use regex::Regex;

type CommandResult = Result<String, String>;

#[derive(Debug)]
pub struct ChrootBuilderResult {
    pub output: String,
    pub build_success: bool,
    pub have_doc: bool,
    pub have_examples: bool,
    pub rustc_version: String,
    pub cratesfyi_version: String,
}

impl DocBuilder {
    /// Builds every package documentation in chroot environment
    pub fn build_world(&mut self) -> Result<(), DocBuilderError> {
        try!(update_sources());

        let mut count = 0;

        crates(self.options.crates_io_index_path.clone(), |name, version| {
            match self.build_package(name, version) {
                Ok(status) => {
                    count += 1;
                    if status && count % 10 == 0 {
                        let _ = self.save_cache();
                    }
                }
                Err(err) => warn!("Failed to build package {}-{}: {}", name, version, err),
            }
            self.cache.insert(format!("{}-{}", name, version));
        })
    }

    /// Builds package documentation in chroot environment and adds into cratesfyi database
    pub fn build_package(&mut self, name: &str, version: &str) -> Result<bool, DocBuilderError> {
        info!("Building package {}-{}", name, version);

        // Skip crates according to options
        if (self.options.skip_if_log_exists &&
            self.cache.contains(&format!("{}-{}", name, version)[..])) ||
           (self.options.skip_if_exists &&
            self.db_cache.contains(&format!("{}-{}", name, version)[..])) {
            info!("Skipping");
            return Ok(false);
        }

        // Database connection
        let conn = try!(connect_db());

        // get_package (and cargo) is using semver, add '=' in front of version.
        let pkg = try!(get_package(name, Some(&format!("={}", version)[..])));
        let res = self.build_package_in_chroot(&pkg);

        // copy sources and documentation
        let file_list = try!(self.add_sources_into_database(&conn, &pkg));
        let successfully_targets = if res.have_doc {
            try!(self.copy_documentation(&pkg, &res.rustc_version, None));
            let successfully_targets = self.build_package_for_all_targets(&pkg);
            for target in &successfully_targets {
                try!(self.copy_documentation(&pkg, &res.rustc_version, Some(target)));
            }
            try!(self.add_documentation_into_database(&conn, &pkg));
            successfully_targets
        } else {
            Vec::new()
        };

        let release_id = try!(add_package_into_database(&conn,
                                                        &pkg,
                                                        &res,
                                                        Some(file_list),
                                                        successfully_targets));
        try!(add_build_into_database(&conn, &release_id, &res));

        // remove documentation, source and build directory after we are done
        try!(self.clean(&pkg));

        // add package into build cache
        self.cache.insert(format!("{}-{}", name, version));

        Ok(res.build_success)
    }

    /// Builds documentation of a package with cratesfyi in chroot environment
    fn build_package_in_chroot(&self, package: &Package) -> ChrootBuilderResult {
        debug!("Building package in chroot");
        let (rustc_version, cratesfyi_version) = self.get_versions();
        let cmd = format!("cratesfyi doc {} ={}",
                          package.manifest().name(),
                          package.manifest().version());
        match self.chroot_command(cmd) {
            Ok(o) => {
                ChrootBuilderResult {
                    output: o,
                    build_success: true,
                    have_doc: self.have_documentation(&package),
                    have_examples: self.have_examples(&package),
                    rustc_version: rustc_version,
                    cratesfyi_version: cratesfyi_version,
                }
            }
            Err(e) => {
                ChrootBuilderResult {
                    output: e,
                    build_success: false,
                    have_doc: false,
                    have_examples: self.have_examples(&package),
                    rustc_version: rustc_version,
                    cratesfyi_version: cratesfyi_version,
                }
            }
        }
    }

    /// Builds documentation of crate for every target and returns Vec of successfully targets
    fn build_package_for_all_targets(&self, package: &Package) -> Vec<String> {
        let targets = ["aarch64-apple-ios",
                       "aarch64-linux-android",
                       "aarch64-unknown-linux-gnu",
                       "arm-linux-androideabi",
                       "arm-unknown-linux-gnueabi",
                       "arm-unknown-linux-gnueabihf",
                       "armv7-apple-ios",
                       "armv7-linux-androideabi",
                       "armv7-unknown-linux-gnueabihf",
                       "armv7s-apple-ios",
                       "i386-apple-ios",
                       "i586-pc-windows-msvc",
                       "i586-unknown-linux-gnu",
                       "i686-apple-darwin",
                       "i686-linux-android",
                       "i686-pc-windows-gnu",
                       "i686-pc-windows-msvc",
                       "i686-unknown-freebsd",
                       "i686-unknown-linux-gnu",
                       "i686-unknown-linux-musl",
                       "mips-unknown-linux-gnu",
                       "mips-unknown-linux-musl",
                       "mipsel-unknown-linux-gnu",
                       "mipsel-unknown-linux-musl",
                       "powerpc-unknown-linux-gnu",
                       "powerpc64-unknown-linux-gnu",
                       "powerpc64le-unknown-linux-gnu",
                       "x86_64-apple-darwin",
                       "x86_64-apple-ios",
                       "x86_64-pc-windows-gnu",
                       "x86_64-pc-windows-msvc",
                       "x86_64-rumprun-netbsd",
                       "x86_64-unknown-freebsd",
                       "x86_64-unknown-linux-gnu",
                       "x86_64-unknown-linux-musl",
                       "x86_64-unknown-netbsd"];

        let mut successfuly_targets = Vec::new();

        for target in targets.iter() {
            debug!("Building {} for {}", canonical_name(&package), target);
            let cmd = format!("cratesfyi doc {} ={} {}",
                              package.manifest().name(),
                              package.manifest().version(),
                              target);
            if let Ok(_) = self.chroot_command(cmd) {
                successfuly_targets.push(target.to_string());
            }
        }
        successfuly_targets
    }

    /// Copies source files of a package into source_path
    #[allow(dead_code)]  // I've been using this function before storing files in database
    fn copy_sources(&self, package: &Package) -> Result<(), DocBuilderError> {
        debug!("Copying sources");
        let destination =
            PathBuf::from(&self.options.sources_path).join(format!("{}/{}",
                                                                   package.manifest().name(),
                                                                   package.manifest().version()));
        // unwrap is safe here, this function will be always called after get_package
        match copy_dir(source_path(&package).unwrap(), &destination) {
            Ok(_) => Ok(()),
            Err(e) => Err(DocBuilderError::Io(e)),
        }
    }

    /// Copies documentation to destination directory
    fn copy_documentation(&self,
                          package: &Package,
                          rustc_version: &str,
                          target: Option<&str>)
                          -> Result<(), DocBuilderError> {
        let crate_doc_path = PathBuf::from(&self.options.chroot_path)
            .join("home")
            .join(&self.options.chroot_user)
            .join(canonical_name(&package))
            .join(target.unwrap_or(""));
        let destination = PathBuf::from(&self.options.destination)
            .join(format!("{}/{}",
                          package.manifest().name(),
                          package.manifest().version()))
            .join(target.unwrap_or(""));
        copy_doc_dir(crate_doc_path,
                     destination,
                     parse_rustc_version(rustc_version).trim(),
                     target.is_some())
            .map_err(DocBuilderError::Io)
    }

    /// Removes build directory of a package in chroot
    fn remove_build_dir(&self, package: &Package) -> Result<(), DocBuilderError> {
        let _ = self.chroot_command(format!("rm -rf {}", canonical_name(&package)));
        Ok(())
    }

    /// Remove documentation, build directory and sources directory of a package
    fn clean(&self, package: &Package) -> Result<(), DocBuilderError> {
        debug!("Cleaning package");
        use std::fs::remove_dir_all;
        let documentation_path = PathBuf::from(&self.options.destination)
            .join(package.manifest().name());
        let source_path = source_path(&package).unwrap();
        // Some crates don't have documentation, so we don't care if removing_dir_all fails
        let _ = self.remove_build_dir(&package);
        let _ = remove_dir_all(documentation_path);
        let _ = remove_dir_all(source_path);
        Ok(())
    }

    /// Runs a command in a chroot environment
    fn chroot_command<T: AsRef<str>>(&self, cmd: T) -> CommandResult {
        command_result(Command::new("sudo")
            .arg("lxc-attach")
            .arg("-n")
            .arg(&self.options.container_name)
            .arg("--")
            .arg("su")
            .arg("-")
            .arg(&self.options.chroot_user)
            .arg("-c")
            .arg(cmd.as_ref())
            .output()
            .unwrap())
    }

    /// Checks a package build directory to determine if package have docs
    ///
    /// This function is checking first target in targets to see if documentation exists for a
    /// crate. Package must be successfully built in chroot environment first.
    fn have_documentation(&self, package: &Package) -> bool {
        let crate_doc_path = PathBuf::from(&self.options.chroot_path)
            .join("home")
            .join(&self.options.chroot_user)
            .join(canonical_name(&package))
            .join("doc")
            .join(package.targets()[0].name().replace("-", "_").to_string());
        crate_doc_path.exists()
    }

    /// Checks if package have examples
    fn have_examples(&self, package: &Package) -> bool {
        let path = source_path(&package).unwrap().join("examples");
        path.exists() && path.is_dir()
    }

    /// Gets rustc and cratesfyi version from chroot environment
    fn get_versions(&self) -> (String, String) {
        // It is safe to use expect here
        // chroot environment must always have rustc and cratesfyi installed
        (String::from(self.chroot_command("rustc --version")
            .expect("Failed to get rustc version")
            .trim()),
         String::from(self.chroot_command("cratesfyi --version")
            .expect("Failed to get cratesfyi version")
            .trim()))
    }

    /// Adds sources into database
    fn add_sources_into_database(&self,
                                 conn: &Connection,
                                 package: &Package)
                                 -> Result<Json, DocBuilderError> {
        debug!("Adding sources into database");
        let prefix = format!("sources/{}/{}",
                             package.manifest().name(),
                             package.manifest().version());
        add_path_into_database(conn, &prefix, source_path(&package).unwrap())
    }

    /// Adds documentations into database
    fn add_documentation_into_database(&self,
                                       conn: &Connection,
                                       package: &Package)
                                       -> Result<Json, DocBuilderError> {
        debug!("Adding documentation into database");
        let prefix = format!("rustdoc/{}/{}",
                             package.manifest().name(),
                             package.manifest().version());
        let crate_doc_path = PathBuf::from(&self.options.destination).join(format!("{}/{}",
                          package.manifest().name(),
                          package.manifest().version()));
        add_path_into_database(conn, &prefix, crate_doc_path)
    }
}

/// Simple function to capture command output
fn command_result(output: Output) -> CommandResult {
    let mut command_out = String::from_utf8_lossy(&output.stdout).into_owned();
    command_out.push_str(&String::from_utf8_lossy(&output.stderr).into_owned()[..]);
    match output.status.success() {
        true => Ok(command_out),
        false => Err(command_out),
    }
}

/// Returns canonical name of a package.
///
/// It's just package-version. All directory structure used in cratesfyi is
/// following this naming scheme.
fn canonical_name(package: &Package) -> String {
    format!("{}-{}",
            package.manifest().name(),
            package.manifest().version())
}

/// Parses rustc commit hash from rustc version string
fn parse_rustc_version<S: AsRef<str>>(version: S) -> String {
    let version_regex = Regex::new(r" ([\w-.]+) \((\w+) (\d+)-(\d+)-(\d+)\)").unwrap();
    let captures = version_regex.captures(version.as_ref()).expect("Failed to parse rustc version");

    format!("{}{}{}-{}-{}",
            captures.at(3).unwrap(),
            captures.at(4).unwrap(),
            captures.at(5).unwrap(),
            captures.at(1).unwrap(),
            captures.at(2).unwrap())
}

/// Runs `func` with the all crates from crates-io.index repository path.
///
/// First argument of func is the name of crate and
/// second argument is the version of crate. Func will be run for every crate.
fn crates<F>(path: PathBuf, mut func: F) -> Result<(), DocBuilderError>
    where F: FnMut(&str, &str) -> ()
{
    crates_from_path(&path, &mut func)
}

#[cfg(test)]
mod test {
    extern crate env_logger;
    use super::parse_rustc_version;
    use std::path::PathBuf;
    use {DocBuilder, DocBuilderOptions};

    #[test]
    #[ignore]
    fn test_build_world() {
        let _ = env_logger::init();
        let options = DocBuilderOptions::from_prefix(PathBuf::from("../cratesfyi-prefix"));
        let mut docbuilder = DocBuilder::new(options);
        // This test is building WHOLE WORLD and may take forever
        assert!(docbuilder.build_world().is_ok());
    }

    #[test]
    #[ignore]
    fn test_build_package() {
        let _ = env_logger::init();
        let options = DocBuilderOptions::from_prefix(PathBuf::from("../cratesfyi-prefix"));
        let mut docbuilder = DocBuilder::new(options);
        let res = docbuilder.build_package("rand", "0.3.14");
        assert!(res.is_ok());
    }

    #[test]
    fn test_parse_rustc_version() {
        assert_eq!(parse_rustc_version("rustc 1.10.0-nightly (57ef01513 2016-05-23)"),
                   "20160523-1.10.0-nightly-57ef01513");
        assert_eq!(parse_rustc_version("cratesfyi 0.2.0 (ba9ae23 2016-05-26)"),
                   "20160526-0.2.0-ba9ae23");
    }
}
<commit_msg>Skip tier 2 and tier 3 platforms for now<commit_after>
use super::DocBuilder;
use super::crates::crates_from_path;
use DocBuilderError;
use utils::{get_package, source_path, copy_dir, copy_doc_dir, update_sources};
use db::{connect_db, add_package_into_database, add_build_into_database, add_path_into_database};
use cargo::core::Package;
use std::process::{Command, Output};
use std::path::PathBuf;
use postgres::Connection;
use rustc_serialize::json::Json;

use regex::Regex;

type CommandResult = Result<String, String>;

#[derive(Debug)]
pub struct ChrootBuilderResult {
    pub output: String,
    pub build_success: bool,
    pub have_doc: bool,
    pub have_examples: bool,
    pub rustc_version: String,
    pub cratesfyi_version: String,
}

impl DocBuilder {
    /// Builds every package documentation in chroot environment
    pub fn build_world(&mut self) -> Result<(), DocBuilderError> {
        try!(update_sources());

        let mut count = 0;

        crates(self.options.crates_io_index_path.clone(), |name, version| {
            match self.build_package(name, version) {
                Ok(status) => {
                    count += 1;
                    if status && count % 10 == 0 {
                        let _ = self.save_cache();
                    }
                }
                Err(err) => warn!("Failed to build package {}-{}: {}", name, version, err),
            }
            self.cache.insert(format!("{}-{}", name, version));
        })
    }

    /// Builds package documentation in chroot environment and adds into cratesfyi database
    pub fn build_package(&mut self, name: &str, version: &str) -> Result<bool, DocBuilderError> {
        info!("Building package {}-{}", name, version);

        // Skip crates according to options
        if (self.options.skip_if_log_exists &&
            self.cache.contains(&format!("{}-{}", name, version)[..])) ||
           (self.options.skip_if_exists &&
            self.db_cache.contains(&format!("{}-{}", name, version)[..])) {
            info!("Skipping");
            return Ok(false);
        }

        // Database connection
        let conn = try!(connect_db());

        // get_package (and cargo) is using semver, add '=' in front of version.
        let pkg = try!(get_package(name, Some(&format!("={}", version)[..])));
        let res = self.build_package_in_chroot(&pkg);

        // copy sources and documentation
        let file_list = try!(self.add_sources_into_database(&conn, &pkg));
        let successfully_targets = if res.have_doc {
            try!(self.copy_documentation(&pkg, &res.rustc_version, None));
            let successfully_targets = self.build_package_for_all_targets(&pkg);
            for target in &successfully_targets {
                try!(self.copy_documentation(&pkg, &res.rustc_version, Some(target)));
            }
            try!(self.add_documentation_into_database(&conn, &pkg));
            successfully_targets
        } else {
            Vec::new()
        };

        let release_id = try!(add_package_into_database(&conn,
                                                        &pkg,
                                                        &res,
                                                        Some(file_list),
                                                        successfully_targets));
        try!(add_build_into_database(&conn, &release_id, &res));

        // remove documentation, source and build directory after we are done
        try!(self.clean(&pkg));

        // add package into build cache
        self.cache.insert(format!("{}-{}", name, version));

        Ok(res.build_success)
    }

    /// Builds documentation of a package with cratesfyi in chroot environment
    fn build_package_in_chroot(&self, package: &Package) -> ChrootBuilderResult {
        debug!("Building package in chroot");
        let (rustc_version, cratesfyi_version) = self.get_versions();
        let cmd = format!("cratesfyi doc {} ={}",
                          package.manifest().name(),
                          package.manifest().version());
        match self.chroot_command(cmd) {
            Ok(o) => {
                ChrootBuilderResult {
                    output: o,
                    build_success: true,
                    have_doc: self.have_documentation(&package),
                    have_examples: self.have_examples(&package),
                    rustc_version: rustc_version,
                    cratesfyi_version: cratesfyi_version,
                }
            }
            Err(e) => {
                ChrootBuilderResult {
                    output: e,
                    build_success: false,
                    have_doc: false,
                    have_examples: self.have_examples(&package),
                    rustc_version: rustc_version,
                    cratesfyi_version: cratesfyi_version,
                }
            }
        }
    }

    /// Builds documentation of crate for every target and returns Vec of successfully targets
    fn build_package_for_all_targets(&self, package: &Package) -> Vec<String> {
        // Temporary skip tier 2 and tier 3 platforms
        let targets = [// "aarch64-apple-ios",
                       // "aarch64-linux-android",
                       // "aarch64-unknown-linux-gnu",
                       // "arm-linux-androideabi",
                       // "arm-unknown-linux-gnueabi",
                       // "arm-unknown-linux-gnueabihf",
                       // "armv7-apple-ios",
                       // "armv7-linux-androideabi",
                       // "armv7-unknown-linux-gnueabihf",
                       // "armv7s-apple-ios",
                       // "i386-apple-ios",
                       // "i586-pc-windows-msvc",
                       // "i586-unknown-linux-gnu",
                       "i686-apple-darwin",
                       // "i686-linux-android",
                       "i686-pc-windows-gnu",
                       "i686-pc-windows-msvc",
                       // "i686-unknown-freebsd",
                       "i686-unknown-linux-gnu",
                       // "i686-unknown-linux-musl",
                       // "mips-unknown-linux-gnu",
                       // "mips-unknown-linux-musl",
                       // "mipsel-unknown-linux-gnu",
                       // "mipsel-unknown-linux-musl",
                       // "powerpc-unknown-linux-gnu",
                       // "powerpc64-unknown-linux-gnu",
                       // "powerpc64le-unknown-linux-gnu",
                       "x86_64-apple-darwin",
                       // "x86_64-apple-ios",
                       "x86_64-pc-windows-gnu",
                       "x86_64-pc-windows-msvc",
                       // "x86_64-rumprun-netbsd",
                       // "x86_64-unknown-freebsd",
                       "x86_64-unknown-linux-gnu",
                       // "x86_64-unknown-linux-musl",
                       // "x86_64-unknown-netbsd",
                       ];

        let mut successfuly_targets = Vec::new();

        for target in targets.iter() {
            debug!("Building {} for {}", canonical_name(&package), target);
            let cmd = format!("cratesfyi doc {} ={} {}",
                              package.manifest().name(),
                              package.manifest().version(),
                              target);
            if let Ok(_) = self.chroot_command(cmd) {
                successfuly_targets.push(target.to_string());
            }
        }
        successfuly_targets
    }

    /// Copies source files of a package into source_path
    #[allow(dead_code)]  // I've been using this function before storing files in database
    fn copy_sources(&self, package: &Package) -> Result<(), DocBuilderError> {
        debug!("Copying sources");
        let destination =
            PathBuf::from(&self.options.sources_path).join(format!("{}/{}",
                                                                   package.manifest().name(),
                                                                   package.manifest().version()));
        // unwrap is safe here, this function will be always called after get_package
        match copy_dir(source_path(&package).unwrap(), &destination) {
            Ok(_) => Ok(()),
            Err(e) => Err(DocBuilderError::Io(e)),
        }
    }

    /// Copies documentation to destination directory
    fn copy_documentation(&self,
                          package: &Package,
                          rustc_version: &str,
                          target: Option<&str>)
                          -> Result<(), DocBuilderError> {
        let crate_doc_path = PathBuf::from(&self.options.chroot_path)
            .join("home")
            .join(&self.options.chroot_user)
            .join(canonical_name(&package))
            .join(target.unwrap_or(""));
        let destination = PathBuf::from(&self.options.destination)
            .join(format!("{}/{}",
                          package.manifest().name(),
                          package.manifest().version()))
            .join(target.unwrap_or(""));
        copy_doc_dir(crate_doc_path,
                     destination,
                     parse_rustc_version(rustc_version).trim(),
                     target.is_some())
            .map_err(DocBuilderError::Io)
    }

    /// Removes build directory of a package in chroot
    fn remove_build_dir(&self, package: &Package) -> Result<(), DocBuilderError> {
        let _ = self.chroot_command(format!("rm -rf {}", canonical_name(&package)));
        Ok(())
    }

    /// Remove documentation, build directory and sources directory of a package
    fn clean(&self, package: &Package) -> Result<(), DocBuilderError> {
        debug!("Cleaning package");
        use std::fs::remove_dir_all;
        let documentation_path = PathBuf::from(&self.options.destination)
            .join(package.manifest().name());
        let source_path = source_path(&package).unwrap();
        // Some crates don't have documentation, so we don't care if removing_dir_all fails
        let _ = self.remove_build_dir(&package);
        let _ = remove_dir_all(documentation_path);
        let _ = remove_dir_all(source_path);
        Ok(())
    }

    /// Runs a command in a chroot environment
    fn chroot_command<T: AsRef<str>>(&self, cmd: T) -> CommandResult {
        command_result(Command::new("sudo")
            .arg("lxc-attach")
            .arg("-n")
            .arg(&self.options.container_name)
            .arg("--")
            .arg("su")
            .arg("-")
            .arg(&self.options.chroot_user)
            .arg("-c")
            .arg(cmd.as_ref())
            .output()
            .unwrap())
    }

    /// Checks a package build directory to determine if package have docs
    ///
    /// This function is checking first target in targets to see if documentation exists for a
    /// crate. Package must be successfully built in chroot environment first.
    fn have_documentation(&self, package: &Package) -> bool {
        let crate_doc_path = PathBuf::from(&self.options.chroot_path)
            .join("home")
            .join(&self.options.chroot_user)
            .join(canonical_name(&package))
            .join("doc")
            .join(package.targets()[0].name().replace("-", "_").to_string());
        crate_doc_path.exists()
    }

    /// Checks if package have examples
    fn have_examples(&self, package: &Package) -> bool {
        let path = source_path(&package).unwrap().join("examples");
        path.exists() && path.is_dir()
    }

    /// Gets rustc and cratesfyi version from chroot environment
    fn get_versions(&self) -> (String, String) {
        // It is safe to use expect here
        // chroot environment must always have rustc and cratesfyi installed
        (String::from(self.chroot_command("rustc --version")
            .expect("Failed to get rustc version")
            .trim()),
         String::from(self.chroot_command("cratesfyi --version")
            .expect("Failed to get cratesfyi version")
            .trim()))
    }

    /// Adds sources into database
    fn add_sources_into_database(&self,
                                 conn: &Connection,
                                 package: &Package)
                                 -> Result<Json, DocBuilderError> {
        debug!("Adding sources into database");
        let prefix = format!("sources/{}/{}",
                             package.manifest().name(),
                             package.manifest().version());
        add_path_into_database(conn, &prefix, source_path(&package).unwrap())
    }

    /// Adds documentations into database
    fn add_documentation_into_database(&self,
                                       conn: &Connection,
                                       package: &Package)
                                       -> Result<Json, DocBuilderError> {
        debug!("Adding documentation into database");
        let prefix = format!("rustdoc/{}/{}",
                             package.manifest().name(),
                             package.manifest().version());
        let crate_doc_path = PathBuf::from(&self.options.destination).join(format!("{}/{}",
                          package.manifest().name(),
                          package.manifest().version()));
        add_path_into_database(conn, &prefix, crate_doc_path)
    }
}

/// Simple function to capture command output
fn command_result(output: Output) -> CommandResult {
    let mut command_out = String::from_utf8_lossy(&output.stdout).into_owned();
    command_out.push_str(&String::from_utf8_lossy(&output.stderr).into_owned()[..]);
    match output.status.success() {
        true => Ok(command_out),
        false => Err(command_out),
    }
}

/// Returns canonical name of a package.
///
/// It's just package-version. All directory structure used in cratesfyi is
/// following this naming scheme.
fn canonical_name(package: &Package) -> String {
    format!("{}-{}",
            package.manifest().name(),
            package.manifest().version())
}

/// Parses rustc commit hash from rustc version string
fn parse_rustc_version<S: AsRef<str>>(version: S) -> String {
    let version_regex = Regex::new(r" ([\w-.]+) \((\w+) (\d+)-(\d+)-(\d+)\)").unwrap();
    let captures = version_regex.captures(version.as_ref()).expect("Failed to parse rustc version");

    format!("{}{}{}-{}-{}",
            captures.at(3).unwrap(),
            captures.at(4).unwrap(),
            captures.at(5).unwrap(),
            captures.at(1).unwrap(),
            captures.at(2).unwrap())
}

/// Runs `func` with the all crates from crates-io.index repository path.
///
/// First argument of func is the name of crate and
/// second argument is the version of crate. Func will be run for every crate.
fn crates<F>(path: PathBuf, mut func: F) -> Result<(), DocBuilderError>
    where F: FnMut(&str, &str) -> ()
{
    crates_from_path(&path, &mut func)
}

#[cfg(test)]
mod test {
    extern crate env_logger;
    use super::parse_rustc_version;
    use std::path::PathBuf;
    use {DocBuilder, DocBuilderOptions};

    #[test]
    #[ignore]
    fn test_build_world() {
        let _ = env_logger::init();
        let options = DocBuilderOptions::from_prefix(PathBuf::from("../cratesfyi-prefix"));
        let mut docbuilder = DocBuilder::new(options);
        // This test is building WHOLE WORLD and may take forever
        assert!(docbuilder.build_world().is_ok());
    }

    #[test]
    #[ignore]
    fn test_build_package() {
        let _ = env_logger::init();
        let options = DocBuilderOptions::from_prefix(PathBuf::from("../cratesfyi-prefix"));
        let mut docbuilder = DocBuilder::new(options);
        let res = docbuilder.build_package("rand", "0.3.14");
        assert!(res.is_ok());
    }

    #[test]
    fn test_parse_rustc_version() {
        assert_eq!(parse_rustc_version("rustc 1.10.0-nightly (57ef01513 2016-05-23)"),
                   "20160523-1.10.0-nightly-57ef01513");
        assert_eq!(parse_rustc_version("cratesfyi 0.2.0 (ba9ae23 2016-05-26)"),
                   "20160526-0.2.0-ba9ae23");
    }
}
<|endoftext|>


<commit_before>// Hound -- A WAV encoding and decoding library in Rust
// Copyright (C) 2015 Ruud van Asseldonk
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License only.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// This example shows how to play a wav file using the cpal crate.

extern crate hound;
extern crate cpal;

use std::env;
use std::fs;
use std::io;
use std::path;

fn main() {
    // Make a WavReader that reads the file provided as program argument.
    let arg = env::args().nth(1).unwrap();
    let fname = path::Path::new(&arg);
    let input = fs::File::open(fname).unwrap();
    let buf_reader = io::BufReader::new(input);
    let mut reader = hound::WavReader::new(buf_reader).unwrap();
    let spec = reader.spec().clone(); // TODO: by value might be better in this case.

    // A voice in cpal is used for playback.
    let mut voice = cpal::Voice::new();

    let mut append_data = |voice: &mut cpal::Voice| {
        let mut buffer: cpal::Buffer<i16> =
            voice.append_data(spec.channels,
                              cpal::SamplesRate(spec.sample_rate),
                              1024); // TODO: Do I know the number of samples?
        // Fill the cpal buffer with data from the wav file.
        for (dest, src) in buffer.iter_mut().zip(reader.samples()) {
            *dest = src.unwrap();
        }
        // TODO: check when reader is done, then exit.
    };

    // The voice must have some data before playing for the first time.
    append_data(&mut voice);
    voice.play();

    // Then we keep providing new data until the end of the audio.
    loop {
        append_data(&mut voice); // TODO exit when done
    }
}
<commit_msg>fix cpal playback example for 16-bit audio file<commit_after>// Hound -- A WAV encoding and decoding library in Rust
// Copyright (C) 2015 Ruud van Asseldonk
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License only.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// This example shows how to play a wav file using the cpal crate.

extern crate hound;
extern crate cpal;

use std::env;
use std::fs;
use std::io;
use std::path;

fn main() {
    // Make a WavReader that reads the file provided as program argument.
    let arg = env::args().nth(1).unwrap();
    let fname = path::Path::new(&arg);
    let input = fs::File::open(fname).unwrap();
    let buf_reader = io::BufReader::new(input);
    let mut reader = hound::WavReader::new(buf_reader).unwrap();
    let spec = reader.spec().clone(); // TODO: by value might be better in this case.

    // A voice in cpal is used for playback.
    let mut voice = cpal::Voice::new();

    let mut append_data = |voice: &mut cpal::Voice| {
        let mut buffer: cpal::Buffer<u16> =
            voice.append_data(spec.channels,
                              cpal::SamplesRate(spec.sample_rate),
                              1024); // TODO: Do I know the number of samples?
        // Fill the cpal buffer with data from the wav file.
        for (dest, src) in buffer.iter_mut().zip(reader.samples::<i16>()) {
            // TODO: Without the cast, there are a lot of artefacts. This
            // suggests that samples are actually unsigned. We can find out by
            // computing the mean: if it is around 0i16, then samples are signed.
            // If it is around 32000u16, then samples are unsigned.
            *dest = src.unwrap() as u16;
        }
        // TODO: check when reader is done, then exit.
    };

    // The voice must have some data before playing for the first time.
    append_data(&mut voice);
    voice.play();

    // Then we keep providing new data until the end of the audio.
    loop {
        append_data(&mut voice); // TODO exit when done
    }
}
<|endoftext|>


<commit_before>#[macro_use] extern crate nickel;
extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;

use nickel::status::StatusCode;
use nickel::{Nickel, JsonBody, HttpRouter, MediaType};

#[derive(Serialize, Deserialize)]
struct Person {
    first_name: String,
    last_name:  String,
}

fn main() {
    let mut server = Nickel::new();

    // try it with curl
    // curl 'http://localhost:6767/a/post/request' -H 'Content-Type: application/json;charset=UTF-8'  --data-binary $'{ "first_name": "John","last_name": "Connor" }'
    server.post("/", middleware! { |request, response|
        let person = try_with!(response, {
            request.json_as::<Person>().map_err(|e| (StatusCode::BadRequest, e))
        });
        format!("Hello {} {}", person.first_name, person.last_name)
    });

    // go to http://localhost:6767/your/name to see this route in action
    server.get("/:first/:last", middleware! { |req|
        // These unwraps are safe because they are required parts of the route
        let first_name = req.param("first").unwrap();
        let last_name = req.param("last").unwrap();

        let person = Person {
            first_name: first_name.to_string(),
            last_name: last_name.to_string(),
        };
        serde_json::to_value(person).map_err(|e| (StatusCode::InternalServerError, e))
    });

    // go to http://localhost:6767/content-type to see this route in action
    server.get("/raw", middleware! { |_, mut response|
        response.set(MediaType::Json);
        r#"{ "foo": "bar" }"#
    });

    server.listen("127.0.0.1:6767").unwrap();
}
<commit_msg>fix(*): curl routes in examples/json.rs<commit_after>#[macro_use]
extern crate nickel;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;

use nickel::status::StatusCode;
use nickel::{HttpRouter, JsonBody, MediaType, Nickel};

#[derive(Serialize, Deserialize)]
struct Person {
    first_name: String,
    last_name: String,
}

fn main() {
    let mut server = Nickel::new();

    // try it with curl
    // curl 'http://localhost:6767' -H 'Content-Type: application/json;charset=UTF-8'  --data-binary $'{ "first_name": "John","last_name": "Connor" }'
    server.post(
        "/",
        middleware! { |request, response|
            let person = try_with!(response, {
                request.json_as::<Person>().map_err(|e| (StatusCode::BadRequest, e))
            });
            format!("Hello {} {}", person.first_name, person.last_name)
        },
    );

    // go to http://localhost:6767/your/name to see this route in action
    server.get(
        "/:first/:last",
        middleware! { |req|
            // These unwraps are safe because they are required parts of the route
            let first_name = req.param("first").unwrap();
            let last_name = req.param("last").unwrap();

            let person = Person {
                first_name: first_name.to_string(),
                last_name: last_name.to_string(),
            };
            serde_json::to_value(person).map_err(|e| (StatusCode::InternalServerError, e))
        },
    );

    // go to http://localhost:6767/raw to see this route in action
    server.get(
        "/raw",
        middleware! { |_, mut response|
            response.set(MediaType::Json);
            r#"{ "foo": "bar" }"#
        },
    );

    server.listen("127.0.0.1:6767").unwrap();
}
<|endoftext|>


<commit_before>//! Enumerates the types of operations and their operands.
//!
//! An `OperationKind` can be converted to an Operation by

use std::boxed::FnBox;

mod execute;
mod implementations;

pub use self::execute::Execute;

use types::*;
use instruction::{Dest, Src, DestKind, SrcKind };
// use self::implementations::*;

#[derive(Copy,Clone,Eq,PartialEq)]
pub enum Operation {
    NoOp,
    Load(Dest, Src),
    Stash(Src),
    Fetch(Src),

    Jump(Src),
    JumpV0(Src),
    Call(Src),
    Ret,

    SkipEq(Src, Src),
    SkipNotEq(Src, Src),
    SkipKey(Src),
    SkipNotKey(Src),

    Add(Dest, Src, Src),
    Sub(Dest, Src, Src),

    Or(Dest, Src, Src),
    And(Dest, Src, Src),
    Xor(Dest, Src, Src),
    Shr(Dest, Src),
    Shl(Dest, Src),

    Rand(Dest, Src, Src),

    Cls,
    Sprite(Src, Src, Src),
    Font(Src),
    Bcd(Src),
    WaitKey(Src),
}

impl Operation {
    pub fn kind(&self) -> OperationKind {
        match *self {
            Operation::NoOp => OperationKind::NoOp,
            _ => panic!("cannot specify"),
        }
    }

    pub fn execute(&self, exec: &mut Execute) -> Chip8Result<()> {
        match *self {
            Operation::NoOp                     => { Ok(()) },
            Operation::Load(dest, src)          => { implementations::load(exec, dest, src) },
            Operation::Add(dest, lhs, rhs)      => { implementations::add(exec, dest, lhs, rhs) },
            Operation::Sub(dest, lhs, rhs)      => { implementations::sub(exec, dest, lhs, rhs) },
            Operation::Jump(addr)               => { implementations::jump(exec, addr) },
            Operation::JumpV0(addr)             => { implementations::jump_v0(exec, addr) },
            Operation::Call(addr)               => { implementations::call(exec, addr) },
            Operation::Ret                      => { implementations::ret(exec) },
            Operation::Or(dest, lhs, rhs)       => { implementations::or(exec, dest, lhs, rhs) },
            Operation::And(dest, lhs, rhs)      => { implementations::and(exec, dest, lhs, rhs) },
            Operation::Xor(dest, lhs, rhs)      => { implementations::xor(exec, dest, lhs, rhs) },
            Operation::Shr(dest, src)           => { implementations::shr(exec, dest, src) },
            Operation::Shl(dest, src)           => { implementations::shl(exec, dest, src) },
            Operation::Font(glyph)              => { implementations::font(exec, glyph) },
            Operation::Bcd(value)               => { implementations::bcd(exec, value) },
            Operation::SkipEq(lhs, rhs)         => { implementations::skip_eq(exec, lhs, rhs) },
            Operation::SkipNotEq(lhs, rhs)      => { implementations::skip_not_eq(exec, lhs, rhs) },
            Operation::SkipKey(key)             => { implementations::skip_key_pressed(exec, key) },
            Operation::SkipNotKey(key)          => { implementations::skip_key_not_pressed(exec, key) },
            Operation::WaitKey(key)             => { implementations::wait_key(exec, key) },
            Operation::Cls                      => { implementations::clear_screen(exec) },
            Operation::Stash(last)              => { implementations::stash(exec, last) },
            Operation::Fetch(last)              => { implementations::fetch(exec, last) },
            Operation::Rand(dest, src, mask)    => { implementations::random(exec, dest, src, mask) },
            Operation::Sprite(x, y, n)          => { implementations::sprite(exec, x, y, n) },
        }
    }
}

#[derive(Copy,Clone,Eq,PartialEq)]
pub enum OperationKind {
    NoOp,
    Load(DestKind, SrcKind),
    Store(DestKind),
    Stash(SrcKind),
    Fetch(DestKind),

    Jump(DestKind),
    JumpV0(DestKind),
    Call(DestKind),
    Ret,

    SkipEq(SrcKind, SrcKind),
    SkipNotEq(SrcKind, SrcKind),
    SkipKey(SrcKind),
    SkipNotKey(SrcKind),

    Add(SrcKind, SrcKind, DestKind),
    Sub(DestKind, SrcKind),
    Subn(DestKind, SrcKind),

    Or(DestKind, SrcKind),
    And(DestKind, SrcKind),
    Xor(DestKind, SrcKind),
    Shr(DestKind, SrcKind),
    Shl(DestKind, SrcKind),

    Rand(DestKind),

    Cls,
    Sprite(SrcKind, SrcKind, SrcKind),
    Font(SrcKind),
    Bcd(SrcKind),
    WaitKey(SrcKind),
}
<commit_msg>Complete Operation -> OperationKind<commit_after>//! Enumerates the types of operations and their operands.
//!
//! An `OperationKind` can be converted to an Operation by

use std::boxed::FnBox;

mod execute;
mod implementations;

pub use self::execute::Execute;

use types::*;
use instruction::{Dest, Src, DestKind, SrcKind };
// use self::implementations::*;

#[derive(Copy,Clone,Eq,PartialEq)]
pub enum OperationKind {
    NoOp,
    Load(DestKind, SrcKind),
    Stash(SrcKind),
    Fetch(SrcKind),

    Jump(SrcKind),
    JumpV0(SrcKind),
    Call(SrcKind),
    Ret,

    SkipEq(SrcKind, SrcKind),
    SkipNotEq(SrcKind, SrcKind),
    SkipKey(SrcKind),
    SkipNotKey(SrcKind),

    Add(DestKind, SrcKind, SrcKind),
    Sub(DestKind, SrcKind, SrcKind),

    Or(DestKind, SrcKind, SrcKind),
    And(DestKind, SrcKind, SrcKind),
    Xor(DestKind, SrcKind, SrcKind),
    Shr(DestKind, SrcKind),
    Shl(DestKind, SrcKind),

    Rand(DestKind, SrcKind, SrcKind),

    Cls,
    Sprite(SrcKind, SrcKind, SrcKind),
    Font(SrcKind),
    Bcd(SrcKind),
    WaitKey(SrcKind),
}

#[derive(Copy,Clone,Eq,PartialEq)]
pub enum Operation {
    NoOp,
    Load(Dest, Src),
    Stash(Src),
    Fetch(Src),

    Jump(Src),
    JumpV0(Src),
    Call(Src),
    Ret,

    SkipEq(Src, Src),
    SkipNotEq(Src, Src),
    SkipKey(Src),
    SkipNotKey(Src),

    Add(Dest, Src, Src),
    Sub(Dest, Src, Src),

    Or(Dest, Src, Src),
    And(Dest, Src, Src),
    Xor(Dest, Src, Src),
    Shr(Dest, Src),
    Shl(Dest, Src),

    Rand(Dest, Src, Src),

    Cls,
    Sprite(Src, Src, Src),
    Font(Src),
    Bcd(Src),
    WaitKey(Src),
}

impl Operation {
    ///
    pub fn kind(&self) -> OperationKind {
        match *self {
            Operation::NoOp => OperationKind::NoOp,
            Operation::Load(d, s) => OperationKind::Load(d.kind(), s.kind()),

            Operation::Stash(s) => OperationKind::Stash(s.kind()),
            Operation::Fetch(s) => OperationKind::Fetch(s.kind()),

            Operation::Jump(d) => OperationKind::Jump(d.kind()),
            Operation::JumpV0(d) => OperationKind::JumpV0(d.kind()),

            Operation::Call(d) => OperationKind::Call(d.kind()),
            Operation::Ret => OperationKind::Ret,

            Operation::SkipEq(a, b) => OperationKind::SkipEq(a.kind(), b.kind()),
            Operation::SkipNotEq(a, b) => OperationKind::SkipNotEq(a.kind(), b.kind()),
            Operation::SkipKey(n) => OperationKind::SkipKey(n.kind()),
            Operation::SkipNotKey(n) => OperationKind::SkipNotKey(n.kind()),

            Operation::Add(d, a, b) => OperationKind::Add(d.kind(), a.kind(), b.kind()),
            Operation::Sub(d, a, b) => OperationKind::Sub(d.kind(), a.kind(), b.kind()),

            Operation::Or(d, a, b) => OperationKind::Or(d.kind(), a.kind(), b.kind()),
            Operation::And(d, a, b) => OperationKind::And(d.kind(), a.kind(), b.kind()),
            Operation::Xor(d, a, b) => OperationKind::Xor(d.kind(), a.kind(), b.kind()),
            Operation::Shr(d, s) => OperationKind::Shr(d.kind(), s.kind()),
            Operation::Shl(d, s) => OperationKind::Shl(d.kind(), s.kind()),

            Operation::Rand(d, s, m) => OperationKind::Rand(d.kind(), s.kind(), m.kind()),

            Operation::Cls => OperationKind::Cls,

            Operation::Sprite(x, y, n) => OperationKind::Sprite(x.kind(), y.kind(), n.kind()),
            Operation::Font(s) => OperationKind::Font(s.kind()),
            Operation::Bcd(s) => OperationKind::Bcd(s.kind()),

            Operation::WaitKey(n) => OperationKind::WaitKey(n.kind()),
        }
    }

    /// Execute the operation on exec.
    pub fn execute(&self, exec: &mut Execute) -> Chip8Result<()> {
        match *self {
            Operation::NoOp                     => { Ok(()) },
            Operation::Load(dest, src)          => { implementations::load(exec, dest, src) },
            Operation::Add(dest, lhs, rhs)      => { implementations::add(exec, dest, lhs, rhs) },
            Operation::Sub(dest, lhs, rhs)      => { implementations::sub(exec, dest, lhs, rhs) },
            Operation::Jump(addr)               => { implementations::jump(exec, addr) },
            Operation::JumpV0(addr)             => { implementations::jump_v0(exec, addr) },
            Operation::Call(addr)               => { implementations::call(exec, addr) },
            Operation::Ret                      => { implementations::ret(exec) },
            Operation::Or(dest, lhs, rhs)       => { implementations::or(exec, dest, lhs, rhs) },
            Operation::And(dest, lhs, rhs)      => { implementations::and(exec, dest, lhs, rhs) },
            Operation::Xor(dest, lhs, rhs)      => { implementations::xor(exec, dest, lhs, rhs) },
            Operation::Shr(dest, src)           => { implementations::shr(exec, dest, src) },
            Operation::Shl(dest, src)           => { implementations::shl(exec, dest, src) },
            Operation::Font(glyph)              => { implementations::font(exec, glyph) },
            Operation::Bcd(value)               => { implementations::bcd(exec, value) },
            Operation::SkipEq(lhs, rhs)         => { implementations::skip_eq(exec, lhs, rhs) },
            Operation::SkipNotEq(lhs, rhs)      => { implementations::skip_not_eq(exec, lhs, rhs) },
            Operation::SkipKey(key)             => { implementations::skip_key_pressed(exec, key) },
            Operation::SkipNotKey(key)          => { implementations::skip_key_not_pressed(exec, key) },
            Operation::WaitKey(key)             => { implementations::wait_key(exec, key) },
            Operation::Cls                      => { implementations::clear_screen(exec) },
            Operation::Stash(last)              => { implementations::stash(exec, last) },
            Operation::Fetch(last)              => { implementations::fetch(exec, last) },
            Operation::Rand(dest, src, mask)    => { implementations::random(exec, dest, src, mask) },
            Operation::Sprite(x, y, n)          => { implementations::sprite(exec, x, y, n) },
        }
    }
}
<|endoftext|>


<commit_before>//Copyright (c) 2016, Ruslan Baratov, Alex Frappier Lachapelle
//All rights reserved.

use std::collections::BTreeMap;
use std::fmt;
use std::sync::{Arc, Mutex};

use config;
use webhooks;

include!("logger_macros.rs");

////////////////////////////////////////////////////////////
//                         Command                        //
////////////////////////////////////////////////////////////

//                            Config,                             Raw webhook,            Arguments
pub type CommandCallback = fn(&Arc<Mutex<config::ConfigHandler>>, webhooks::WebhookEvent, Vec<&str>) -> Result<String, String>;

pub struct Command {
    pub requires_please: bool,
    pub whitelist_only:  bool,
    pub callback:        CommandCallback
}

impl Command {
    pub fn new(requires_please: bool, whitelist_only: bool, callback: CommandCallback) -> Command {
        Command{
            requires_please: requires_please,
            whitelist_only:  whitelist_only,
            callback:        callback
        }
    }
}

impl fmt::Debug for Command {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Command {{requires_please: {}, whitelist_only: {}, callback: fn(&Arc<Mutex<config::ConfigHandler>>, webhooks::WebhookEvent, Vec<&str>) -> Result<String, String>}}", self.requires_please, self.whitelist_only)
    }
}

////////////////////////////////////////////////////////////
//                     CommandHandler                     //
////////////////////////////////////////////////////////////

#[derive(Debug)]
pub struct CommandHandler {
    config:   Arc<Mutex<config::ConfigHandler>>,
    commands: BTreeMap<String, Command>
}

impl CommandHandler {

    pub fn new(tsconfig: &Arc<Mutex<config::ConfigHandler>>) -> CommandHandler {

        thread_trace!("commands.rs: CommandHandler::new(tsconfig)");

        //Register commands
        let mut commands: BTreeMap<String, Command> = BTreeMap::new();
        commands.insert(String::from("ping"), Command::new(false, false, ping));

        CommandHandler {
            config:   tsconfig.clone(),
            commands: commands
        }
    }

    pub fn parse_command(&self, webhook: webhooks::WebhookEvent) {

        thread_trace!("commands.rs: CommandHandler::parse_command(&self, webhook)");

        let mut tokens: Vec<&str>   = webhook.command.split_whitespace().collect();
        let mut is_please_provided  = false;
        let mut is_user_whitelisted = false;
        let mut next_token_index    = 0;

        thread_debug!("Command tokens: {:?}", tokens);

        //Check if please was said
        if tokens[0].to_lowercase() == "please" {
            is_please_provided = true;
            next_token_index   = 1;
        }
        thread_debug!("Please was said: {}", is_please_provided);

        //Check if user is whitelisted for restricted commands
        //  Restricted scope, we are using the config here but
        //  the callback can also use it so we force the mutex
        //  to unlock before letting the callback lock it
        //  given that locking twice will induce a panic.
        {
            let mut config = self.config.lock().unwrap();
            is_user_whitelisted = config.whitelist_validate_user(webhook.clone().user);
        }

        //Find command among registered commands
        thread_trace!("Check if command exists.");
        thread_debug!("Looking for command: {}", tokens[next_token_index]);
        let command = match self.commands.get(tokens[next_token_index]) {
            Some(command) => {
                thread_trace!("Command exists.");
                command
            },
            None          => {
                thread_trace!("Command does not exists, send response.");
                respond(&self.config, webhook.clone(), String::from("Sorry the command was not found."));
                return;
            }
        };

        //Check if please and whitelist is required
        let mut run_cmd         = false;
        let mut response_prefix = String::new();

        thread_trace!("Check for whitelist and please.");
        thread_debug!("is_please_provided:  {}", is_please_provided);
        thread_debug!("is_user_whitelisted: {}", is_user_whitelisted);

        if !(command.whitelist_only && !is_user_whitelisted) {
            if (command.requires_please && is_please_provided) || (!command.requires_please && !is_please_provided) {
                run_cmd = true;
            } else if !command.requires_please && is_please_provided {
                run_cmd         = true;
                response_prefix = String::from("You didn't need to say please but thanks anyways :smiley:\n");
            } else if command.requires_please && !is_please_provided {
                //TODO: keep please state
                respond(&self.config, webhook.clone(), String::from("Whats the magic word?"));
            }
        } else {
            respond(&self.config, webhook.clone(), String::from("Sorry! That command if for whitelisted people only!"));
        }

        thread_debug!("run_cmd: {}", run_cmd);

        if run_cmd {
            match (command.callback)(&self.config, webhook.clone(), tokens.split_off(next_token_index)) {
                Ok(msg)  => {
                    respond(&self.config, webhook.clone(), format!("{}{}", response_prefix, msg));
                }
                Err(msg) => {
                    respond(&self.config, webhook.clone(), format!("An error occurred while executing the command: {}", msg));
                }
            }
        }
    }
}

////////////////////////////////////////////////////////////
//                        Callbacks                       //
////////////////////////////////////////////////////////////

pub fn ping(tsconfig: &Arc<Mutex<config::ConfigHandler>>, raw_event: webhooks::WebhookEvent, Args: Vec<&str>) -> Result<String, String> {
    return Ok(String::from("Pong"))
}

////////////////////////////////////////////////////////////
//                          Utils                         //
////////////////////////////////////////////////////////////

pub fn respond(tsconfig: &Arc<Mutex<config::ConfigHandler>>, raw_event: webhooks::WebhookEvent, msg: String) {

    thread_trace!("commands.rs: respond(tsconfig, raw_event, msg)");

    //TODO: Don't forget to change this when refactoring config
    //Get repo were following
    let github_follow_repo: String;
    let github_owner_token: String;
    {
        let mut config = tsconfig.lock().unwrap();
        match config.get_string("config", "github_follow_repo") {
            Ok(_github_follow_repo) => github_follow_repo = _github_follow_repo,
            Err(err)                => {
                thread_error!("Failed to acquire \"github_follow_repo\": {}", err);
                panic!("Failed to acquire \"github_follow_repo\": {}", err);
            }
        }

        //Get owner api token
        match config.get_string("config", "github_bot_token") {
            Ok(_github_owner_token) => github_owner_token = _github_owner_token,
            Err(err)                => {
                thread_error!("Error getting  the \"github_owner_token\" value from config: {}", err);
                panic!("Error getting the \"github_owner_token\" value from config: {}", err);
            }
        }
    }

    let endpoint = format!("repos/{}/issues/{}/comments?access_token={}", github_follow_repo, raw_event.number, github_owner_token);
    let message  = format!("{{\"body\": \"@{} {}\"}}", raw_event.user, msg);
    match webhooks::github_post_request(endpoint, message) {
        Ok(())   => (),
        Err(err) => {thread_error!("{}", err);}
    }
}
<commit_msg>Fixed bad request on please not required<commit_after>//Copyright (c) 2016, Ruslan Baratov, Alex Frappier Lachapelle
//All rights reserved.

use std::collections::BTreeMap;
use std::fmt;
use std::sync::{Arc, Mutex};

use config;
use webhooks;

include!("logger_macros.rs");

////////////////////////////////////////////////////////////
//                         Command                        //
////////////////////////////////////////////////////////////

//                            Config,                             Raw webhook,            Arguments
pub type CommandCallback = fn(&Arc<Mutex<config::ConfigHandler>>, webhooks::WebhookEvent, Vec<&str>) -> Result<String, String>;

pub struct Command {
    pub requires_please: bool,
    pub whitelist_only:  bool,
    pub callback:        CommandCallback
}

impl Command {
    pub fn new(requires_please: bool, whitelist_only: bool, callback: CommandCallback) -> Command {
        Command{
            requires_please: requires_please,
            whitelist_only:  whitelist_only,
            callback:        callback
        }
    }
}

impl fmt::Debug for Command {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Command {{requires_please: {}, whitelist_only: {}, callback: fn(&Arc<Mutex<config::ConfigHandler>>, webhooks::WebhookEvent, Vec<&str>) -> Result<String, String>}}", self.requires_please, self.whitelist_only)
    }
}

////////////////////////////////////////////////////////////
//                     CommandHandler                     //
////////////////////////////////////////////////////////////

#[derive(Debug)]
pub struct CommandHandler {
    config:   Arc<Mutex<config::ConfigHandler>>,
    commands: BTreeMap<String, Command>
}

impl CommandHandler {

    pub fn new(tsconfig: &Arc<Mutex<config::ConfigHandler>>) -> CommandHandler {

        thread_trace!("commands.rs: CommandHandler::new(tsconfig)");

        //Register commands
        let mut commands: BTreeMap<String, Command> = BTreeMap::new();
        commands.insert(String::from("ping"), Command::new(false, false, ping));

        CommandHandler {
            config:   tsconfig.clone(),
            commands: commands
        }
    }

    pub fn parse_command(&self, webhook: webhooks::WebhookEvent) {

        thread_trace!("commands.rs: CommandHandler::parse_command(&self, webhook)");

        let mut tokens: Vec<&str>   = webhook.command.split_whitespace().collect();
        let mut is_please_provided  = false;
        let mut is_user_whitelisted = false;
        let mut next_token_index    = 0;

        thread_debug!("Command tokens: {:?}", tokens);

        //Check if please was said
        if tokens[0].to_lowercase() == "please" {
            is_please_provided = true;
            next_token_index   = 1;
        }
        thread_debug!("Please was said: {}", is_please_provided);

        //Check if user is whitelisted for restricted commands
        //  Restricted scope, we are using the config here but
        //  the callback can also use it so we force the mutex
        //  to unlock before letting the callback lock it
        //  given that locking twice will induce a panic.
        {
            let mut config = self.config.lock().unwrap();
            is_user_whitelisted = config.whitelist_validate_user(webhook.clone().user);
        }

        //Find command among registered commands
        thread_trace!("Check if command exists.");
        thread_debug!("Looking for command: {}", tokens[next_token_index]);
        let command = match self.commands.get(tokens[next_token_index]) {
            Some(command) => {
                thread_trace!("Command exists.");
                command
            },
            None          => {
                thread_trace!("Command does not exists, send response.");
                respond(&self.config, webhook.clone(), String::from("Sorry the command was not found."));
                return;
            }
        };

        //Check if please and whitelist is required
        let mut run_cmd         = false;
        let mut response_prefix = String::new();

        thread_trace!("Check for whitelist and please.");
        thread_debug!("is_please_provided:  {}", is_please_provided);
        thread_debug!("is_user_whitelisted: {}", is_user_whitelisted);

        if !(command.whitelist_only && !is_user_whitelisted) {
            if (command.requires_please && is_please_provided) || (!command.requires_please && !is_please_provided) {
                run_cmd = true;
            } else if !command.requires_please && is_please_provided {
                run_cmd         = true;
                response_prefix = String::from("You didn't need to say please but thanks anyways :smiley: Ohh and also: ");
            } else if command.requires_please && !is_please_provided {
                //TODO: keep please state
                respond(&self.config, webhook.clone(), String::from("Whats the magic word?"));
            }
        } else {
            respond(&self.config, webhook.clone(), String::from("Sorry! That command if for whitelisted people only!"));
        }

        thread_debug!("run_cmd: {}", run_cmd);

        if run_cmd {
            match (command.callback)(&self.config, webhook.clone(), tokens.split_off(next_token_index)) {
                Ok(msg)  => {
                    respond(&self.config, webhook.clone(), format!("{}{}", response_prefix, msg));
                }
                Err(msg) => {
                    respond(&self.config, webhook.clone(), format!("An error occurred while executing the command: {}", msg));
                }
            }
        }
    }
}

////////////////////////////////////////////////////////////
//                        Callbacks                       //
////////////////////////////////////////////////////////////

pub fn ping(tsconfig: &Arc<Mutex<config::ConfigHandler>>, raw_event: webhooks::WebhookEvent, Args: Vec<&str>) -> Result<String, String> {
    return Ok(String::from("Pong"))
}

////////////////////////////////////////////////////////////
//                          Utils                         //
////////////////////////////////////////////////////////////

pub fn respond(tsconfig: &Arc<Mutex<config::ConfigHandler>>, raw_event: webhooks::WebhookEvent, msg: String) {

    thread_trace!("commands.rs: respond(tsconfig, raw_event, msg)");

    //TODO: Don't forget to change this when refactoring config
    //Get repo were following
    let github_follow_repo: String;
    let github_owner_token: String;
    {
        let mut config = tsconfig.lock().unwrap();
        match config.get_string("config", "github_follow_repo") {
            Ok(_github_follow_repo) => github_follow_repo = _github_follow_repo,
            Err(err)                => {
                thread_error!("Failed to acquire \"github_follow_repo\": {}", err);
                panic!("Failed to acquire \"github_follow_repo\": {}", err);
            }
        }

        //Get owner api token
        match config.get_string("config", "github_bot_token") {
            Ok(_github_owner_token) => github_owner_token = _github_owner_token,
            Err(err)                => {
                thread_error!("Error getting  the \"github_owner_token\" value from config: {}", err);
                panic!("Error getting the \"github_owner_token\" value from config: {}", err);
            }
        }
    }

    let endpoint = format!("repos/{}/issues/{}/comments?access_token={}", github_follow_repo, raw_event.number, github_owner_token);
    let message  = format!("{{\"body\": \"@{} {}\"}}", raw_event.user, msg);
    match webhooks::github_post_request(endpoint, message) {
        Ok(())   => (),
        Err(err) => {thread_error!("{}", err);}
    }
}
<|endoftext|>