<commit_before>// Copyright 2017 The Exonum Team
//
// 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,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! `RESTful` API and corresponding utilities.

use std::ops::Deref;
use std::marker::PhantomData;
use std::io;
use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;

use iron::IronError;
use iron::prelude::*;
use iron::status;
use iron::headers::Cookie;
use hyper::header::{ContentType, SetCookie};
use cookie::Cookie as CookiePair;
use router::Router;
use params;
use serde_json;
use serde::{Serialize, Serializer};
use serde::de::{self, Visitor, Deserialize, Deserializer};
use failure::Fail;

use crypto::{PublicKey, SecretKey, Hash};
use encoding::serialize::{FromHex, FromHexError, ToHex, encode_hex};
use storage;

pub mod public;
pub mod private;
#[cfg(test)]
mod tests;

/// List of possible Api errors.
#[derive(Fail, Debug)]
pub enum ApiError {
    /// Storage error.
    #[fail(display = "Storage error: {}", _0)]
    Storage(
        #[cause]
        storage::Error
    ),

    /// Input/output error.
    #[fail(display = "IO error: {}", _0)]
    Io(
        #[cause]
        ::std::io::Error
    ),

    /// File not found.
    #[fail(display = "File not found")]
    FileNotFound(Hash),

    /// Not found.
    #[fail(display = "Not found")]
    NotFound,

    /// File too big.
    #[fail(display = "File too big")]
    FileTooBig,

    /// File already exists.
    #[fail(display = "File exists")]
    FileExists(Hash),

    /// Bad request.
    #[fail(display = "Bad request: {}", _0)]
    BadRequest(String),

    /// Internal error.
    #[fail(display = "Internal server error: {}", _0)]
    InternalError(Box<::std::error::Error + Send + Sync>),

    /// Unauthorized error.
    #[fail(display = "Unauthorized")]
    Unauthorized,
}

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

impl From<storage::Error> for ApiError {
    fn from(e: storage::Error) -> ApiError {
        ApiError::Storage(e)
    }
}

impl From<ApiError> for IronError {
    fn from(e: ApiError) -> IronError {
        let code = match e {
            ApiError::FileExists(..) => status::Conflict,
            ApiError::FileNotFound(..) |
            ApiError::NotFound => status::NotFound,
            // Note that `status::Unauthorized` does not fit here, because
            //
            // > A server generating a 401 (Unauthorized) response MUST send a
            // > WWW-Authenticate header field containing at least one challenge.
            //
            // https://tools.ietf.org/html/rfc7235#section-4.1
            ApiError::Unauthorized => status::Forbidden,

            ApiError::BadRequest(..) |
            ApiError::FileTooBig => status::BadRequest,

            ApiError::Storage(..) |
            ApiError::Io(..) |
            ApiError::InternalError(..) => status::InternalServerError,
        };
        let body = {
            let mut map = BTreeMap::new();
            map.insert("debug", format!("{:?}", e));
            map.insert("description", e.to_string());
            match e {
                ApiError::FileExists(hash) |
                ApiError::FileNotFound(hash) => {
                    map.insert("hash", encode_hex(&hash));
                }
                _ => (),
            }
            ::serde_json::to_string_pretty(&map).unwrap()
        };
        IronError::new(e.compat(), (code, body))
    }
}

/// `Field` that is serialized/deserialized from/to hex.
#[derive(Clone, Debug)]
struct HexField<T: AsRef<[u8]> + Clone>(pub T);

impl<T> Deref for HexField<T>
where
    T: AsRef<[u8]> + Clone,
{
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

impl<T> Serialize for HexField<T>
where
    T: AsRef<[u8]> + Clone,
{
    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        ser.serialize_str(&encode_hex(&self.0))
    }
}

struct HexVisitor<T>
where
    T: AsRef<[u8]> + Clone + FromHex<Error = FromHexError>,
{
    _p: PhantomData<T>,
}

impl<'v, T> Visitor<'v> for HexVisitor<T>
where
    T: AsRef<[u8]> + Clone + FromHex<Error = FromHexError>,
{
    type Value = HexField<T>;

    fn expecting(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(fmt, "expected hex represented string")
    }

    fn visit_str<E>(self, s: &str) -> Result<HexField<T>, E>
    where
        E: de::Error,
    {
        let v = T::from_hex(s).map_err(|_| de::Error::custom("Invalid hex"))?;
        Ok(HexField(v))
    }
}

impl<'de, T> Deserialize<'de> for HexField<T>
where
    T: AsRef<[u8]>
        + FromHex<Error = FromHexError>
        + ToHex
        + Clone,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(HexVisitor { _p: PhantomData })
    }
}

/// `Api` trait defines `RESTful` API.
pub trait Api {
    /// Loads hex value from the cookies.
    fn load_hex_value_from_cookie<'a>(
        &self,
        request: &'a Request,
        key: &str,
    ) -> storage::Result<Vec<u8>> {
        if let Some(&Cookie(ref cookies)) = request.headers.get() {
            for cookie in cookies.iter() {
                if let Ok(c) = CookiePair::parse(cookie.as_str()) {
                    if c.name() == key {
                        if let Ok(value) = FromHex::from_hex(c.value()) {
                            return Ok(value);
                        }
                    }
                }
            }
        }
        Err(storage::Error::new(
            format!("Unable to find value with given key {}", key),
        ))
    }

    /// Loads public and secret key from the cookies.
    fn load_keypair_from_cookies(
        &self,
        request: &Request,
    ) -> Result<(PublicKey, SecretKey), ApiError> {
        let public_key = PublicKey::from_slice(
            self.load_hex_value_from_cookie(request, "public_key")?
                .as_ref(),
        );
        let secret_key = SecretKey::from_slice(
            self.load_hex_value_from_cookie(request, "secret_key")?
                .as_ref(),
        );

        let public_key = public_key.ok_or(ApiError::Unauthorized)?;
        let secret_key = secret_key.ok_or(ApiError::Unauthorized)?;
        Ok((public_key, secret_key))
    }

    //TODO: Remove duplicate code
    /// Returns NotFound and some response with cookies.
    fn not_found_response_with_cookies(
        &self,
        json: &serde_json::Value,
        cookies: Option<Vec<String>>,
    ) -> IronResult<Response> {
        let mut resp = Response::with((
            status::NotFound,
            serde_json::to_string_pretty(json).unwrap(),
        ));
        resp.headers.set(ContentType::json());
        if let Some(cookies) = cookies {
            resp.headers.set(SetCookie(cookies));
        }
        Ok(resp)
    }

    /// Returns OK and some response with cookies.
    fn ok_response_with_cookies(
        &self,
        json: &serde_json::Value,
        cookies: Option<Vec<String>>,
    ) -> IronResult<Response> {
        let mut resp = Response::with((status::Ok, serde_json::to_string_pretty(json).unwrap()));
        resp.headers.set(ContentType::json());
        if let Some(cookies) = cookies {
            resp.headers.set(SetCookie(cookies));
        }
        Ok(resp)
    }

    /// Returns OK and some response.
    fn ok_response(&self, json: &serde_json::Value) -> IronResult<Response> {
        self.ok_response_with_cookies(json, None)
    }
    /// Returns NotFound and some response.
    fn not_found_response(&self, json: &serde_json::Value) -> IronResult<Response> {
        self.not_found_response_with_cookies(json, None)
    }

    /// Used to extend Api.
    fn wire<'b>(&self, router: &'b mut Router);
}

fn url_fragment<T>(request: &Request, name: &str) -> Result<T, ApiError>
where
    T: FromStr,
    T::Err: fmt::Display,
{
    let params = request.extensions.get::<Router>().unwrap();
    let fragment = params.find(name).ok_or_else(|| {
        ApiError::BadRequest(format!("Required parameter '{}' is missing", name))
    })?;
    let value: T = T::from_str(fragment).map_err(|e| {
        ApiError::BadRequest(format!("Invalid '{}' parameter: {}", name, e))
    })?;
    Ok(value)
}

fn optional_param<T>(request: &mut Request, name: &str) -> Result<Option<T>, ApiError>
where
    T: FromStr,
    T::Err: fmt::Display,
{
    let map = request.get_ref::<params::Params>().unwrap();
    let value = match map.find(&[name]) {
        Some(&params::Value::String(ref param)) => {
            let value: T = T::from_str(param).map_err(|e| {
                ApiError::BadRequest(format!("Invalid '{}' parameter: {}", name, e))
            })?;
            Some(value)
        }
        _ => None,
    };
    Ok(value)
}

fn required_param<T>(request: &mut Request, name: &str) -> Result<T, ApiError>
where
    T: FromStr,
    T::Err: fmt::Display,
{
    optional_param(request, name)?.ok_or_else(|| {
        ApiError::BadRequest(format!("Required parameter '{}' is missing", name))
    })
}
<commit_msg>Remove unused ApiError variants<commit_after>// Copyright 2017 The Exonum Team
//
// 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,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! `RESTful` API and corresponding utilities.

use std::ops::Deref;
use std::marker::PhantomData;
use std::io;
use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;

use iron::IronError;
use iron::prelude::*;
use iron::status;
use iron::headers::Cookie;
use hyper::header::{ContentType, SetCookie};
use cookie::Cookie as CookiePair;
use router::Router;
use params;
use serde_json;
use serde::{Serialize, Serializer};
use serde::de::{self, Visitor, Deserialize, Deserializer};
use failure::Fail;

use crypto::{PublicKey, SecretKey};
use encoding::serialize::{FromHex, FromHexError, ToHex, encode_hex};
use storage;

pub mod public;
pub mod private;
#[cfg(test)]
mod tests;

/// List of possible Api errors.
#[derive(Fail, Debug)]
pub enum ApiError {
    /// Storage error.
    #[fail(display = "Storage error: {}", _0)]
    Storage(
        #[cause]
        storage::Error
    ),

    /// Input/output error.
    #[fail(display = "IO error: {}", _0)]
    Io(
        #[cause]
        ::std::io::Error
    ),

    /// Bad request.
    #[fail(display = "Bad request: {}", _0)]
    BadRequest(String),

    /// Internal error.
    #[fail(display = "Internal server error: {}", _0)]
    InternalError(Box<::std::error::Error + Send + Sync>),

    /// Unauthorized error.
    #[fail(display = "Unauthorized")]
    Unauthorized,
}

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

impl From<storage::Error> for ApiError {
    fn from(e: storage::Error) -> ApiError {
        ApiError::Storage(e)
    }
}

impl From<ApiError> for IronError {
    fn from(e: ApiError) -> IronError {
        let code = match e {
            // Note that `status::Unauthorized` does not fit here, because
            //
            // > A server generating a 401 (Unauthorized) response MUST send a
            // > WWW-Authenticate header field containing at least one challenge.
            //
            // https://tools.ietf.org/html/rfc7235#section-4.1
            ApiError::Unauthorized => status::Forbidden,

            ApiError::BadRequest(..) => status::BadRequest,

            ApiError::Storage(..) |
            ApiError::Io(..) |
            ApiError::InternalError(..) => status::InternalServerError,
        };
        let body = {
            let mut map = BTreeMap::new();
            map.insert("debug", format!("{:?}", e));
            map.insert("description", e.to_string());
            ::serde_json::to_string_pretty(&map).unwrap()
        };
        IronError::new(e.compat(), (code, body))
    }
}

/// `Field` that is serialized/deserialized from/to hex.
#[derive(Clone, Debug)]
struct HexField<T: AsRef<[u8]> + Clone>(pub T);

impl<T> Deref for HexField<T>
where
    T: AsRef<[u8]> + Clone,
{
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

impl<T> Serialize for HexField<T>
where
    T: AsRef<[u8]> + Clone,
{
    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        ser.serialize_str(&encode_hex(&self.0))
    }
}

struct HexVisitor<T>
where
    T: AsRef<[u8]> + Clone + FromHex<Error = FromHexError>,
{
    _p: PhantomData<T>,
}

impl<'v, T> Visitor<'v> for HexVisitor<T>
where
    T: AsRef<[u8]> + Clone + FromHex<Error = FromHexError>,
{
    type Value = HexField<T>;

    fn expecting(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(fmt, "expected hex represented string")
    }

    fn visit_str<E>(self, s: &str) -> Result<HexField<T>, E>
    where
        E: de::Error,
    {
        let v = T::from_hex(s).map_err(|_| de::Error::custom("Invalid hex"))?;
        Ok(HexField(v))
    }
}

impl<'de, T> Deserialize<'de> for HexField<T>
where
    T: AsRef<[u8]>
        + FromHex<Error = FromHexError>
        + ToHex
        + Clone,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(HexVisitor { _p: PhantomData })
    }
}

/// `Api` trait defines `RESTful` API.
pub trait Api {
    /// Loads hex value from the cookies.
    fn load_hex_value_from_cookie<'a>(
        &self,
        request: &'a Request,
        key: &str,
    ) -> storage::Result<Vec<u8>> {
        if let Some(&Cookie(ref cookies)) = request.headers.get() {
            for cookie in cookies.iter() {
                if let Ok(c) = CookiePair::parse(cookie.as_str()) {
                    if c.name() == key {
                        if let Ok(value) = FromHex::from_hex(c.value()) {
                            return Ok(value);
                        }
                    }
                }
            }
        }
        Err(storage::Error::new(
            format!("Unable to find value with given key {}", key),
        ))
    }

    /// Loads public and secret key from the cookies.
    fn load_keypair_from_cookies(
        &self,
        request: &Request,
    ) -> Result<(PublicKey, SecretKey), ApiError> {
        let public_key = PublicKey::from_slice(
            self.load_hex_value_from_cookie(request, "public_key")?
                .as_ref(),
        );
        let secret_key = SecretKey::from_slice(
            self.load_hex_value_from_cookie(request, "secret_key")?
                .as_ref(),
        );

        let public_key = public_key.ok_or(ApiError::Unauthorized)?;
        let secret_key = secret_key.ok_or(ApiError::Unauthorized)?;
        Ok((public_key, secret_key))
    }

    //TODO: Remove duplicate code
    /// Returns NotFound and some response with cookies.
    fn not_found_response_with_cookies(
        &self,
        json: &serde_json::Value,
        cookies: Option<Vec<String>>,
    ) -> IronResult<Response> {
        let mut resp = Response::with((
            status::NotFound,
            serde_json::to_string_pretty(json).unwrap(),
        ));
        resp.headers.set(ContentType::json());
        if let Some(cookies) = cookies {
            resp.headers.set(SetCookie(cookies));
        }
        Ok(resp)
    }

    /// Returns OK and some response with cookies.
    fn ok_response_with_cookies(
        &self,
        json: &serde_json::Value,
        cookies: Option<Vec<String>>,
    ) -> IronResult<Response> {
        let mut resp = Response::with((status::Ok, serde_json::to_string_pretty(json).unwrap()));
        resp.headers.set(ContentType::json());
        if let Some(cookies) = cookies {
            resp.headers.set(SetCookie(cookies));
        }
        Ok(resp)
    }

    /// Returns OK and some response.
    fn ok_response(&self, json: &serde_json::Value) -> IronResult<Response> {
        self.ok_response_with_cookies(json, None)
    }
    /// Returns NotFound and some response.
    fn not_found_response(&self, json: &serde_json::Value) -> IronResult<Response> {
        self.not_found_response_with_cookies(json, None)
    }

    /// Used to extend Api.
    fn wire<'b>(&self, router: &'b mut Router);
}

fn url_fragment<T>(request: &Request, name: &str) -> Result<T, ApiError>
where
    T: FromStr,
    T::Err: fmt::Display,
{
    let params = request.extensions.get::<Router>().unwrap();
    let fragment = params.find(name).ok_or_else(|| {
        ApiError::BadRequest(format!("Required parameter '{}' is missing", name))
    })?;
    let value: T = T::from_str(fragment).map_err(|e| {
        ApiError::BadRequest(format!("Invalid '{}' parameter: {}", name, e))
    })?;
    Ok(value)
}

fn optional_param<T>(request: &mut Request, name: &str) -> Result<Option<T>, ApiError>
where
    T: FromStr,
    T::Err: fmt::Display,
{
    let map = request.get_ref::<params::Params>().unwrap();
    let value = match map.find(&[name]) {
        Some(&params::Value::String(ref param)) => {
            let value: T = T::from_str(param).map_err(|e| {
                ApiError::BadRequest(format!("Invalid '{}' parameter: {}", name, e))
            })?;
            Some(value)
        }
        _ => None,
    };
    Ok(value)
}

fn required_param<T>(request: &mut Request, name: &str) -> Result<T, ApiError>
where
    T: FromStr,
    T::Err: fmt::Display,
{
    optional_param(request, name)?.ok_or_else(|| {
        ApiError::BadRequest(format!("Required parameter '{}' is missing", name))
    })
}
<|endoftext|>


<commit_before>use std::io::{self, Read};
use std::cmp;
use std::fmt;

use parse::{self, ParseError};
use value::{Value, Args, FromLisp, ToLisp};
use valtype::Type;
use env::Env;

pub type FuncResult = Result<Value, FuncError>;

pub enum FuncError {
    InvalidArguments {
        expected: Args,
        got: usize,
    },
    InvalidType {
        expected: Vec<Type>,
        got: Value,
    },
    UndeclaredSymbol(String),

    AttemptToCallNonFunction(Value),
    AttemptToEvalEmptyList,

    GivenEmptyList,

    IoError(io::Error),

    ParsingErr(ParseError),
}

pub struct Lisp {
    pub scopes: Vec<Env>,
}

impl Lisp {
    pub fn new() -> Lisp {
        Lisp {
            scopes: vec![Env::std_lib()],
        }
    }

    pub fn eval<T: FromLisp>(&mut self, code: &str) -> Result<T, FuncError> {
        let result = try!(self.eval_raw(code));

        return T::from_lisp(result);
    }

    pub fn eval_reader<R: Read>(&mut self, mut code: R) -> FuncResult {
        let mut read_string = String::new();
        match code.read_to_string(&mut read_string) {
            Ok(_) => (),
            Err(err) => return Err(FuncError::IoError(err)),
        }

        self.eval_raw(&read_string)
    }

    pub fn eval_raw(&mut self, code: &str) -> FuncResult {
        let tokens = match parse::parse_str(code) {
            Ok(tok) => tok,
            Err(err) => return Err(FuncError::ParsingErr(err)),
        };

        self.eval_token_vec(tokens)
    }

    pub fn eval_token_vec(&mut self, mut tokens: Vec<Value>) -> FuncResult {
        let ret_token = match tokens.pop() {
            Some(token) => token,
            None => unreachable!(),
        };

        for token in tokens {
            try!(self.eval_token(token));
        }

        self.eval_token(ret_token)
    }

    pub fn eval_token(&mut self, token: Value) -> FuncResult {
        match token {
            Value::Symbol(sym) => {
                for env in self.scopes.iter().rev() {
                    if let Some(val) = env.map.get(&sym) {
                        return Ok(val.clone());
                    }
                }

                Err(FuncError::UndeclaredSymbol(sym))
            },
            Value::List(mut tokens) => {
                if tokens.len() == 0 {
                  return Err(FuncError::AttemptToEvalEmptyList);
                }

                let val = tokens.remove(0);
                let func = try!(self.eval_token(val.clone()));
                self.sub_scope(); // each list has its own scope

                match func {
                    Value::HardFunc(hard_func) => {
                        let mut args = Vec::new();

                        for token in tokens {
                            args.push(try!(self.eval_token(token)));
                        }

                        match hard_func.args {
                            Args::Variant => (),
                            Args::Fixed(count) => {
                                if args.len() != count {
                                    invalid_args!(self, Args::Fixed(count), args);
                                }
                            },
                            Args::Multiple(possible_counts) => {
                                let mut arg_match = false;

                                // I wish I could label match statements and break out of them...
                                for count in &possible_counts {
                                    if args.len() == *count {
                                        arg_match = true;
                                        break;
                                    }
                                }

                                if !arg_match {
                                    invalid_args!(self, Args::Multiple(possible_counts), args);
                                }
                            },
                            Args::Atleast(count) => {
                                if args.len() < count {
                                    invalid_args!(self, Args::Atleast(count), args);
                                }
                            }
                        }

                        let result = (hard_func.func)(args, self);
                        self.exit_scope();

                        result
                    },
                    Value::Lambda { args, body } => {
                        if tokens.len() != args.len() {
                            invalid_args!(self, Args::Fixed(args.len()), tokens);
                        }

                        for sym in args.iter() {
                            let value = try!(self.eval_token(tokens.remove(0)));
                            self.cur_scope().set(sym, value);
                        }

                        let result = self.eval_token_vec(body);
                        self.exit_scope();

                        result
                    },
                    _ => return Err(FuncError::AttemptToCallNonFunction(val)),
                }
            },
            Value::Quote(val) => Ok(*val),
            _ => Ok(token),
        }
    }

    pub fn set_global<T: ToLisp>(&mut self, name: &str, value: T) {
        self.scopes[0].set(name, value);
    }

    pub fn sub_scope(&mut self) {
        self.scopes.push(Env::new());
    }

    pub fn exit_scope(&mut self) {
        self.scopes.pop();
    }

    pub fn cur_scope(&mut self) -> &mut Env {
        let index = self.scopes.len() - 1;
        &mut self.scopes[index]
    }

    pub fn parent_scope(&mut self) -> &mut Env {
        let index = cmp::max(self.scopes.len() as i32 - 2, 0) as usize;
        &mut self.scopes[index]
    }
}

impl fmt::Debug for Value {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match self {
            &Value::List(ref values) => parse::write_list(fmt, values, "(", " ", ")"),
            &Value::String(ref string) => write!(fmt, "{:?}", string),
            &Value::Symbol(ref string) => write!(fmt, "{}", string),
            &Value::Number(num) => write!(fmt, "{}", num),
            &Value::HardFunc(ref func) => write!(fmt, "HardFunc({:?})", func.args),
            &Value::Lambda { ref args, ref body }=> write!(fmt, "λ {:?} => {:?}", args, body),
            &Value::Nil => write!(fmt, "nil"),
            &Value::Bool(val) => write!(fmt, "{}", val),
            &Value::Quote(ref tok) => write!(fmt, "'{:?}", tok),
            &Value::Type(ref typ) => write!(fmt, "{:?}", typ),
            &Value::Foreign(ref val) => write!(fmt, "{:?}", val),
        }
    }
}
<commit_msg>Add ability to create Lisp with no stdlib<commit_after>use std::io::{self, Read};
use std::cmp;
use std::fmt;

use parse::{self, ParseError};
use value::{Value, Args, FromLisp, ToLisp};
use valtype::Type;
use env::Env;

pub type FuncResult = Result<Value, FuncError>;

pub enum FuncError {
    InvalidArguments {
        expected: Args,
        got: usize,
    },
    InvalidType {
        expected: Vec<Type>,
        got: Value,
    },
    UndeclaredSymbol(String),

    AttemptToCallNonFunction(Value),
    AttemptToEvalEmptyList,

    GivenEmptyList,

    IoError(io::Error),

    ParsingErr(ParseError),
}

pub struct Lisp {
    pub scopes: Vec<Env>,
}

impl Lisp {
    pub fn new() -> Lisp {
        Lisp {
            scopes: vec![Env::std_lib()],
        }
    }

    pub fn new_no_stdlib() -> Lisp {
        Lisp {
            scopes: vec![Env::new()],
        }
    }

    pub fn eval<T: FromLisp>(&mut self, code: &str) -> Result<T, FuncError> {
        let result = try!(self.eval_raw(code));

        return T::from_lisp(result);
    }

    pub fn eval_reader<R: Read>(&mut self, mut code: R) -> FuncResult {
        let mut read_string = String::new();
        match code.read_to_string(&mut read_string) {
            Ok(_) => (),
            Err(err) => return Err(FuncError::IoError(err)),
        }

        self.eval_raw(&read_string)
    }

    pub fn eval_raw(&mut self, code: &str) -> FuncResult {
        let tokens = match parse::parse_str(code) {
            Ok(tok) => tok,
            Err(err) => return Err(FuncError::ParsingErr(err)),
        };

        self.eval_token_vec(tokens)
    }

    pub fn eval_token_vec(&mut self, mut tokens: Vec<Value>) -> FuncResult {
        let ret_token = match tokens.pop() {
            Some(token) => token,
            None => unreachable!(),
        };

        for token in tokens {
            try!(self.eval_token(token));
        }

        self.eval_token(ret_token)
    }

    pub fn eval_token(&mut self, token: Value) -> FuncResult {
        match token {
            Value::Symbol(sym) => {
                for env in self.scopes.iter().rev() {
                    if let Some(val) = env.map.get(&sym) {
                        return Ok(val.clone());
                    }
                }

                Err(FuncError::UndeclaredSymbol(sym))
            },
            Value::List(mut tokens) => {
                if tokens.len() == 0 {
                  return Err(FuncError::AttemptToEvalEmptyList);
                }

                let val = tokens.remove(0);
                let func = try!(self.eval_token(val.clone()));
                self.sub_scope(); // each list has its own scope

                match func {
                    Value::HardFunc(hard_func) => {
                        let mut args = Vec::new();

                        for token in tokens {
                            args.push(try!(self.eval_token(token)));
                        }

                        match hard_func.args {
                            Args::Variant => (),
                            Args::Fixed(count) => {
                                if args.len() != count {
                                    invalid_args!(self, Args::Fixed(count), args);
                                }
                            },
                            Args::Multiple(possible_counts) => {
                                let mut arg_match = false;

                                // I wish I could label match statements and break out of them...
                                for count in &possible_counts {
                                    if args.len() == *count {
                                        arg_match = true;
                                        break;
                                    }
                                }

                                if !arg_match {
                                    invalid_args!(self, Args::Multiple(possible_counts), args);
                                }
                            },
                            Args::Atleast(count) => {
                                if args.len() < count {
                                    invalid_args!(self, Args::Atleast(count), args);
                                }
                            }
                        }

                        let result = (hard_func.func)(args, self);
                        self.exit_scope();

                        result
                    },
                    Value::Lambda { args, body } => {
                        if tokens.len() != args.len() {
                            invalid_args!(self, Args::Fixed(args.len()), tokens);
                        }

                        for sym in args.iter() {
                            let value = try!(self.eval_token(tokens.remove(0)));
                            self.cur_scope().set(sym, value);
                        }

                        let result = self.eval_token_vec(body);
                        self.exit_scope();

                        result
                    },
                    _ => return Err(FuncError::AttemptToCallNonFunction(val)),
                }
            },
            Value::Quote(val) => Ok(*val),
            _ => Ok(token),
        }
    }

    pub fn set_global<T: ToLisp>(&mut self, name: &str, value: T) {
        self.scopes[0].set(name, value);
    }

    pub fn sub_scope(&mut self) {
        self.scopes.push(Env::new());
    }

    pub fn exit_scope(&mut self) {
        self.scopes.pop();
    }

    pub fn cur_scope(&mut self) -> &mut Env {
        let index = self.scopes.len() - 1;
        &mut self.scopes[index]
    }

    pub fn parent_scope(&mut self) -> &mut Env {
        let index = cmp::max(self.scopes.len() as i32 - 2, 0) as usize;
        &mut self.scopes[index]
    }
}

impl fmt::Debug for Value {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match self {
            &Value::List(ref values) => parse::write_list(fmt, values, "(", " ", ")"),
            &Value::String(ref string) => write!(fmt, "{:?}", string),
            &Value::Symbol(ref string) => write!(fmt, "{}", string),
            &Value::Number(num) => write!(fmt, "{}", num),
            &Value::HardFunc(ref func) => write!(fmt, "HardFunc({:?})", func.args),
            &Value::Lambda { ref args, ref body }=> write!(fmt, "λ {:?} => {:?}", args, body),
            &Value::Nil => write!(fmt, "nil"),
            &Value::Bool(val) => write!(fmt, "{}", val),
            &Value::Quote(ref tok) => write!(fmt, "'{:?}", tok),
            &Value::Type(ref typ) => write!(fmt, "{:?}", typ),
            &Value::Foreign(ref val) => write!(fmt, "{:?}", val),
        }
    }
}
<|endoftext|>


<commit_before>use std::io::{fs, IoResult};
use std::io;
use unicode::str::UnicodeStrSlice;

use ansi_term::{Paint, Colour, Plain, Style, Red, Green, Yellow, Blue, Purple, Cyan, Fixed};

use column::{Column, Permissions, FileName, FileSize, User, Group, HardLinks, Inode, Blocks};
use format::{format_metric_bytes, format_IEC_bytes};
use unix::Unix;
use sort::SortPart;
use dir::Dir;
use filetype::HasType;

static Grey: Colour = Fixed(244);

// Instead of working with Rust's Paths, we have our own File object
// that holds the Path and various cached information. Each file is
// definitely going to have its filename used at least once, its stat
// information queried at least once, and its file extension extracted
// at least once, so we may as well carry around that information with
// the actual path.

pub struct File<'a> {
    pub name:  String,
    pub dir:   &'a Dir<'a>,
    pub ext:   Option<String>,
    pub path:  &'a Path,
    pub stat:  io::FileStat,
    pub parts: Vec<SortPart>,
}

impl<'a> File<'a> {
    pub fn from_path(path: &'a Path, parent: &'a Dir) -> IoResult<File<'a>> {
        let v = path.filename().unwrap();  // fails if / or . or ..
        let filename = String::from_utf8_lossy(v).to_string();

        // Use lstat here instead of file.stat(), as it doesn't follow
        // symbolic links. Otherwise, the stat() call will fail if it
        // encounters a link that's target is non-existent.

        fs::lstat(path).map(|stat| File {
            path:  path,
            dir:   parent,
            stat:  stat,
            name:  filename.clone(),
            ext:   File::ext(filename.clone()),
            parts: SortPart::split_into_parts(filename.clone()),
        })
    }

    fn ext(name: String) -> Option<String> {
        // The extension is the series of characters after a dot at
        // the end of a filename. This deliberately also counts
        // dotfiles - the ".git" folder has the extension "git".
        let re = regex!(r"\.([^.]+)$");
        re.captures(name.as_slice()).map(|caps| caps.at(1).to_string())
    }

    pub fn is_dotfile(&self) -> bool {
        self.name.as_slice().starts_with(".")
    }

    pub fn is_tmpfile(&self) -> bool {
        let name = self.name.as_slice();
        name.ends_with("~") || (name.starts_with("#") && name.ends_with("#"))
    }

    // Highlight the compiled versions of files. Some of them, like .o,
    // get special highlighting when they're alone because there's no
    // point in existing without their source. Others can be perfectly
    // content without their source files, such as how .js is valid
    // without a .coffee.

    pub fn get_source_files(&self) -> Vec<Path> {
        if self.ext.is_none() {
            return vec![];
        }

        let ext = self.ext.clone().unwrap();
        match ext.as_slice() {
            "class" => vec![self.path.with_extension("java")],  // Java
            "elc" => vec![self.path.with_extension("el")],  // Emacs Lisp
            "hi" => vec![self.path.with_extension("hs")],  // Haskell
            "o" => vec![self.path.with_extension("c"), self.path.with_extension("cpp")],  // C, C++
            "pyc" => vec![self.path.with_extension("py")],  // Python
            "js" => vec![self.path.with_extension("coffee"), self.path.with_extension("ts")],  // CoffeeScript, TypeScript
            "css" => vec![self.path.with_extension("sass"), self.path.with_extension("less")],  // SASS, Less

            "aux" => vec![self.path.with_extension("tex")],  // TeX: auxiliary file
            "bbl" => vec![self.path.with_extension("tex")],  // BibTeX bibliography file
            "blg" => vec![self.path.with_extension("tex")],  // BibTeX log file
            "lof" => vec![self.path.with_extension("tex")],  // list of figures
            "log" => vec![self.path.with_extension("tex")],  // TeX log file
            "lot" => vec![self.path.with_extension("tex")],  // list of tables
            "toc" => vec![self.path.with_extension("tex")],  // table of contents

            _ => vec![],
        }
    }

    pub fn display(&self, column: &Column, unix: &mut Unix) -> String {
        match *column {
            Permissions => self.permissions_string(),
            FileName => self.file_name(),
            FileSize(use_iec) => self.file_size(use_iec),

            // A file with multiple links is interesting, but
            // directories and suchlike can have multiple links all
            // the time.
            HardLinks => {
                let style = if self.stat.kind == io::TypeFile && self.stat.unstable.nlink > 1 { Red.on(Yellow) } else { Red.normal() };
                style.paint(self.stat.unstable.nlink.to_string().as_slice())
            },

            Inode => Purple.paint(self.stat.unstable.inode.to_string().as_slice()),
            Blocks => {
                if self.stat.kind == io::TypeFile || self.stat.kind == io::TypeSymlink {
                    Cyan.paint(self.stat.unstable.blocks.to_string().as_slice())
                }
                else {
                    Grey.paint("-")
                }
            },

            // Display the ID if the user/group doesn't exist, which
            // usually means it was deleted but its files weren't.
            User => {
                let uid = self.stat.unstable.uid as u32;
                unix.load_user(uid);
                let style = if unix.uid == uid { Yellow.bold() } else { Plain };
                let string = unix.get_user_name(uid).unwrap_or(uid.to_string());
                style.paint(string.as_slice())
            },
            Group => {
                let gid = self.stat.unstable.gid as u32;
                unix.load_group(gid);
                let name = unix.get_group_name(gid).unwrap_or(gid.to_string());
                let style = if unix.is_group_member(gid) { Yellow.normal() } else { Plain };
                style.paint(name.as_slice())
            },
        }
    }

    pub fn file_name(&self) -> String {
        let name = self.name.as_slice();
        let displayed_name = self.file_colour().paint(name);
        if self.stat.kind == io::TypeSymlink {
            match fs::readlink(self.path) {
                Ok(path) => {
                    let target_path = if path.is_absolute() { path } else { self.dir.path.join(path) };
                    format!("{} {}", displayed_name, self.target_file_name_and_arrow(target_path))
                }
                Err(_) => displayed_name,
            }
        }
        else {
            displayed_name
        }
    }

    pub fn file_name_width(&self) -> uint {
        self.name.as_slice().width(false)
    }

    fn target_file_name_and_arrow(&self, target_path: Path) -> String {
        let v = target_path.filename().unwrap();
        let filename = String::from_utf8_lossy(v).to_string();

        let link_target = fs::stat(&target_path).map(|stat| File {
            path:  &target_path,
            dir:   self.dir,
            stat:  stat,
            name:  filename.clone(),
            ext:   File::ext(filename.clone()),
            parts: vec![],  // not needed
        });

        // Statting a path usually fails because the file at the other
        // end doesn't exist. Show this by highlighting the target
        // file in red instead of displaying an error, because the
        // error would be shown out of context and it's almost always
        // that reason anyway.

        match link_target {
            Ok(file) => format!("{} {}", Grey.paint("=>"), file.file_colour().paint(filename.as_slice())),
            Err(_)   => format!("{} {}", Red.paint("=>"), Red.underline().paint(filename.as_slice())),
        }
    }

    fn file_size(&self, use_iec_prefixes: bool) -> String {
        // Don't report file sizes for directories. I've never looked
        // at one of those numbers and gained any information from it.
        if self.stat.kind == io::TypeDirectory {
            Grey.paint("-")
        } else {
            let (size, suffix) = if use_iec_prefixes {
                format_IEC_bytes(self.stat.size)
            } else {
                format_metric_bytes(self.stat.size)
            };

            return format!("{}{}", Green.bold().paint(size.as_slice()), Green.paint(suffix.as_slice()));
        }
    }

    fn type_char(&self) -> String {
        return match self.stat.kind {
            io::TypeFile         => ".".to_string(),
            io::TypeDirectory    => Blue.paint("d"),
            io::TypeNamedPipe    => Yellow.paint("|"),
            io::TypeBlockSpecial => Purple.paint("s"),
            io::TypeSymlink      => Cyan.paint("l"),
            io::TypeUnknown      => "?".to_string(),
        }
    }

    pub fn file_colour(&self) -> Style {
        self.get_type().style()
    }

    fn permissions_string(&self) -> String {
        let bits = self.stat.perm;
        return format!("{}{}{}{}{}{}{}{}{}{}",
            self.type_char(),

            // The first three are bold because they're the ones used
            // most often.
            File::permission_bit(bits, io::UserRead,     "r", Yellow.bold()),
            File::permission_bit(bits, io::UserWrite,    "w", Red.bold()),
            File::permission_bit(bits, io::UserExecute,  "x", Green.bold().underline()),
            File::permission_bit(bits, io::GroupRead,    "r", Yellow.normal()),
            File::permission_bit(bits, io::GroupWrite,   "w", Red.normal()),
            File::permission_bit(bits, io::GroupExecute, "x", Green.normal()),
            File::permission_bit(bits, io::OtherRead,    "r", Yellow.normal()),
            File::permission_bit(bits, io::OtherWrite,   "w", Red.normal()),
            File::permission_bit(bits, io::OtherExecute, "x", Green.normal()),
       );
    }

    fn permission_bit(bits: io::FilePermission, bit: io::FilePermission, character: &'static str, style: Style) -> String {
        if bits.contains(bit) {
            style.paint(character.as_slice())
        } else {
            Grey.paint("-".as_slice())
        }
    }
}
<commit_msg>Misc style changes<commit_after>use std::io::{fs, IoResult};
use std::io;
use unicode::str::UnicodeStrSlice;

use ansi_term::{Paint, Colour, Plain, Style, Red, Green, Yellow, Blue, Purple, Cyan, Fixed};

use column::{Column, Permissions, FileName, FileSize, User, Group, HardLinks, Inode, Blocks};
use format::{format_metric_bytes, format_IEC_bytes};
use unix::Unix;
use sort::SortPart;
use dir::Dir;
use filetype::HasType;

static Grey: Colour = Fixed(244);

// Instead of working with Rust's Paths, we have our own File object
// that holds the Path and various cached information. Each file is
// definitely going to have its filename used at least once, its stat
// information queried at least once, and its file extension extracted
// at least once, so we may as well carry around that information with
// the actual path.

pub struct File<'a> {
    pub name:  String,
    pub dir:   &'a Dir<'a>,
    pub ext:   Option<String>,
    pub path:  &'a Path,
    pub stat:  io::FileStat,
    pub parts: Vec<SortPart>,
}

impl<'a> File<'a> {
    pub fn from_path(path: &'a Path, parent: &'a Dir) -> IoResult<File<'a>> {
        let v = path.filename().unwrap();  // fails if / or . or ..
        let filename = String::from_utf8_lossy(v).to_string();

        // Use lstat here instead of file.stat(), as it doesn't follow
        // symbolic links. Otherwise, the stat() call will fail if it
        // encounters a link that's target is non-existent.

        fs::lstat(path).map(|stat| File {
            path:  path,
            dir:   parent,
            stat:  stat,
            name:  filename.clone(),
            ext:   File::ext(filename.clone()),
            parts: SortPart::split_into_parts(filename.clone()),
        })
    }

    fn ext(name: String) -> Option<String> {
        // The extension is the series of characters after a dot at
        // the end of a filename. This deliberately also counts
        // dotfiles - the ".git" folder has the extension "git".
        let re = regex!(r"\.([^.]+)$");
        re.captures(name.as_slice()).map(|caps| caps.at(1).to_string())
    }

    pub fn is_dotfile(&self) -> bool {
        self.name.as_slice().starts_with(".")
    }

    pub fn is_tmpfile(&self) -> bool {
        let name = self.name.as_slice();
        name.ends_with("~") || (name.starts_with("#") && name.ends_with("#"))
    }

    // Highlight the compiled versions of files. Some of them, like .o,
    // get special highlighting when they're alone because there's no
    // point in existing without their source. Others can be perfectly
    // content without their source files, such as how .js is valid
    // without a .coffee.

    pub fn get_source_files(&self) -> Vec<Path> {
        if self.ext.is_none() {
            return vec![];
        }

        let ext = self.ext.clone().unwrap();
        match ext.as_slice() {
            "class" => vec![self.path.with_extension("java")],  // Java
            "elc" => vec![self.path.with_extension("el")],  // Emacs Lisp
            "hi" => vec![self.path.with_extension("hs")],  // Haskell
            "o" => vec![self.path.with_extension("c"), self.path.with_extension("cpp")],  // C, C++
            "pyc" => vec![self.path.with_extension("py")],  // Python
            "js" => vec![self.path.with_extension("coffee"), self.path.with_extension("ts")],  // CoffeeScript, TypeScript
            "css" => vec![self.path.with_extension("sass"), self.path.with_extension("less")],  // SASS, Less

            "aux" => vec![self.path.with_extension("tex")],  // TeX: auxiliary file
            "bbl" => vec![self.path.with_extension("tex")],  // BibTeX bibliography file
            "blg" => vec![self.path.with_extension("tex")],  // BibTeX log file
            "lof" => vec![self.path.with_extension("tex")],  // TeX list of figures
            "log" => vec![self.path.with_extension("tex")],  // TeX log file
            "lot" => vec![self.path.with_extension("tex")],  // TeX list of tables
            "toc" => vec![self.path.with_extension("tex")],  // TeX table of contents

            _ => vec![],
        }
    }

    pub fn display(&self, column: &Column, unix: &mut Unix) -> String {
        match *column {
            Permissions => {
                self.permissions_string()
            },

            FileName => {
                self.file_name()
            },

            FileSize(use_iec) => {
                self.file_size(use_iec)
            },

            // A file with multiple links is interesting, but
            // directories and suchlike can have multiple links all
            // the time.
            HardLinks => {
                let style = if self.has_multiple_links() { Red.on(Yellow) } else { Red.normal() };
                style.paint(self.stat.unstable.nlink.to_string().as_slice())
            },

            Inode => {
                Purple.paint(self.stat.unstable.inode.to_string().as_slice())
            },

            Blocks => {
                if self.stat.kind == io::TypeFile || self.stat.kind == io::TypeSymlink {
                    Cyan.paint(self.stat.unstable.blocks.to_string().as_slice())
                }
                else {
                    Grey.paint("-")
                }
            },

            // Display the ID if the user/group doesn't exist, which
            // usually means it was deleted but its files weren't.
            User => {
                let uid = self.stat.unstable.uid as u32;
                unix.load_user(uid);
                let user_name = unix.get_user_name(uid).unwrap_or(uid.to_string());
                let style = if unix.uid == uid { Yellow.bold() } else { Plain };
                style.paint(user_name.as_slice())
            },

            Group => {
                let gid = self.stat.unstable.gid as u32;
                unix.load_group(gid);
                let group_name = unix.get_group_name(gid).unwrap_or(gid.to_string());
                let style = if unix.is_group_member(gid) { Yellow.normal() } else { Plain };
                style.paint(group_name.as_slice())
            },
        }
    }

    pub fn file_name(&self) -> String {
        let name = self.name.as_slice();
        let displayed_name = self.file_colour().paint(name);
        if self.stat.kind == io::TypeSymlink {
            match fs::readlink(self.path) {
                Ok(path) => {
                    let target_path = if path.is_absolute() { path } else { self.dir.path.join(path) };
                    format!("{} {}", displayed_name, self.target_file_name_and_arrow(target_path))
                }
                Err(_) => displayed_name,
            }
        }
        else {
            displayed_name
        }
    }

    pub fn file_name_width(&self) -> uint {
        self.name.as_slice().width(false)
    }

    fn target_file_name_and_arrow(&self, target_path: Path) -> String {
        let v = target_path.filename().unwrap();
        let filename = String::from_utf8_lossy(v).to_string();

        let link_target = fs::stat(&target_path).map(|stat| File {
            path:  &target_path,
            dir:   self.dir,
            stat:  stat,
            name:  filename.clone(),
            ext:   File::ext(filename.clone()),
            parts: vec![],  // not needed
        });

        // Statting a path usually fails because the file at the
        // other end doesn't exist. Show this by highlighting the
        // target file in red instead of displaying an error, because
        // the error would be shown out of context (before the
        // results, not right by the file) and it's almost always for
        // that reason anyway.

        match link_target {
            Ok(file) => format!("{} {}", Grey.paint("=>"), file.file_colour().paint(filename.as_slice())),
            Err(_)   => format!("{} {}", Red.paint("=>"), Red.underline().paint(filename.as_slice())),
        }
    }

    fn file_size(&self, use_iec_prefixes: bool) -> String {
        // Don't report file sizes for directories. I've never looked
        // at one of those numbers and gained any information from it.
        if self.stat.kind == io::TypeDirectory {
            Grey.paint("-")
        } else {
            let (size, suffix) = if use_iec_prefixes {
                format_IEC_bytes(self.stat.size)
            } else {
                format_metric_bytes(self.stat.size)
            };

            return format!("{}{}", Green.bold().paint(size.as_slice()), Green.paint(suffix.as_slice()));
        }
    }

    fn type_char(&self) -> String {
        return match self.stat.kind {
            io::TypeFile         => ".".to_string(),
            io::TypeDirectory    => Blue.paint("d"),
            io::TypeNamedPipe    => Yellow.paint("|"),
            io::TypeBlockSpecial => Purple.paint("s"),
            io::TypeSymlink      => Cyan.paint("l"),
            io::TypeUnknown      => "?".to_string(),
        }
    }

    pub fn file_colour(&self) -> Style {
        self.get_type().style()
    }

    fn has_multiple_links(&self) -> bool {
        self.stat.kind == io::TypeFile && self.stat.unstable.nlink > 1
    }

    fn permissions_string(&self) -> String {
        let bits = self.stat.perm;
        return format!("{}{}{}{}{}{}{}{}{}{}",
            self.type_char(),

            // The first three are bold because they're the ones used
            // most often.
            File::permission_bit(bits, io::UserRead,     "r", Yellow.bold()),
            File::permission_bit(bits, io::UserWrite,    "w", Red.bold()),
            File::permission_bit(bits, io::UserExecute,  "x", Green.bold().underline()),
            File::permission_bit(bits, io::GroupRead,    "r", Yellow.normal()),
            File::permission_bit(bits, io::GroupWrite,   "w", Red.normal()),
            File::permission_bit(bits, io::GroupExecute, "x", Green.normal()),
            File::permission_bit(bits, io::OtherRead,    "r", Yellow.normal()),
            File::permission_bit(bits, io::OtherWrite,   "w", Red.normal()),
            File::permission_bit(bits, io::OtherExecute, "x", Green.normal()),
       );
    }

    fn permission_bit(bits: io::FilePermission, bit: io::FilePermission, character: &'static str, style: Style) -> String {
        if bits.contains(bit) {
            style.paint(character.as_slice())
        } else {
            Grey.paint("-".as_slice())
        }
    }
}
<|endoftext|>


<commit_before>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

//! Extension trait for libimagstore::store::Store
//!
//! This module contains traits and code for extending the Store with functions that can be used to
//! create, get and delete events.

use chrono::NaiveDateTime as NDT;
use toml::Value;
use toml_query::insert::TomlValueInsertExt;

use libimagstore::store::Store;
use libimagstore::store::FileLockEntry;
use libimagentrydatetime::datepath::compiler::DatePathCompiler;

use result::Result;
use constants::*;
use error::TimeTrackErrorKind as TTEK;
use error::MapErrInto;
use iter::get::GetTimeTrackIter;

use tag::TimeTrackingTag as TTT;

pub trait TimeTrackStore<'a> {

    fn create_timetracking_now(&'a self, ts: &TTT)                     -> Result<FileLockEntry<'a>>;
    fn create_timetracking_at(&'a self, start: &NDT, ts: &TTT)         -> Result<FileLockEntry<'a>>;
    fn create_timetracking(&'a self, start: &NDT, end: &NDT, ts: &TTT) -> Result<FileLockEntry<'a>>;

    fn get_timetrackings<I>(&'a self) -> Result<GetTimeTrackIter<'a>>;
}

fn now() -> NDT {
    use chrono::offset::Local;
    Local::now().naive_local()
}

lazy_static! {
    static ref COMPILER: DatePathCompiler = {
        use libimagentrydatetime::datepath::accuracy::Accuracy;
        use libimagentrydatetime::datepath::format::Format;

        DatePathCompiler::new(Accuracy::Second, Format::ElementIsFolder)
    };
}

impl<'a> TimeTrackStore<'a> for Store {

    fn create_timetracking_now(&'a self, ts: &TTT) -> Result<FileLockEntry<'a>> {
        self.create_timetracking_at(&now(), ts)
    }

    fn create_timetracking_at(&'a self, start: &NDT, ts: &TTT) -> Result<FileLockEntry<'a>> {
        use std::path::PathBuf;

        COMPILER.compile(CRATE_NAME, start)
            .map_err_into(TTEK::StoreIdError)
            .map(|mut id| {
                id.local_push(PathBuf::from(ts.as_str()));
                id
            })
            .and_then(|id| self.create(id).map_err_into(TTEK::StoreWriteError))
            .and_then(|mut fle| {
                let v = Value::String(ts.as_str().to_owned());
                fle.get_header_mut()
                    .insert(DATE_TIME_TAG_HEADER_PATH, v)
                    .map_err_into(TTEK::HeaderWriteError)
                    .map(|_| fle)
            })
            .and_then(|mut fle| {
                let v = Value::String(start.format(DATE_TIME_FORMAT).to_string());
                fle.get_header_mut()
                    .insert(DATE_TIME_START_HEADER_PATH, v)
                    .map_err_into(TTEK::HeaderWriteError)
                    .map(|_| fle)
            })
    }

    fn create_timetracking(&'a self, start: &NDT, end: &NDT, ts: &TTT) -> Result<FileLockEntry<'a>> {
        self.create_timetracking_at(start, ts)
            .and_then(|mut fle| {
                let v = Value::String(end.format(DATE_TIME_FORMAT).to_string());
                fle.get_header_mut()
                    .insert(DATE_TIME_END_HEADER_PATH, v)
                    .map_err_into(TTEK::HeaderWriteError)
                    .map(|_| fle)
            })
    }

    fn get_timetrackings<I>(&'a self) -> Result<GetTimeTrackIter<'a>> {
        self.retrieve_for_module(CRATE_NAME)
            .map_err_into(TTEK::StoreReadError)
            .map(|iter| GetTimeTrackIter::new(iter, self))
    }

}

<commit_msg>Fix: Remove unused type parameter<commit_after>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

//! Extension trait for libimagstore::store::Store
//!
//! This module contains traits and code for extending the Store with functions that can be used to
//! create, get and delete events.

use chrono::NaiveDateTime as NDT;
use toml::Value;
use toml_query::insert::TomlValueInsertExt;

use libimagstore::store::Store;
use libimagstore::store::FileLockEntry;
use libimagentrydatetime::datepath::compiler::DatePathCompiler;

use result::Result;
use constants::*;
use error::TimeTrackErrorKind as TTEK;
use error::MapErrInto;
use iter::get::GetTimeTrackIter;

use tag::TimeTrackingTag as TTT;

pub trait TimeTrackStore<'a> {

    fn create_timetracking_now(&'a self, ts: &TTT)                     -> Result<FileLockEntry<'a>>;
    fn create_timetracking_at(&'a self, start: &NDT, ts: &TTT)         -> Result<FileLockEntry<'a>>;
    fn create_timetracking(&'a self, start: &NDT, end: &NDT, ts: &TTT) -> Result<FileLockEntry<'a>>;

    fn get_timetrackings(&'a self) -> Result<GetTimeTrackIter<'a>>;
}

fn now() -> NDT {
    use chrono::offset::Local;
    Local::now().naive_local()
}

lazy_static! {
    static ref COMPILER: DatePathCompiler = {
        use libimagentrydatetime::datepath::accuracy::Accuracy;
        use libimagentrydatetime::datepath::format::Format;

        DatePathCompiler::new(Accuracy::Second, Format::ElementIsFolder)
    };
}

impl<'a> TimeTrackStore<'a> for Store {

    fn create_timetracking_now(&'a self, ts: &TTT) -> Result<FileLockEntry<'a>> {
        self.create_timetracking_at(&now(), ts)
    }

    fn create_timetracking_at(&'a self, start: &NDT, ts: &TTT) -> Result<FileLockEntry<'a>> {
        use std::path::PathBuf;

        COMPILER.compile(CRATE_NAME, start)
            .map_err_into(TTEK::StoreIdError)
            .map(|mut id| {
                id.local_push(PathBuf::from(ts.as_str()));
                id
            })
            .and_then(|id| self.create(id).map_err_into(TTEK::StoreWriteError))
            .and_then(|mut fle| {
                let v = Value::String(ts.as_str().to_owned());
                fle.get_header_mut()
                    .insert(DATE_TIME_TAG_HEADER_PATH, v)
                    .map_err_into(TTEK::HeaderWriteError)
                    .map(|_| fle)
            })
            .and_then(|mut fle| {
                let v = Value::String(start.format(DATE_TIME_FORMAT).to_string());
                fle.get_header_mut()
                    .insert(DATE_TIME_START_HEADER_PATH, v)
                    .map_err_into(TTEK::HeaderWriteError)
                    .map(|_| fle)
            })
    }

    fn create_timetracking(&'a self, start: &NDT, end: &NDT, ts: &TTT) -> Result<FileLockEntry<'a>> {
        self.create_timetracking_at(start, ts)
            .and_then(|mut fle| {
                let v = Value::String(end.format(DATE_TIME_FORMAT).to_string());
                fle.get_header_mut()
                    .insert(DATE_TIME_END_HEADER_PATH, v)
                    .map_err_into(TTEK::HeaderWriteError)
                    .map(|_| fle)
            })
    }

    fn get_timetrackings(&'a self) -> Result<GetTimeTrackIter<'a>> {
        self.retrieve_for_module(CRATE_NAME)
            .map_err_into(TTEK::StoreReadError)
            .map(|iter| GetTimeTrackIter::new(iter, self))
    }

}

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Implementation of cookie creation and matching as specified by
//! http://tools.ietf.org/html/rfc6265

use cookie_rs;
use net_traits::CookieSource;
use pub_domains::PUB_DOMAINS;
use std::borrow::ToOwned;
use std::net::{Ipv4Addr, Ipv6Addr};
use time::{Tm, now, at, Duration};
use url::Url;

/// A stored cookie that wraps the definition in cookie-rs. This is used to implement
/// various behaviours defined in the spec that rely on an associated request URL,
/// which cookie-rs and hyper's header parsing do not support.
#[derive(Clone, Debug)]
pub struct Cookie {
    pub cookie: cookie_rs::Cookie,
    pub host_only: bool,
    pub persistent: bool,
    pub creation_time: Tm,
    pub last_access: Tm,
    pub expiry_time: Option<Tm>,
}

impl Cookie {
    /// http://tools.ietf.org/html/rfc6265#section-5.3
    pub fn new_wrapped(mut cookie: cookie_rs::Cookie, request: &Url, source: CookieSource)
                       -> Option<Cookie> {
        // Step 3
        let (persistent, expiry_time) = match (&cookie.max_age, &cookie.expires) {
            (&Some(max_age), _) => {
                (true, Some(at(now().to_timespec() + Duration::seconds(max_age as i64))))
            }
            (_, &Some(expires)) => (true, Some(expires)),
            _ => (false, None)
        };

        let url_host = request.host().map_or("".to_owned(), |host| host.serialize());

        // Step 4
        let mut domain = cookie.domain.clone().unwrap_or("".to_owned());

        // Step 5
        match PUB_DOMAINS.iter().find(|&x| domain == *x) {
            Some(val) if *val == url_host => domain = "".to_owned(),
            Some(_) => return None,
            None => {}
        }

        // Step 6
        let host_only = if !domain.is_empty() {
            if !Cookie::domain_match(&url_host, &domain) {
                return None;
            } else {
                cookie.domain = Some(domain);
                false
            }
        } else {
            cookie.domain = Some(url_host);
            true
        };

        // Step 7
        let mut path = cookie.path.unwrap_or("".to_owned());
        if path.is_empty() || path.as_bytes()[0] != b'/' {
            let url_path = request.serialize_path();
            let url_path = url_path.as_ref().map(|path| &**path);
            path = Cookie::default_path(url_path.unwrap_or("")).to_owned();
        }
        cookie.path = Some(path);

        // Step 10
        if cookie.httponly && source != CookieSource::HTTP {
            return None;
        }

        Some(Cookie {
            cookie: cookie,
            host_only: host_only,
            persistent: persistent,
            creation_time: now(),
            last_access: now(),
            expiry_time: expiry_time,
        })
    }

    pub fn touch(&mut self) {
        self.last_access = now();
    }

    // http://tools.ietf.org/html/rfc6265#section-5.1.4
    pub fn default_path(request_path: &str) -> &str {
        // Step 2
        if request_path.is_empty() || !request_path.starts_with("/") {
            return "/";
        }

        // Step 3
        let rightmost_slash_idx = request_path.rfind("/").unwrap();
        if rightmost_slash_idx == 0 {
            // There's only one slash; it's the first character
            return "/";
        }

        // Step 4
        &request_path[..rightmost_slash_idx]
    }

    // http://tools.ietf.org/html/rfc6265#section-5.1.4
    pub fn path_match(request_path: &str, cookie_path: &str) -> bool {
        // A request-path path-matches a given cookie-path if at least one of
        // the following conditions holds:

        // The cookie-path and the request-path are identical.
        request_path == cookie_path ||

        (request_path.starts_with(cookie_path) && (
            // The cookie-path is a prefix of the request-path, and the last
            // character of the cookie-path is %x2F ("/").
            cookie_path.ends_with("/") ||
            // The cookie-path is a prefix of the request-path, and the first
            // character of the request-path that is not included in the cookie-
            // path is a %x2F ("/") character.
            request_path[cookie_path.len()..].starts_with("/")
        ))
    }

    // http://tools.ietf.org/html/rfc6265#section-5.1.3
    pub fn domain_match(string: &str, domain_string: &str) -> bool {
        if string == domain_string {
            return true;
        }
        if string.ends_with(domain_string)
            && string.as_bytes()[string.len()-domain_string.len()-1] == b'.'
            && string.parse::<Ipv4Addr>().is_err()
            && string.parse::<Ipv6Addr>().is_err() {
            return true;
        }
        false
    }

    // http://tools.ietf.org/html/rfc6265#section-5.4 step 1
    pub fn appropriate_for_url(&self, url: &Url, source: CookieSource) -> bool {
        let domain = url.host().map(|host| host.serialize());
        if self.host_only {
            if self.cookie.domain != domain {
                return false;
            }
        } else {
            if let (Some(ref domain), &Some(ref cookie_domain)) = (domain, &self.cookie.domain) {
                if !Cookie::domain_match(domain, cookie_domain) {
                    return false;
                }
            }
        }

        if let (Some(ref path), &Some(ref cookie_path)) = (url.serialize_path(), &self.cookie.path) {
            if !Cookie::path_match(path, cookie_path) {
                return false;
            }
        }

        if self.cookie.secure && url.scheme != "https" {
            return false;
        }
        if self.cookie.httponly && source == CookieSource::NonHTTP {
            return false;
        }

        true
    }
}
<commit_msg>More idiomatic char retrieval from `String`.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Implementation of cookie creation and matching as specified by
//! http://tools.ietf.org/html/rfc6265

use cookie_rs;
use net_traits::CookieSource;
use pub_domains::PUB_DOMAINS;
use std::borrow::ToOwned;
use std::net::{Ipv4Addr, Ipv6Addr};
use time::{Tm, now, at, Duration};
use url::Url;

/// A stored cookie that wraps the definition in cookie-rs. This is used to implement
/// various behaviours defined in the spec that rely on an associated request URL,
/// which cookie-rs and hyper's header parsing do not support.
#[derive(Clone, Debug)]
pub struct Cookie {
    pub cookie: cookie_rs::Cookie,
    pub host_only: bool,
    pub persistent: bool,
    pub creation_time: Tm,
    pub last_access: Tm,
    pub expiry_time: Option<Tm>,
}

impl Cookie {
    /// http://tools.ietf.org/html/rfc6265#section-5.3
    pub fn new_wrapped(mut cookie: cookie_rs::Cookie, request: &Url, source: CookieSource)
                       -> Option<Cookie> {
        // Step 3
        let (persistent, expiry_time) = match (&cookie.max_age, &cookie.expires) {
            (&Some(max_age), _) => {
                (true, Some(at(now().to_timespec() + Duration::seconds(max_age as i64))))
            }
            (_, &Some(expires)) => (true, Some(expires)),
            _ => (false, None)
        };

        let url_host = request.host().map_or("".to_owned(), |host| host.serialize());

        // Step 4
        let mut domain = cookie.domain.clone().unwrap_or("".to_owned());

        // Step 5
        match PUB_DOMAINS.iter().find(|&x| domain == *x) {
            Some(val) if *val == url_host => domain = "".to_owned(),
            Some(_) => return None,
            None => {}
        }

        // Step 6
        let host_only = if !domain.is_empty() {
            if !Cookie::domain_match(&url_host, &domain) {
                return None;
            } else {
                cookie.domain = Some(domain);
                false
            }
        } else {
            cookie.domain = Some(url_host);
            true
        };

        // Step 7
        let mut path = cookie.path.unwrap_or("".to_owned());
        if path.chars().next() != Some('/') {
            let url_path = request.serialize_path();
            let url_path = url_path.as_ref().map(|path| &**path);
            path = Cookie::default_path(url_path.unwrap_or("")).to_owned();
        }
        cookie.path = Some(path);

        // Step 10
        if cookie.httponly && source != CookieSource::HTTP {
            return None;
        }

        Some(Cookie {
            cookie: cookie,
            host_only: host_only,
            persistent: persistent,
            creation_time: now(),
            last_access: now(),
            expiry_time: expiry_time,
        })
    }

    pub fn touch(&mut self) {
        self.last_access = now();
    }

    // http://tools.ietf.org/html/rfc6265#section-5.1.4
    pub fn default_path(request_path: &str) -> &str {
        // Step 2
        if request_path.chars().next() != Some('/') {
            return "/";
        }

        // Step 3
        let rightmost_slash_idx = request_path.rfind("/").unwrap();
        if rightmost_slash_idx == 0 {
            // There's only one slash; it's the first character
            return "/";
        }

        // Step 4
        &request_path[..rightmost_slash_idx]
    }

    // http://tools.ietf.org/html/rfc6265#section-5.1.4
    pub fn path_match(request_path: &str, cookie_path: &str) -> bool {
        // A request-path path-matches a given cookie-path if at least one of
        // the following conditions holds:

        // The cookie-path and the request-path are identical.
        request_path == cookie_path ||

        (request_path.starts_with(cookie_path) && (
            // The cookie-path is a prefix of the request-path, and the last
            // character of the cookie-path is %x2F ("/").
            cookie_path.ends_with("/") ||
            // The cookie-path is a prefix of the request-path, and the first
            // character of the request-path that is not included in the cookie-
            // path is a %x2F ("/") character.
            request_path[cookie_path.len()..].starts_with("/")
        ))
    }

    // http://tools.ietf.org/html/rfc6265#section-5.1.3
    pub fn domain_match(string: &str, domain_string: &str) -> bool {
        if string == domain_string {
            return true;
        }
        if string.ends_with(domain_string)
            && string.as_bytes()[string.len()-domain_string.len()-1] == b'.'
            && string.parse::<Ipv4Addr>().is_err()
            && string.parse::<Ipv6Addr>().is_err() {
            return true;
        }
        false
    }

    // http://tools.ietf.org/html/rfc6265#section-5.4 step 1
    pub fn appropriate_for_url(&self, url: &Url, source: CookieSource) -> bool {
        let domain = url.host().map(|host| host.serialize());
        if self.host_only {
            if self.cookie.domain != domain {
                return false;
            }
        } else {
            if let (Some(ref domain), &Some(ref cookie_domain)) = (domain, &self.cookie.domain) {
                if !Cookie::domain_match(domain, cookie_domain) {
                    return false;
                }
            }
        }

        if let (Some(ref path), &Some(ref cookie_path)) = (url.serialize_path(), &self.cookie.path) {
            if !Cookie::path_match(path, cookie_path) {
                return false;
            }
        }

        if self.cookie.secure && url.scheme != "https" {
            return false;
        }
        if self.cookie.httponly && source == CookieSource::NonHTTP {
            return false;
        }

        true
    }
}
<|endoftext|>


<commit_before>use super::{filters, settings};
use serde_json::json;

pub fn build_query(
    q: &str,
    _filters: filters::Filters,
    _langs: &[&str],
    settings: &settings::QuerySettings,
) -> serde_json::Value {
    json!({
        "query": {
            "bool": {
                "must": [ build_string_query(q, &settings.string_query) ],
                "should": build_importance_query(q, settings)
            },
        }
    })
}

fn build_string_query(q: &str, settings: &settings::StringQuery) -> serde_json::Value {
    json!({
        "bool": {
            "boost": settings.global,
            "should": [
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["name"],
                        "boost": settings.boosts.name
                    }
                },
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["label"],
                        "boost": settings.boosts.label
                    }
                },
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["label.prefix"],
                        "boost": settings.boosts.label_prefix
                    }
                }
            ]
        }
    })
}

fn build_importance_query(_q: &str, settings: &settings::QuerySettings) -> serde_json::Value {
    // TODO: in production, admins are boosted by their weight only in prefix mode.
    json!([build_admin_weight_query(&settings.importance_query)])
}

fn build_admin_weight_query(settings: &settings::ImportanceQueryBoosts) -> serde_json::Value {
    json!({
        "function_score": {
            "query": { "term": { "type": "admin" } },
            "boost_mode": "replace",
            "functions": [
                {
                    "field_value_factor": {
                        "field": "weight",
                        "factor": 1e6,
                        "modifier": "log1p",
                        "missing": 0
                    }
                },
                {
                    // TODO: in production, this weight depends of the focus radius
                    "weight": settings.weights.max_radius.admin
                }
            ]
        }
    })
}

// fn build_coverage_condition(datasets: &[&str]) -> serde_json::Value {
//     json!({
//         "bool": {
//             "should": [
//                 { "bool": { "must_not": { "exists": { "field": "coverages" } } } },
//                 { "terms": { "coverages": datasets } }
//             ]
//         }
//     })
// }
//
// #[derive(Debug)]
// enum DecayFn {
//     Gauss,
//     Exp,
//     Linear,
// }
//
// impl std::fmt::Display for DecayFn {
//     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
//         write!(f, "{:?}", self)
//     }
// }
//
// #[derive(Debug)]
// struct DecayFnParam {
//     pub func: DecayFn,
//     pub scale: f32,
//     pub offset: f32,
//     pub decay: f32,
// }

// // The decay function takes 4 parameters:
// // - origin: Here the origin is a geo-point, so we can express it as an object:
// //    { "location": { "lat": ..., "lon": ... } }
// // - scale
// // - offset
// // - decay
// // FIXME Probably use something like Into<Coord>
// fn build_proximity_with_boost(coord: Coord, decay_fn_param: DecayFnParam) -> String {
//     format!(
//         r#"{{
//             "function_score": {{
//                 "{func}": {{
//                     "coord": {{
//                         "origin": {{
//                             "location": {{
//                                 "lat": {lat},
//                                 "lon": {lon}
//                             }}
//                         }},
//                         "scale": "{scale}km",
//                         "offset": "{offset}km",
//                         "decay": {decay}
//                     }}
//                 }}
//             }}
//         }}"#,
//         lat = coord.lat,
//         lon = coord.lon,
//         func = decay_fn_param.func,
//         scale = decay_fn_param.scale,
//         offset = decay_fn_param.offset,
//         decay = decay_fn_param.decay
//     )
// }

pub fn build_reverse_query(distance: &str, lat: f64, lon: f64) -> serde_json::Value {
    json!({
    "query": {
        "bool": {
            "must": {
                "match_all": {}
            },
            "filter": {
                "geo_distance": {
                    "distance": distance,
                    "coord": {
                        "lat": lat,
                        "lon": lon
                    }
                }
            }
        }
    },
    "sort": [{
         "_geo_distance": {
             "coord": {
                 "lat": lat,
                 "lon": lon
             },
             "order": "asc",
             "unit": "m",
             "mode": "min",
             "distance_type": "arc",
             "ignore_unmapped": true
         }
     }]
    })
}
<commit_msg>WIP shapes<commit_after>use geojson::Geometry;
use serde_json::json;

use super::{filters, settings};

pub fn build_query(
    q: &str,
    filters: filters::Filters,
    _langs: &[&str],
    settings: &settings::QuerySettings,
) -> serde_json::Value {
    let filters::Filters {
        coord: _,
        shape,
        datasets: _,
        zone_types: _,
        poi_types: _,
    } = filters;

    let geoshape_filter = shape.map(|(geometry, scope)| build_shape_query(geometry, scope));

    json!({
        "query": {
            "bool": {
                "must": [ build_string_query(q, &settings.string_query) ],
                "should": build_importance_query(q, settings),
                "filter": [ geoshape_filter ]
            }
        }
    })
}

fn build_string_query(q: &str, settings: &settings::StringQuery) -> serde_json::Value {
    json!({
        "bool": {
            "boost": settings.global,
            "should": [
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["name"],
                        "boost": settings.boosts.name
                    }
                },
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["label"],
                        "boost": settings.boosts.label
                    }
                },
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["label.prefix"],
                        "boost": settings.boosts.label_prefix
                    }
                }
            ]
        }
    })
}

fn build_importance_query(_q: &str, settings: &settings::QuerySettings) -> serde_json::Value {
    // TODO: in production, admins are boosted by their weight only in prefix mode.
    json!([build_admin_weight_query(&settings.importance_query)])
}

fn build_admin_weight_query(settings: &settings::ImportanceQueryBoosts) -> serde_json::Value {
    json!({
        "function_score": {
            "query": { "term": { "type": "admin" } },
            "boost_mode": "replace",
            "functions": [
                {
                    "field_value_factor": {
                        "field": "weight",
                        "factor": 1e6,
                        "modifier": "log1p",
                        "missing": 0
                    }
                },
                {
                    // TODO: in production, this weight depends of the focus radius
                    "weight": settings.weights.max_radius.admin
                }
            ]
        }
    })
}

// fn build_coverage_condition(datasets: &[&str]) -> serde_json::Value {
//     json!({
//         "bool": {
//             "should": [
//                 { "bool": { "must_not": { "exists": { "field": "coverages" } } } },
//                 { "terms": { "coverages": datasets } }
//             ]
//         }
//     })
// }
//
// #[derive(Debug)]
// enum DecayFn {
//     Gauss,
//     Exp,
//     Linear,
// }
//
// impl std::fmt::Display for DecayFn {
//     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
//         write!(f, "{:?}", self)
//     }kkkkkkkkkkkkkkk
// }
//
// #[derive(Debug)]
// struct DecayFnParam {
//     pub func: DecayFn,
//     pub scale: f32,
//     pub offset: f32,
//     pub decay: f32,
// }

// // The decay function takes 4 parameters:
// // - origin: Here the origin is a geo-point, so we can express it as an object:
// //    { "location": { "lat": ..., "lon": ... } }
// // - scale
// // - offset
// // - decay
// // FIXME Probably use something like Into<Coord>
// fn build_proximity_with_boost(coord: Coord, decay_fn_param: DecayFnParam) -> String {
//     format!(
//         r#"{{
//             "function_score": {{
//                 "{func}": {{
//                     "coord": {{
//                         "origin": {{
//                             "location": {{
//                                 "lat": {lat},
//                                 "lon": {lon}
//                             }}
//                         }},
//                         "scale": "{scale}km",
//                         "offset": "{offset}km",
//                         "decay": {decay}
//                     }}
//                 }}
//             }}
//         }}"#,
//         lat = coord.lat,
//         lon = coord.lon,
//         func = decay_fn_param.func,
//         scale = decay_fn_param.scale,
//         offset = decay_fn_param.offset,
//         decay = decay_fn_param.decay
//     )
// }

pub fn build_reverse_query(distance: &str, lat: f64, lon: f64) -> serde_json::Value {
    json!({
    "query": {
        "bool": {
            "must": {
                "match_all": {}
            },
            "filter": {
                "geo_distance": {
                    "distance": distance,
                    "coord": {
                        "lat": lat,
                        "lon": lon
                    }
                }
            }
        }
    },
    "sort": [{
         "_geo_distance": {
             "coord": {
                 "lat": lat,
                 "lon": lon
             },
             "order": "asc",
             "unit": "m",
             "mode": "min",
             "distance_type": "arc",
             "ignore_unmapped": true
         }
     }]
    })
}

// If there is a shape, all the places listed in shape_scope are restricted to the shape.
// and the places that are not listed are not restricted.
// So if shape_scope = {A, B}, we should end up with something like
// should [
//   must {               => filwer_w_shape
//     term: _type in {A, B}
//     filter: geoshape
//   },
//   must_not {            => filter_wo_shape
//      term: _type in {A, B}
//   }
// ]
//
pub fn build_shape_query(shape: Geometry, scope: Vec<String>) -> serde_json::Value {
    json!({
        "query": {
            "bool": {
                "should": [
                {
                    "bool": {
                        "must": {
                            "terms": {
                                "_type": scope
                            }
                        },
                        "filter": {
                            "geo_shape": {
                                "location": {
                                    "shape": shape,
                                    "relation": "intersects"
                                }
                            }
                        }
                    }
                },
                {
                    "bool": {
                        "must_not": {
                            "terms": {
                                "_type": scope
                            }
                        }
                    }
                }
                ]
            }
        }
    })
}

#[cfg(test)]
mod tests {
    use geojson::GeoJson;

    use super::*;

    const GEOJSON_STR: &str = "{\"coordinates\":[7.428959,1.513394],\"type\":\"Point\"}";

    #[test]
    fn should_correctly_build_shape_query() {
        let geometry = match GEOJSON_STR.parse::<GeoJson>() {
            Ok(GeoJson::Geometry(g)) => g,
            _ => return,
        };

        let json = build_shape_query(geometry, vec![String::from("foo"), String::from("bar")]);

        println!("{}", serde_json::to_string_pretty(&json).unwrap());
    }

    #[test]
    fn should_correctly_build_query() {
        let geometry = match GEOJSON_STR.parse::<GeoJson>() {
            Ok(GeoJson::Geometry(g)) => g,
            _ => return,
        };

        let json = build_query(
            "chatelet",
            filters::Filters {
                shape: Some((geometry, vec![String::from("foo"), String::from("bar")])),
                ..Default::default()
            },
            &[],
            &settings::QuerySettings::default(),
        );

        println!("{}", serde_json::to_string_pretty(&json).unwrap());
    }
}
<|endoftext|>


<commit_before>use std::sync::atomic;
use std::{mem, ptr};
use {Atomic, Guard};

pub struct Treiber<T> {
    head: Atomic<Node<T>>,
}

struct Node<T> {
    data: T,
    next: *const Node<T>,
}

impl<T> Treiber<T> {
    pub fn new() -> Treiber<T> {
        Treiber {
            head: Atomic::default(),
        }
    }

    pub fn pop(&self, item: T) -> Option<Guard<T>> {
        // TODO: Use `catch {}` here when it lands.
        let mut head = self.head.load(atomic::Ordering::Acquire);
        while let Some(node) = head {
            // TODO: This should be something that ignores the guard creation when the CAS
            //       fails, because it's expensive to do and not used anyway. It should be easy
            //       enough to implement, but I am struggling to come up with a good name for
            //       the method.
            match unsafe {
                self.head.compare_and_swap_raw(
                    &*node,
                    node.next as *mut Node<T>,
                    atomic::Ordering::Release
                )
            } {
                Ok(_) => return Some(node.map(|x| &x.data)),
                Err(new_head) => head = new_head,
            }
        }

        None
    }

    pub fn push(&self, item: T)
    where T: 'static {
        // Load the current head.
        let mut current = self.head.load(atomic::Ordering::Acquire);
        let mut current_ptr: Option<*const Node<T>>;

        // TODO: Use `catch {}` here when it lands.
        // Construct a node, which will be the new head.
        let mut node = Box::new(Node {
            data: item,
            // Placeholder; we will replace it with an actual value in the loop.
            next: ptr::null(),
        });

        loop {
            // Derive the nullable current pointer from the current head.
            current_ptr = current.as_ref().map(Guard::as_raw);
            // Construct the next-pointer of the new node from the current head.
            node.next = current_ptr.unwrap_or(ptr::null());

            // TODO: This should be something that ignores the guard creation when the CAS
            //       succeeds, because it's expensive to do and not used anyway. It should be easy
            //       enough to implement, but I am struggling to come up with a good name for the
            //       method.

            // CAS from the read pointer to the new head.
            match self.head.compare_and_swap(current_ptr, Some(node), atomic::Ordering::Release) {
                // If it succeeds, the item has been pushed.
                Ok(_) => break,
                // If it fails, we will retry the CAS with updated values.
                Err((new_head, Some(node2))) => {
                    // Update the current head.
                    current = new_head;
                    // Put the box we gave back to the variable where it belongs.
                    node = node2;
                },
                // This should never be reached as we gave an argument which was unconditionally
                // `Some`.
                _ => unreachable!(),
            }
        }
    }
}
<commit_msg>Add tests and documentation to `conc::sync::Treiber`.<commit_after>//! Treiber stacks.

use std::sync::atomic;
use std::{mem, ptr};
use {Atomic, Guard};

/// A Treiber stack.
pub struct Treiber<T> {
    head: Atomic<Node<T>>,
}

struct Node<T> {
    data: T,
    next: *const Node<T>,
}

impl<T> Treiber<T> {
    /// Create a new, empty Treiber stack.
    pub fn new() -> Treiber<T> {
        Treiber {
            head: Atomic::default(),
        }
    }

    /// Pop an item from the stack.
    pub fn pop(&self) -> Option<Guard<T>> {
        // TODO: Use `catch {}` here when it lands.
        // Read the current head.
        let mut current = self.head.load(atomic::Ordering::Acquire);

        // Unless the current head snapshot is `None`, try to replace it with the tail.
        while let Some(node) = current {
            // Attempt to replace the head with the tail of the head.
            match unsafe {
                self.current.compare_and_swap_raw(
                    &*node,
                    node.next as *mut Node<T>,
                    atomic::Ordering::Release,
                )
            } {
                // It succeeded; return the item.
                Ok(_) => return Some(node.map(|x| &x.data)),
                // It failed, update the current head and continue.
                Err(new) => current = new,
            }
        }

        // As the head was empty, there is nothing to pop.
        None
    }

    /// Push an item to the stack.
    pub fn push(&self, item: T)
    where T: 'static {
        // Load the current head.
        let mut current = self.head.load(atomic::Ordering::Acquire);
        let mut current_ptr: Option<*const Node<T>>;

        // TODO: Use `catch {}` here when it lands.
        // Construct a node, which will be the new head.
        let mut node = Box::new(Node {
            data: item,
            // Placeholder; we will replace it with an actual value in the loop.
            next: ptr::null(),
        });

        loop {
            // Derive the nullable current pointer from the current head.
            current_ptr = current.as_ref().map(Guard::as_raw);
            // Construct the next-pointer of the new node from the current head.
            node.next = current_ptr.unwrap_or(ptr::null());

            // TODO: This should be something that ignores the guard creation when the CAS
            //       succeeds, because it's expensive to do and not used anyway. It should be easy
            //       enough to implement, but I am struggling to come up with a good name for the
            //       method.

            // CAS from the read pointer to the new head.
            match self.head.compare_and_swap(current_ptr, Some(node), atomic::Ordering::Release) {
                // If it succeeds, the item has been pushed.
                Ok(_) => break,
                // If it fails, we will retry the CAS with updated values.
                Err((new_head, Some(node2))) => {
                    // Update the current head.
                    current = new_head;
                    // Put the box we gave back to the variable where it belongs.
                    node = node2;
                },
                // This should never be reached as we gave an argument which was unconditionally
                // `Some`.
                _ => unreachable!(),
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread;
    use std::sync::Arc;

    #[test]
    fn single_thread() {
        let stack = Treiber::new();

        for i in 0..10000 {
            stack.push(i);
        }

        for i in (0..10000).rev() {
            assert_eq!(*stack.pop().unwrap(), i);
        }

        assert!(stack.pop().is_none());
        assert!(stack.pop().is_none());
        assert!(stack.pop().is_none());
        assert!(stack.pop().is_none());
    }

    #[test]
    fn push_pop() {
        let stack = Arc::new(Treiber::new());
        let mut j = Vec::new();
        for _ in 0..16 {
            let s = stack.clone();
            j.push(thread::spawn(move || {
                for _ in 0..10_000_000 {
                    s.push(23);
                    assert_eq!(*s.pop().unwrap(), 23);
                }
            }));
        }

        for i in j {
            i.join().unwrap();
        }
    }
}
<|endoftext|>


<commit_before>use std::fs::File;

use crate::common::util::*;

#[test]
#[cfg(not(windows))]
fn test_dev_null() {
    new_ucmd!()
        .set_stdin(File::open("/dev/null").unwrap())
        .fails()
        .code_is(1)
        .stdout_is("not a tty\n");
}

#[test]
#[cfg(not(windows))]
fn test_dev_null_silent() {
    new_ucmd!()
        .args(&["-s"])
        .set_stdin(File::open("/dev/null").unwrap())
        .fails()
        .code_is(1)
        .stdout_is("");
}

#[test]
fn test_close_stdin() {
    let mut child = new_ucmd!().run_no_wait();
    drop(child.stdin.take());
    let output = child.wait_with_output().unwrap();
    assert_eq!(output.status.code(), Some(1));
    assert_eq!(std::str::from_utf8(&output.stdout), Ok("not a tty\n"));
}

#[test]
fn test_close_stdin_silent() {
    let mut child = new_ucmd!().arg("-s").run_no_wait();
    drop(child.stdin.take());
    let output = child.wait_with_output().unwrap();
    assert_eq!(output.status.code(), Some(1));
    assert!(output.stdout.is_empty());
}

#[test]
fn test_close_stdin_silent_long() {
    let mut child = new_ucmd!().arg("--silent").run_no_wait();
    drop(child.stdin.take());
    let output = child.wait_with_output().unwrap();
    assert_eq!(output.status.code(), Some(1));
    assert!(output.stdout.is_empty());
}

#[test]
fn test_close_stdin_silent_alias() {
    let mut child = new_ucmd!().arg("--quiet").run_no_wait();
    drop(child.stdin.take());
    let output = child.wait_with_output().unwrap();
    assert_eq!(output.status.code(), Some(1));
    assert!(output.stdout.is_empty());
}

#[test]
fn test_wrong_argument() {
    new_ucmd!().args(&["a"]).fails().code_is(2);
}

#[test]
// FixME: freebsd panic
#[cfg(not(any(windows, target_os = "freebsd")))]
fn test_stdout_fail() {
    let mut child = new_ucmd!().run_no_wait();
    drop(child.stdout.take());
    let status = child.wait().unwrap();
    assert_eq!(status.code(), Some(3));
}
<commit_msg>tty: Make test_stdout_fail() less flaky<commit_after>use std::fs::File;

use crate::common::util::*;

#[test]
#[cfg(not(windows))]
fn test_dev_null() {
    new_ucmd!()
        .set_stdin(File::open("/dev/null").unwrap())
        .fails()
        .code_is(1)
        .stdout_is("not a tty\n");
}

#[test]
#[cfg(not(windows))]
fn test_dev_null_silent() {
    new_ucmd!()
        .args(&["-s"])
        .set_stdin(File::open("/dev/null").unwrap())
        .fails()
        .code_is(1)
        .stdout_is("");
}

#[test]
fn test_close_stdin() {
    let mut child = new_ucmd!().run_no_wait();
    drop(child.stdin.take());
    let output = child.wait_with_output().unwrap();
    assert_eq!(output.status.code(), Some(1));
    assert_eq!(std::str::from_utf8(&output.stdout), Ok("not a tty\n"));
}

#[test]
fn test_close_stdin_silent() {
    let mut child = new_ucmd!().arg("-s").run_no_wait();
    drop(child.stdin.take());
    let output = child.wait_with_output().unwrap();
    assert_eq!(output.status.code(), Some(1));
    assert!(output.stdout.is_empty());
}

#[test]
fn test_close_stdin_silent_long() {
    let mut child = new_ucmd!().arg("--silent").run_no_wait();
    drop(child.stdin.take());
    let output = child.wait_with_output().unwrap();
    assert_eq!(output.status.code(), Some(1));
    assert!(output.stdout.is_empty());
}

#[test]
fn test_close_stdin_silent_alias() {
    let mut child = new_ucmd!().arg("--quiet").run_no_wait();
    drop(child.stdin.take());
    let output = child.wait_with_output().unwrap();
    assert_eq!(output.status.code(), Some(1));
    assert!(output.stdout.is_empty());
}

#[test]
fn test_wrong_argument() {
    new_ucmd!().args(&["a"]).fails().code_is(2);
}

#[test]
// FixME: freebsd panic
#[cfg(all(unix, not(target_os = "freebsd")))]
fn test_stdout_fail() {
    use std::process::{Command, Stdio};
    let ts = TestScenario::new(util_name!());
    // Sleep inside a shell to ensure the process doesn't finish before we've
    // closed its stdout
    let mut proc = Command::new("sh")
        .arg("-c")
        .arg(format!(
            "sleep 0.2; exec {} {}",
            ts.bin_path.to_str().unwrap(),
            ts.util_name
        ))
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();
    drop(proc.stdout.take());
    let status = proc.wait().unwrap();
    assert_eq!(status.code(), Some(3));
}
<|endoftext|>