<commit_before>use std::{collections::HashMap, error::Error};

use config::{builder::AsyncState, AsyncSource, ConfigBuilder, ConfigError, FileFormat};

use async_trait::async_trait;
use futures::{select, FutureExt};
use warp::Filter;

// Example below presents sample configuration server and client.
//
// Server serves simple configuration on HTTP endpoint.
// Client consumes it using custom HTTP AsyncSource built on top of reqwest.

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    select! {
        r = run_server().fuse() => r,
        r = run_client().fuse() => r
    }
}

async fn run_server() -> Result<(), Box<dyn Error>> {
    let service = warp::path("configuration").map(|| r#"{ "value" : 123 }"#);

    println!("Running server on localhost:5001");

    warp::serve(service).bind(([127, 0, 0, 1], 5001)).await;

    Ok(())
}

async fn run_client() -> Result<(), Box<dyn Error>> {
    // Good enough for an example to allow server to start
    tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;

    let config = ConfigBuilder::<AsyncState>::default()
        .add_async_source(HttpSource {
            uri: "http://localhost:5001/configuration".into(),
            format: FileFormat::Json,
        })
        .build()
        .await?;

    println!("Config value is {}", config.get::<String>("value")?);

    Ok(())
}

// Actual implementation of AsyncSource can be found below

#[derive(Debug)]
struct HttpSource {
    uri: String,
    format: FileFormat,
}

impl HttpSource {
    async fn call(&self) -> Result<String, reqwest::Error> {
        reqwest::get(&self.uri).await?.text().await
    }
}

#[async_trait]
impl AsyncSource for HttpSource {
    async fn collect(&self) -> Result<HashMap<String, config::Value>, ConfigError> {
        self.call()
            .await
            .map_err(|e| ConfigError::Foreign(Box::new(e)))
            .and_then(|text| {
                self.format
                    .parse(Some(&self.uri), &text)
                    .map_err(|e| ConfigError::Foreign(e))
            })
    }
}
<commit_msg>Simplify example impl<commit_after>use std::{collections::HashMap, error::Error};

use config::{builder::AsyncState, AsyncSource, ConfigBuilder, ConfigError, FileFormat};

use async_trait::async_trait;
use futures::{select, FutureExt};
use warp::Filter;

// Example below presents sample configuration server and client.
//
// Server serves simple configuration on HTTP endpoint.
// Client consumes it using custom HTTP AsyncSource built on top of reqwest.

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    select! {
        r = run_server().fuse() => r,
        r = run_client().fuse() => r
    }
}

async fn run_server() -> Result<(), Box<dyn Error>> {
    let service = warp::path("configuration").map(|| r#"{ "value" : 123 }"#);

    println!("Running server on localhost:5001");

    warp::serve(service).bind(([127, 0, 0, 1], 5001)).await;

    Ok(())
}

async fn run_client() -> Result<(), Box<dyn Error>> {
    // Good enough for an example to allow server to start
    tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;

    let config = ConfigBuilder::<AsyncState>::default()
        .add_async_source(HttpSource {
            uri: "http://localhost:5001/configuration".into(),
            format: FileFormat::Json,
        })
        .build()
        .await?;

    println!("Config value is {}", config.get::<String>("value")?);

    Ok(())
}

// Actual implementation of AsyncSource can be found below

#[derive(Debug)]
struct HttpSource {
    uri: String,
    format: FileFormat,
}

#[async_trait]
impl AsyncSource for HttpSource {
    async fn collect(&self) -> Result<HashMap<String, config::Value>, ConfigError> {
        reqwest::get(&self.uri)
            .await
            .map_err(|e| ConfigError::Foreign(Box::new(e)))? // error conversion is possible from custom AsyncSource impls
            .text()
            .await
            .map_err(|e| ConfigError::Foreign(Box::new(e)))
            .and_then(|text| {
                self.format
                    .parse(Some(&self.uri), &text)
                    .map_err(|e| ConfigError::Foreign(e))
            })
    }
}
<|endoftext|>


<commit_before>use std::io::{Write, ErrorKind};
use std::io::Read;

use mio::*;
use mio::tcp::TcpStream;
use mio::unix::UnixReady;
use uuid::adapter::Hyphenated;
use sozu_command::buffer::Buffer;
use {
  SessionMetrics,
  SessionResult,
  Readiness,
  BackendConnectionStatus,
  protocol::{ProtocolResult, pipe::Pipe},
  socket::SocketHandler,
  pool::Checkout,
};

use super::header::*;

pub struct SendProxyProtocol<Front:SocketHandler> {
  pub header:         Option<Vec<u8>>,
  pub frontend:       Front,
  pub request_id:     Hyphenated,
  pub backend:        Option<TcpStream>,
  pub frontend_token: Token,
  pub backend_token:  Option<Token>,
  pub front_readiness:Readiness,
  pub back_readiness: Readiness,
  cursor_header:      usize,
}

impl <Front:SocketHandler + Read> SendProxyProtocol<Front> {
  pub fn new(frontend: Front, frontend_token: Token, request_id: Hyphenated,
    backend: Option<TcpStream>) -> Self {
    SendProxyProtocol {
      header: None,
      frontend,
      request_id,
      backend,
      frontend_token,
      backend_token:  None,
      front_readiness: Readiness {
        interest: UnixReady::hup() | UnixReady::error(),
        event:    UnixReady::from(Ready::empty()),
      },
      back_readiness: Readiness {
        interest: UnixReady::hup() | UnixReady::error(),
        event:    UnixReady::from(Ready::empty()),
      },
      cursor_header: 0,
    }
  }

  // The header is send immediately at once upon the connection is establish
  // and prepended before any data.
  pub fn back_writable(&mut self, metrics: &mut SessionMetrics) -> (ProtocolResult, SessionResult) {
    debug!("Trying to write proxy protocol header");

    // Generate the proxy protocol header if not already exist.
    if self.header.is_none() {
      if let Some(backend_addr) = self.back_socket().and_then(|s| s.peer_addr().ok()) {
        if let Ok(frontend_addr) = self.front_socket().peer_addr() {
          self.header = Some(ProxyProtocolHeader::V2(HeaderV2::new(Command::Proxy, frontend_addr, backend_addr)).into_bytes());
        } else {
          return (ProtocolResult::Continue, SessionResult::CloseSession);
        }
      };
    }

    if let Some(ref mut socket) = self.backend {
      if let Some(ref mut header) = self.header {
        loop {
          match socket.write(&mut header[self.cursor_header..]) {
            Ok(sz) => {
              self.cursor_header += sz;
              metrics.backend_bout += sz;

              if self.cursor_header == header.len() {
                debug!("Proxy protocol sent, upgrading");
                return (ProtocolResult::Upgrade, SessionResult::Continue);
              }
            },
            Err(e) => match e.kind() {
              ErrorKind::WouldBlock => {
                self.back_readiness.event.remove(Ready::writable());
                return (ProtocolResult::Continue, SessionResult::Continue);
              },
              e => {
                incr!("proxy_protocol.errors");
                debug!("send proxy protocol write error {:?}", e);
                return (ProtocolResult::Continue, SessionResult::CloseSession);
              }
            },
          }
        }
      }
    }

    error!("started Send proxy protocol with no header or backend socket");
    (ProtocolResult::Continue, SessionResult::CloseSession)
  }

  pub fn front_socket(&self) -> &TcpStream {
    self.frontend.socket_ref()
  }

  pub fn back_socket(&self) -> Option<&TcpStream> {
    self.backend.as_ref()
  }

  pub fn set_back_socket(&mut self, socket: TcpStream) {
    self.backend = Some(socket);
  }

  pub fn back_token(&self) -> Option<Token> {
    self.backend_token
  }

  pub fn set_back_token(&mut self, token: Token) {
    self.backend_token = Some(token);
  }

  pub fn set_back_connected(&mut self, status: BackendConnectionStatus) {
    if status == BackendConnectionStatus::Connected {
      self.back_readiness.interest.insert(UnixReady::from(Ready::writable()));
    }
  }

  pub fn front_readiness(&mut self) -> &mut Readiness {
    &mut self.front_readiness
  }

  pub fn back_readiness(&mut self) -> &mut Readiness {
    &mut self.back_readiness
  }

  pub fn into_pipe(mut self, front_buf: Checkout<Buffer>, back_buf: Checkout<Buffer>) -> Pipe<Front> {
    let backend_socket = self.backend.take().unwrap();
    let addr = backend_socket.peer_addr().map(|s| s.ip()).ok();

    let mut pipe = Pipe::new(
      self.frontend,
      self.frontend_token,
      self.request_id,
      Some(backend_socket),
      front_buf,
      back_buf,
      addr,
    );

    pipe.front_readiness = self.front_readiness;
    pipe.back_readiness  = self.back_readiness;

    pipe.front_readiness.interest.insert(UnixReady::from(Ready::readable()));
    pipe.back_readiness.interest.insert(UnixReady::from(Ready::readable()));

    if let Some(back_token) = self.backend_token {
      pipe.set_back_token(back_token);
    }

    pipe
  }
}

#[cfg(test)]
mod send_test {

  use super::*;

  use super::super::parser::parse_v2_header;

  use std::{sync::{Arc, Barrier}, thread::{self, JoinHandle}, net::SocketAddr};
  use mio::net::{TcpListener, TcpStream};
  use std::net::{TcpListener as StdTcpListener, TcpStream as StdTcpStream};
  use std::os::unix::io::{FromRawFd,IntoRawFd};
  use uuid::Uuid;

  #[test]
  fn it_should_send_a_proxy_protocol_header_to_the_upstream_backend() {
    setup_test_logger!();
    let addr_client: SocketAddr = "127.0.0.1:6666".parse().expect("parse address error");
    let addr_backend: SocketAddr = "127.0.0.1:2001".parse().expect("parse address error");
    let barrier = Arc::new(Barrier::new(3));
    let end_barrier = Arc::new(Barrier::new(2));

    start_client(addr_client.clone(), barrier.clone(), end_barrier.clone());
    let backend = start_backend(addr_backend.clone(), barrier.clone(), end_barrier.clone());
    start_middleware(addr_client, addr_backend, barrier.clone());

    backend.join().expect("Couldn't join on the associated backend");
  }

  // Get connection from the session and connect to the backend
  // When connections are etablish we send the proxy protocol header
  fn start_middleware(addr_client: SocketAddr, addr_backend: SocketAddr, barrier: Arc<Barrier>) {
    let listener = TcpListener::bind(&addr_client).expect("could not accept session connection");

    let client_stream;
    barrier.wait();

    loop {
      if let Ok((stream, _addr)) = listener.accept() {
        client_stream = stream;
        break;
      }
    }

    // connect in blocking first, then convert to a mio socket
    let backend_stream = StdTcpStream::connect(&addr_backend).expect("could not connect to the backend");
    let fd = backend_stream.into_raw_fd();
    let backend_stream = unsafe { TcpStream::from_raw_fd(fd) };

    let mut send_pp = SendProxyProtocol::new(client_stream, Token(0),
      Uuid::new_v4().to_hyphenated(), Some(backend_stream));
    let mut session_metrics = SessionMetrics::new();

    send_pp.set_back_connected(BackendConnectionStatus::Connected);

    loop {
      let (protocol, session) = send_pp.back_writable(&mut session_metrics);
      if session != SessionResult::Continue {
        panic!("state machine error: protocol result = {:?}, session result = {:?}", protocol, session);
      }

      if protocol == ProtocolResult::Upgrade {
        break;
      }
    }
  }

  // Only connect to the middleware
  fn start_client(addr: SocketAddr, barrier: Arc<Barrier>, end_barrier: Arc<Barrier>) {
    thread::spawn(move|| {
      barrier.wait();

      let stream = StdTcpStream::connect(&addr).unwrap();

      end_barrier.wait();
    });
  }

  // Get connection from the middleware read on the socket stream.
  // We check if we receive a valid proxy protocol header
  fn start_backend(addr: SocketAddr, barrier: Arc<Barrier>, end_barrier: Arc<Barrier>) -> JoinHandle<()> {
    let listener = StdTcpListener::bind(&addr).expect("could not start backend");

    thread::spawn(move|| {
      barrier.wait();

      let mut buf: [u8; 28] = [0; 28];
      let (mut conn, _) = listener.accept().expect("could not accept connection from light middleware");
      println!("backend got a connection from the middleware");

      let mut index = 0usize;
      loop {
        if index >= 28 {
          break;
        }

        match conn.read(&mut buf[index..]) {
          Err(e) => match e.kind() {
            ErrorKind::WouldBlock => continue,
            e => {
              end_barrier.wait();
              panic!("read error: {:?}", e);
            }
          },
          Ok(sz) => {
            println!("backend read {} bytes", sz);
            index += sz;
          },
        }
      }

      match parse_v2_header(&buf) {
        Ok((_,_)) => println!("complete header received"),
        err => {
          end_barrier.wait();
          panic!("incorrect proxy protocol header received: {:?}", err);
        }
      };

      end_barrier.wait();
    })
  }
}
<commit_msg>ProxyProtocol: fix destination address<commit_after>use std::io::{Write, ErrorKind};
use std::io::Read;

use mio::*;
use mio::tcp::TcpStream;
use mio::unix::UnixReady;
use uuid::adapter::Hyphenated;
use sozu_command::buffer::Buffer;
use {
  SessionMetrics,
  SessionResult,
  Readiness,
  BackendConnectionStatus,
  protocol::{ProtocolResult, pipe::Pipe},
  socket::SocketHandler,
  pool::Checkout,
};

use super::header::*;

pub struct SendProxyProtocol<Front:SocketHandler> {
  pub header:         Option<Vec<u8>>,
  pub frontend:       Front,
  pub request_id:     Hyphenated,
  pub backend:        Option<TcpStream>,
  pub frontend_token: Token,
  pub backend_token:  Option<Token>,
  pub front_readiness:Readiness,
  pub back_readiness: Readiness,
  cursor_header:      usize,
}

impl <Front:SocketHandler + Read> SendProxyProtocol<Front> {
  pub fn new(frontend: Front, frontend_token: Token, request_id: Hyphenated,
    backend: Option<TcpStream>) -> Self {
    SendProxyProtocol {
      header: None,
      frontend,
      request_id,
      backend,
      frontend_token,
      backend_token:  None,
      front_readiness: Readiness {
        interest: UnixReady::hup() | UnixReady::error(),
        event:    UnixReady::from(Ready::empty()),
      },
      back_readiness: Readiness {
        interest: UnixReady::hup() | UnixReady::error(),
        event:    UnixReady::from(Ready::empty()),
      },
      cursor_header: 0,
    }
  }

  // The header is send immediately at once upon the connection is establish
  // and prepended before any data.
  pub fn back_writable(&mut self, metrics: &mut SessionMetrics) -> (ProtocolResult, SessionResult) {
    debug!("Trying to write proxy protocol header");

    // Generate the proxy protocol header if not already exist.
    if self.header.is_none() {
      if let Ok(local_addr) = self.front_socket().local_addr() {
        if let Ok(frontend_addr) = self.front_socket().peer_addr() {
          self.header = Some(ProxyProtocolHeader::V2(HeaderV2::new(Command::Proxy, frontend_addr, local_addr)).into_bytes());
        } else {
          return (ProtocolResult::Continue, SessionResult::CloseSession);
        }
      };
    }

    if let Some(ref mut socket) = self.backend {
      if let Some(ref mut header) = self.header {
        loop {
          match socket.write(&mut header[self.cursor_header..]) {
            Ok(sz) => {
              self.cursor_header += sz;
              metrics.backend_bout += sz;

              if self.cursor_header == header.len() {
                debug!("Proxy protocol sent, upgrading");
                return (ProtocolResult::Upgrade, SessionResult::Continue);
              }
            },
            Err(e) => match e.kind() {
              ErrorKind::WouldBlock => {
                self.back_readiness.event.remove(Ready::writable());
                return (ProtocolResult::Continue, SessionResult::Continue);
              },
              e => {
                incr!("proxy_protocol.errors");
                debug!("send proxy protocol write error {:?}", e);
                return (ProtocolResult::Continue, SessionResult::CloseSession);
              }
            },
          }
        }
      }
    }

    error!("started Send proxy protocol with no header or backend socket");
    (ProtocolResult::Continue, SessionResult::CloseSession)
  }

  pub fn front_socket(&self) -> &TcpStream {
    self.frontend.socket_ref()
  }

  pub fn back_socket(&self) -> Option<&TcpStream> {
    self.backend.as_ref()
  }

  pub fn set_back_socket(&mut self, socket: TcpStream) {
    self.backend = Some(socket);
  }

  pub fn back_token(&self) -> Option<Token> {
    self.backend_token
  }

  pub fn set_back_token(&mut self, token: Token) {
    self.backend_token = Some(token);
  }

  pub fn set_back_connected(&mut self, status: BackendConnectionStatus) {
    if status == BackendConnectionStatus::Connected {
      self.back_readiness.interest.insert(UnixReady::from(Ready::writable()));
    }
  }

  pub fn front_readiness(&mut self) -> &mut Readiness {
    &mut self.front_readiness
  }

  pub fn back_readiness(&mut self) -> &mut Readiness {
    &mut self.back_readiness
  }

  pub fn into_pipe(mut self, front_buf: Checkout<Buffer>, back_buf: Checkout<Buffer>) -> Pipe<Front> {
    let backend_socket = self.backend.take().unwrap();
    let addr = backend_socket.peer_addr().map(|s| s.ip()).ok();

    let mut pipe = Pipe::new(
      self.frontend,
      self.frontend_token,
      self.request_id,
      Some(backend_socket),
      front_buf,
      back_buf,
      addr,
    );

    pipe.front_readiness = self.front_readiness;
    pipe.back_readiness  = self.back_readiness;

    pipe.front_readiness.interest.insert(UnixReady::from(Ready::readable()));
    pipe.back_readiness.interest.insert(UnixReady::from(Ready::readable()));

    if let Some(back_token) = self.backend_token {
      pipe.set_back_token(back_token);
    }

    pipe
  }
}

#[cfg(test)]
mod send_test {

  use super::*;

  use super::super::parser::parse_v2_header;

  use std::{sync::{Arc, Barrier}, thread::{self, JoinHandle}, net::SocketAddr};
  use mio::net::{TcpListener, TcpStream};
  use std::net::{TcpListener as StdTcpListener, TcpStream as StdTcpStream};
  use std::os::unix::io::{FromRawFd,IntoRawFd};
  use uuid::Uuid;

  #[test]
  fn it_should_send_a_proxy_protocol_header_to_the_upstream_backend() {
    setup_test_logger!();
    let addr_client: SocketAddr = "127.0.0.1:6666".parse().expect("parse address error");
    let addr_backend: SocketAddr = "127.0.0.1:2001".parse().expect("parse address error");
    let barrier = Arc::new(Barrier::new(3));
    let end_barrier = Arc::new(Barrier::new(2));

    start_client(addr_client.clone(), barrier.clone(), end_barrier.clone());
    let backend = start_backend(addr_backend.clone(), barrier.clone(), end_barrier.clone());
    start_middleware(addr_client, addr_backend, barrier.clone());

    backend.join().expect("Couldn't join on the associated backend");
  }

  // Get connection from the session and connect to the backend
  // When connections are etablish we send the proxy protocol header
  fn start_middleware(addr_client: SocketAddr, addr_backend: SocketAddr, barrier: Arc<Barrier>) {
    let listener = TcpListener::bind(&addr_client).expect("could not accept session connection");

    let client_stream;
    barrier.wait();

    loop {
      if let Ok((stream, _addr)) = listener.accept() {
        client_stream = stream;
        break;
      }
    }

    // connect in blocking first, then convert to a mio socket
    let backend_stream = StdTcpStream::connect(&addr_backend).expect("could not connect to the backend");
    let fd = backend_stream.into_raw_fd();
    let backend_stream = unsafe { TcpStream::from_raw_fd(fd) };

    let mut send_pp = SendProxyProtocol::new(client_stream, Token(0),
      Uuid::new_v4().to_hyphenated(), Some(backend_stream));
    let mut session_metrics = SessionMetrics::new();

    send_pp.set_back_connected(BackendConnectionStatus::Connected);

    loop {
      let (protocol, session) = send_pp.back_writable(&mut session_metrics);
      if session != SessionResult::Continue {
        panic!("state machine error: protocol result = {:?}, session result = {:?}", protocol, session);
      }

      if protocol == ProtocolResult::Upgrade {
        break;
      }
    }
  }

  // Only connect to the middleware
  fn start_client(addr: SocketAddr, barrier: Arc<Barrier>, end_barrier: Arc<Barrier>) {
    thread::spawn(move|| {
      barrier.wait();

      let stream = StdTcpStream::connect(&addr).unwrap();

      end_barrier.wait();
    });
  }

  // Get connection from the middleware read on the socket stream.
  // We check if we receive a valid proxy protocol header
  fn start_backend(addr: SocketAddr, barrier: Arc<Barrier>, end_barrier: Arc<Barrier>) -> JoinHandle<()> {
    let listener = StdTcpListener::bind(&addr).expect("could not start backend");

    thread::spawn(move|| {
      barrier.wait();

      let mut buf: [u8; 28] = [0; 28];
      let (mut conn, _) = listener.accept().expect("could not accept connection from light middleware");
      println!("backend got a connection from the middleware");

      let mut index = 0usize;
      loop {
        if index >= 28 {
          break;
        }

        match conn.read(&mut buf[index..]) {
          Err(e) => match e.kind() {
            ErrorKind::WouldBlock => continue,
            e => {
              end_barrier.wait();
              panic!("read error: {:?}", e);
            }
          },
          Ok(sz) => {
            println!("backend read {} bytes", sz);
            index += sz;
          },
        }
      }

      match parse_v2_header(&buf) {
        Ok((_,_)) => println!("complete header received"),
        err => {
          end_barrier.wait();
          panic!("incorrect proxy protocol header received: {:?}", err);
        }
      };

      end_barrier.wait();
    })
  }
}
<|endoftext|>


<commit_before>mod statistics;
mod planner;

use kite::query::Query;
use kite::collectors::{Collector, DocumentMatch};
use byteorder::{ByteOrder, BigEndian};

use super::RocksDBIndexReader;
use doc_id_set::DocIdSet;
use segment::{Segment, SegmentReadError};
use search::statistics::{StatisticsReader, RocksDBStatisticsReader};
use search::planner::{SearchPlan, plan_query};
use search::planner::boolean_query::BooleanQueryOp;
use search::planner::score_function::{CombinatorScorer, ScoreFunctionOp};

impl<'a> RocksDBIndexReader<'a> {
    fn search_segment_boolean_phase<S: Segment>(&self, boolean_query: &Vec<BooleanQueryOp>, is_negated: bool, segment: &S) -> Result<DocIdSet, SegmentReadError> {
        // Execute boolean query
        let mut stack = Vec::new();
        for op in boolean_query.iter() {
            match *op {
                BooleanQueryOp::PushEmpty => {
                    stack.push(DocIdSet::new_filled(0));
                }
                BooleanQueryOp::PushFull => {
                    stack.push(DocIdSet::new_filled(65536));
                }
                BooleanQueryOp::PushTermDirectory(field_ref, term_ref) => {
                    match try!(segment.load_term_directory(field_ref, term_ref)) {
                        Some(doc_id_set) => stack.push(doc_id_set),
                        None => stack.push(DocIdSet::new_filled(0)),
                    }
                }
                BooleanQueryOp::PushDeletionList => {
                     match try!(segment.load_deletion_list()) {
                        Some(doc_id_set) => stack.push(doc_id_set),
                        None => stack.push(DocIdSet::new_filled(0)),
                    }
                }
                BooleanQueryOp::And => {
                    let b = stack.pop().expect("boolean query executor: stack underflow");
                    let a = stack.pop().expect("boolean query executor: stack underflow");
                    stack.push(a.intersection(&b));
                }
                BooleanQueryOp::Or => {
                    let b = stack.pop().expect("boolean query executor: stack underflow");
                    let a = stack.pop().expect("boolean query executor: stack underflow");
                    stack.push(a.union(&b));
                }
                BooleanQueryOp::AndNot => {
                    let b = stack.pop().expect("boolean query executor: stack underflow");
                    let a = stack.pop().expect("boolean query executor: stack underflow");
                    stack.push(a.exclusion(&b));
                }
            }
        }

        if !stack.len() == 1 {
            // This shouldn't be possible unless there's a bug in the planner
            panic!("boolean query executor: stack size too big ({})", stack.len());
        }
        let mut matches = stack.pop().unwrap();

        // Invert the list if the query is negated
        if is_negated {
            let total_docs = try!(segment.load_statistic(b"total_docs")).unwrap_or(0);
            let all_docs = DocIdSet::new_filled(total_docs as u32);
            matches = all_docs.exclusion(&matches);
        }

        Ok(matches)
    }

    fn score_doc<S: Segment>(&self, doc_id: u16, score_function: &Vec<ScoreFunctionOp>, segment: &S, mut stats: &mut RocksDBStatisticsReader) -> Result<f64, SegmentReadError> {
        // Execute score function
        let mut stack = Vec::new();
        for op in score_function.iter() {
            match *op {
                ScoreFunctionOp::Literal(val) => stack.push(val),
                ScoreFunctionOp::TermScorer(field_ref, term_ref, ref scorer) => {
                    // TODO: Check this isn't really slow
                    match try!(segment.load_term_directory(field_ref, term_ref)) {
                        Some(doc_id_set) => {
                            if doc_id_set.contains_doc(doc_id) {
                                // Read field length
                                // TODO: we only need this for BM25
                                let field_length_raw = try!(segment.load_stored_field_value_raw(doc_id, field_ref, b"len"));
                                let field_length = match field_length_raw {
                                    Some(value) => {
                                        let length_sqrt = (value[0] as f64) / 3.0 + 1.0;
                                        length_sqrt * length_sqrt
                                    }
                                    None => 1.0
                                };

                                // Read term frequency
                                let mut value_type = vec![b't', b'f'];
                                value_type.extend(term_ref.ord().to_string().as_bytes());
                                let term_frequency_raw = try!(segment.load_stored_field_value_raw(doc_id, field_ref, &value_type));
                                let term_frequency = match term_frequency_raw {
                                    Some(value) => BigEndian::read_i64(&value),
                                    None => 1,
                                };

                                let score = scorer.similarity_model.score(term_frequency as u32, field_length, try!(stats.total_tokens(field_ref)) as u64, try!(stats.total_docs(field_ref)) as u64, try!(stats.term_document_frequency(field_ref, term_ref)) as u64);
                                stack.push(score * scorer.boost);
                            } else {
                                stack.push(0.0f64);
                            }
                        }
                        None => stack.push(0.0f64),
                    }
                }
                ScoreFunctionOp::CombinatorScorer(num_vals, ref scorer) => {
                    let score = match *scorer {
                        CombinatorScorer::Avg => {
                            let mut total_score = 0.0f64;

                            for _ in 0..num_vals {
                                total_score += stack.pop().expect("document scorer: stack underflow");
                            }

                            total_score / num_vals as f64
                        }
                        CombinatorScorer::Max => {
                            let mut max_score = 0.0f64;

                            for _ in 0..num_vals {
                                let score = stack.pop().expect("document scorer: stack underflow");
                                if score > max_score {
                                    max_score = score
                                }
                            }

                            max_score
                        }
                    };

                    stack.push(score);
                }
            }
        }

        if !stack.len() == 1 {
            // This shouldn't be possible unless there's a bug in the planner
            panic!("document scorer: stack size too big ({})", stack.len());
        }

        Ok(stack.pop().expect("document scorer: stack underflow"))
    }

    fn search_segment<C: Collector, S: Segment>(&self, collector: &mut C, plan: &SearchPlan, segment: &S, mut stats: &mut RocksDBStatisticsReader) -> Result<(), SegmentReadError> {
        let matches = try!(self.search_segment_boolean_phase(&plan.boolean_query, plan.boolean_query_is_negated, segment));

        // Score documents and pass to collector
        for doc in matches.iter() {
            let score = try!(self.score_doc(doc, &plan.score_function, segment, &mut stats));

            let doc_ref = segment.doc_ref(doc);
            let doc_match = DocumentMatch::new_scored(doc_ref.as_u64(), score);
            collector.collect(doc_match);
        }

        Ok(())
    }

    pub fn search<C: Collector>(&self, collector: &mut C, query: &Query) -> Result<(), SegmentReadError> {
        // Plan query
        let plan = plan_query(&self, query, collector.needs_score());

        // Initialise statistics reader
        let mut stats = RocksDBStatisticsReader::new(&self);

        // Run query on each segment
        for segment in self.store.segments.iter_active(&self) {
            try!(self.search_segment(collector, &plan, &segment, &mut stats));
        }

        Ok(())
    }
}
<commit_msg>Make score_docs and search_segments statistics paramater generic<commit_after>mod statistics;
mod planner;

use kite::query::Query;
use kite::collectors::{Collector, DocumentMatch};
use byteorder::{ByteOrder, BigEndian};

use super::RocksDBIndexReader;
use doc_id_set::DocIdSet;
use segment::{Segment, SegmentReadError};
use search::statistics::{StatisticsReader, RocksDBStatisticsReader};
use search::planner::{SearchPlan, plan_query};
use search::planner::boolean_query::BooleanQueryOp;
use search::planner::score_function::{CombinatorScorer, ScoreFunctionOp};

impl<'a> RocksDBIndexReader<'a> {
    fn search_segment_boolean_phase<S: Segment>(&self, boolean_query: &Vec<BooleanQueryOp>, is_negated: bool, segment: &S) -> Result<DocIdSet, SegmentReadError> {
        // Execute boolean query
        let mut stack = Vec::new();
        for op in boolean_query.iter() {
            match *op {
                BooleanQueryOp::PushEmpty => {
                    stack.push(DocIdSet::new_filled(0));
                }
                BooleanQueryOp::PushFull => {
                    stack.push(DocIdSet::new_filled(65536));
                }
                BooleanQueryOp::PushTermDirectory(field_ref, term_ref) => {
                    match try!(segment.load_term_directory(field_ref, term_ref)) {
                        Some(doc_id_set) => stack.push(doc_id_set),
                        None => stack.push(DocIdSet::new_filled(0)),
                    }
                }
                BooleanQueryOp::PushDeletionList => {
                     match try!(segment.load_deletion_list()) {
                        Some(doc_id_set) => stack.push(doc_id_set),
                        None => stack.push(DocIdSet::new_filled(0)),
                    }
                }
                BooleanQueryOp::And => {
                    let b = stack.pop().expect("boolean query executor: stack underflow");
                    let a = stack.pop().expect("boolean query executor: stack underflow");
                    stack.push(a.intersection(&b));
                }
                BooleanQueryOp::Or => {
                    let b = stack.pop().expect("boolean query executor: stack underflow");
                    let a = stack.pop().expect("boolean query executor: stack underflow");
                    stack.push(a.union(&b));
                }
                BooleanQueryOp::AndNot => {
                    let b = stack.pop().expect("boolean query executor: stack underflow");
                    let a = stack.pop().expect("boolean query executor: stack underflow");
                    stack.push(a.exclusion(&b));
                }
            }
        }

        if !stack.len() == 1 {
            // This shouldn't be possible unless there's a bug in the planner
            panic!("boolean query executor: stack size too big ({})", stack.len());
        }
        let mut matches = stack.pop().unwrap();

        // Invert the list if the query is negated
        if is_negated {
            let total_docs = try!(segment.load_statistic(b"total_docs")).unwrap_or(0);
            let all_docs = DocIdSet::new_filled(total_docs as u32);
            matches = all_docs.exclusion(&matches);
        }

        Ok(matches)
    }

    fn score_doc<S: Segment, R: StatisticsReader>(&self, doc_id: u16, score_function: &Vec<ScoreFunctionOp>, segment: &S, mut stats: &mut R) -> Result<f64, SegmentReadError> {
        // Execute score function
        let mut stack = Vec::new();
        for op in score_function.iter() {
            match *op {
                ScoreFunctionOp::Literal(val) => stack.push(val),
                ScoreFunctionOp::TermScorer(field_ref, term_ref, ref scorer) => {
                    // TODO: Check this isn't really slow
                    match try!(segment.load_term_directory(field_ref, term_ref)) {
                        Some(doc_id_set) => {
                            if doc_id_set.contains_doc(doc_id) {
                                // Read field length
                                // TODO: we only need this for BM25
                                let field_length_raw = try!(segment.load_stored_field_value_raw(doc_id, field_ref, b"len"));
                                let field_length = match field_length_raw {
                                    Some(value) => {
                                        let length_sqrt = (value[0] as f64) / 3.0 + 1.0;
                                        length_sqrt * length_sqrt
                                    }
                                    None => 1.0
                                };

                                // Read term frequency
                                let mut value_type = vec![b't', b'f'];
                                value_type.extend(term_ref.ord().to_string().as_bytes());
                                let term_frequency_raw = try!(segment.load_stored_field_value_raw(doc_id, field_ref, &value_type));
                                let term_frequency = match term_frequency_raw {
                                    Some(value) => BigEndian::read_i64(&value),
                                    None => 1,
                                };

                                let score = scorer.similarity_model.score(term_frequency as u32, field_length, try!(stats.total_tokens(field_ref)) as u64, try!(stats.total_docs(field_ref)) as u64, try!(stats.term_document_frequency(field_ref, term_ref)) as u64);
                                stack.push(score * scorer.boost);
                            } else {
                                stack.push(0.0f64);
                            }
                        }
                        None => stack.push(0.0f64),
                    }
                }
                ScoreFunctionOp::CombinatorScorer(num_vals, ref scorer) => {
                    let score = match *scorer {
                        CombinatorScorer::Avg => {
                            let mut total_score = 0.0f64;

                            for _ in 0..num_vals {
                                total_score += stack.pop().expect("document scorer: stack underflow");
                            }

                            total_score / num_vals as f64
                        }
                        CombinatorScorer::Max => {
                            let mut max_score = 0.0f64;

                            for _ in 0..num_vals {
                                let score = stack.pop().expect("document scorer: stack underflow");
                                if score > max_score {
                                    max_score = score
                                }
                            }

                            max_score
                        }
                    };

                    stack.push(score);
                }
            }
        }

        if !stack.len() == 1 {
            // This shouldn't be possible unless there's a bug in the planner
            panic!("document scorer: stack size too big ({})", stack.len());
        }

        Ok(stack.pop().expect("document scorer: stack underflow"))
    }

    fn search_segment<C: Collector, S: Segment, R: StatisticsReader>(&self, collector: &mut C, plan: &SearchPlan, segment: &S, mut stats: &mut R) -> Result<(), SegmentReadError> {
        let matches = try!(self.search_segment_boolean_phase(&plan.boolean_query, plan.boolean_query_is_negated, segment));

        // Score documents and pass to collector
        for doc in matches.iter() {
            let score = try!(self.score_doc(doc, &plan.score_function, segment, stats));

            let doc_ref = segment.doc_ref(doc);
            let doc_match = DocumentMatch::new_scored(doc_ref.as_u64(), score);
            collector.collect(doc_match);
        }

        Ok(())
    }

    pub fn search<C: Collector>(&self, collector: &mut C, query: &Query) -> Result<(), SegmentReadError> {
        // Plan query
        let plan = plan_query(&self, query, collector.needs_score());

        // Initialise statistics reader
        let mut stats = RocksDBStatisticsReader::new(&self);

        // Run query on each segment
        for segment in self.store.segments.iter_active(&self) {
            try!(self.search_segment(collector, &plan, &segment, &mut stats));
        }

        Ok(())
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2017 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#![feature(proc_macro)]

extern crate gtk;
#[macro_use]
extern crate relm;
extern crate relm_attributes;
#[macro_use]
extern crate relm_derive;
#[macro_use]
extern crate relm_test;

use gtk::{
    ButtonExt,
    Inhibit,
    LabelExt,
    OrientableExt,
    WidgetExt,
};
use gtk::Orientation::Vertical;
use relm::Widget;
use relm_attributes::widget;

use self::Msg::*;

// Define the structure of the model.
pub struct Model {
    counter: i32,
}

// The messages that can be sent to the update function.
#[derive(Msg)]
pub enum Msg {
    #[cfg(test)] Test,
    Decrement,
    Increment,
    Quit,
}

#[widget]
impl Widget for Win {
    // The initial model.
    fn model() -> Model {
        Model {
            counter: 0,
        }
    }

    // Update the model according to the message received.
    fn update(&mut self, event: Msg) {
        match event {
            #[cfg(test)] Test => (),
            Decrement => self.model.counter -= 1,
            Increment => self.model.counter += 1,
            Quit => gtk::main_quit(),
        }
    }

    view! {
        gtk::Window {
            gtk::Box {
                // Set the orientation property of the Box.
                orientation: Vertical,
                // Create a Button inside the Box.
                #[name="inc_button"]
                gtk::Button {
                    // Send the message Increment when the button is clicked.
                    clicked => Increment,
                    // TODO: check if using two events of the same name work.
                    label: "+",
                },
                #[name="label"]
                gtk::Label {
                    // Bind the text property of the label to the counter attribute of the model.
                    text: &self.model.counter.to_string(),
                },
                #[name="dec_button"]
                gtk::Button {
                    clicked => Decrement,
                    label: "-",
                },
            },
            delete_event(_, _) => (Quit, Inhibit(false)),
        }
    }
}

fn main() {
    Win::run(()).unwrap();
}

#[cfg(test)]
mod tests {
    use gtk::LabelExt;

    use relm;
    use relm_test::click;

    use Win;

    #[test]
    fn label_change() {
        let (_component, widgets) = relm::init_test::<Win>(()).unwrap();
        let inc_button = &widgets.inc_button;
        let dec_button = &widgets.dec_button;
        let label = &widgets.label;

        assert_text!(label, 0);
        click(inc_button);
        assert_text!(label, 1);
        click(inc_button);
        assert_text!(label, 2);
        click(inc_button);
        assert_text!(label, 3);
        click(inc_button);
        assert_text!(label, 4);

        click(dec_button);
        assert_text!(label, 3);
        click(dec_button);
        assert_text!(label, 2);
        click(dec_button);
        assert_text!(label, 1);
        click(dec_button);
        assert_text!(label, 0);
        click(dec_button);
        assert_text!(label, 0);
    }
}
<commit_msg>Fix typo<commit_after>/*
 * Copyright (c) 2017 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#![feature(proc_macro)]

extern crate gtk;
#[macro_use]
extern crate relm;
extern crate relm_attributes;
#[macro_use]
extern crate relm_derive;
#[macro_use]
extern crate relm_test;

use gtk::{
    ButtonExt,
    Inhibit,
    LabelExt,
    OrientableExt,
    WidgetExt,
};
use gtk::Orientation::Vertical;
use relm::Widget;
use relm_attributes::widget;

use self::Msg::*;

// Define the structure of the model.
pub struct Model {
    counter: i32,
}

// The messages that can be sent to the update function.
#[derive(Msg)]
pub enum Msg {
    #[cfg(test)] Test,
    Decrement,
    Increment,
    Quit,
}

#[widget]
impl Widget for Win {
    // The initial model.
    fn model() -> Model {
        Model {
            counter: 0,
        }
    }

    // Update the model according to the message received.
    fn update(&mut self, event: Msg) {
        match event {
            #[cfg(test)] Test => (),
            Decrement => self.model.counter -= 1,
            Increment => self.model.counter += 1,
            Quit => gtk::main_quit(),
        }
    }

    view! {
        gtk::Window {
            gtk::Box {
                // Set the orientation property of the Box.
                orientation: Vertical,
                // Create a Button inside the Box.
                #[name="inc_button"]
                gtk::Button {
                    // Send the message Increment when the button is clicked.
                    clicked => Increment,
                    // TODO: check if using two events of the same name work.
                    label: "+",
                },
                #[name="label"]
                gtk::Label {
                    // Bind the text property of the label to the counter attribute of the model.
                    text: &self.model.counter.to_string(),
                },
                #[name="dec_button"]
                gtk::Button {
                    clicked => Decrement,
                    label: "-",
                },
            },
            delete_event(_, _) => (Quit, Inhibit(false)),
        }
    }
}

fn main() {
    Win::run(()).unwrap();
}

#[cfg(test)]
mod tests {
    use gtk::LabelExt;

    use relm;
    use relm_test::click;

    use Win;

    #[test]
    fn label_change() {
        let (_component, widgets) = relm::init_test::<Win>(()).unwrap();
        let inc_button = &widgets.inc_button;
        let dec_button = &widgets.dec_button;
        let label = &widgets.label;

        assert_text!(label, 0);
        click(inc_button);
        assert_text!(label, 1);
        click(inc_button);
        assert_text!(label, 2);
        click(inc_button);
        assert_text!(label, 3);
        click(inc_button);
        assert_text!(label, 4);

        click(dec_button);
        assert_text!(label, 3);
        click(dec_button);
        assert_text!(label, 2);
        click(dec_button);
        assert_text!(label, 1);
        click(dec_button);
        assert_text!(label, 0);
        click(dec_button);
        assert_text!(label, -1);
    }
}
<|endoftext|>


<commit_before>use std::path::Path;
use super::{Driver, Dataset, Feature, FeatureIterator, Geometry};

mod convert_geo;

macro_rules! fixture {
    ($name:expr) => (
        Path::new(file!())
            .parent().unwrap()
            .parent().unwrap()
            .parent().unwrap()
            .parent().unwrap()
            .join("fixtures").as_path()
            .join($name).as_path()
    )
}

fn assert_almost_eq(a: f64, b: f64) {
    let f: f64 = a / b;
    assert!(f < 1.00001);
    assert!(f > 0.99999);
}

#[test]
fn test_layer_count() {
    let ds = Dataset::open(fixture!("roads.geojson")).unwrap();
    assert_eq!(ds.count(), 1);
}

fn with_features<F>(name: &str, f: F) where F: Fn(FeatureIterator) {
    let mut ds = Dataset::open(fixture!(name)).unwrap();
    let layer = ds.layer(0).unwrap();
    f(layer.features());
}

fn with_first_feature<F>(name: &str, f: F) where F: Fn(Feature) {
    with_features(name, |mut features| f(features.next().unwrap()));
}

#[test]
fn test_iterate_features() {
    with_features("roads.geojson", |features| {
        let feature_vec: Vec<Feature> = features.collect();
        assert_eq!(feature_vec.len(), 21);
    });
}

#[test]
fn test_string_field() {
    with_features("roads.geojson", |mut features| {
        let feature = features.next().unwrap();
        assert_eq!(feature.field("highway")
                          .unwrap()
                          .as_string(),
                   "footway".to_string());
        assert_eq!(
            features.filter(|field| {
                let highway = field.field("highway")
                                   .unwrap()
                                   .as_string();
                highway == "residential".to_string() })
                .count(),
            2);
    });
}

#[test]
fn test_float_field() {
    with_first_feature("roads.geojson", |feature| {
        assert_almost_eq(
            feature.field("sort_key")
                   .unwrap()
                   .as_real(),
            -9.0
        );
    });
}

#[test]
fn test_missing_field() {
    with_first_feature("roads.geojson", |feature| {
        assert!(feature.field("no such field").is_none());
    });
}

#[test]
fn test_wkt() {
    with_first_feature("roads.geojson", |feature| {
        let wkt = feature.geometry().wkt().unwrap();
        let wkt_ok = format!("{}{}",
            "LINESTRING (26.1019276 44.4302748,",
            "26.1019382 44.4303191,26.1020002 44.4304202)"
            ).to_string();
        assert_eq!(wkt, wkt_ok);
    });
}

#[test]
fn test_json() {
    with_first_feature("roads.geojson", |feature| {
        let json = feature.geometry().json();
        let json_ok = format!("{}{}{}{}",
            "{ \"type\": \"LineString\", \"coordinates\": [ ",
            "[ 26.1019276, 44.4302748 ], ",
            "[ 26.1019382, 44.4303191 ], ",
            "[ 26.1020002, 44.4304202 ] ] }"
            ).to_string();
        assert_eq!(json.unwrap(), json_ok);
    });
}

#[test]
fn test_schema() {
    let mut ds = Dataset::open(fixture!("roads.geojson")).unwrap();
    let layer = ds.layer(0).unwrap();
    let name_list: Vec<String> = layer
        .defn().fields()
        .map(|f| f.name())
        .collect();
    let ok_names: Vec<String> = vec!(
        "kind", "sort_key", "is_link", "is_tunnel",
        "is_bridge", "railway", "highway")
        .iter().map(|s| s.to_string()).collect();
    assert_eq!(name_list, ok_names);
}

#[test]
fn test_create_bbox() {
    let bbox = Geometry::bbox(-27., 33., 52., 85.).unwrap();
    assert_eq!(bbox.json().unwrap(), "{ \"type\": \"Polygon\", \"coordinates\": [ [ [ -27.0, 85.0 ], [ 52.0, 85.0 ], [ 52.0, 33.0 ], [ -27.0, 33.0 ], [ -27.0, 85.0 ] ] ] }");
}

#[test]
fn test_spatial_filter() {
    let mut ds = Dataset::open(fixture!("roads.geojson")).unwrap();
    let layer = ds.layer(0).unwrap();

    let all_features: Vec<Feature> = layer.features().collect();
    assert_eq!(all_features.len(), 21);

    let bbox = Geometry::bbox(26.1017, 44.4297, 26.1025, 44.4303).unwrap();
    layer.set_spatial_filter(&bbox);

    let some_features: Vec<Feature> = layer.features().collect();
    assert_eq!(some_features.len(), 7);

    layer.clear_spatial_filter();

    let again_all_features: Vec<Feature> = layer.features().collect();
    assert_eq!(again_all_features.len(), 21);
}

#[test]
fn test_convex_hull() {
    let star = "POLYGON ((0 1,3 1,1 3,1.5 0.0,2 3,0 1))";
    let hull = "POLYGON ((1.5 0.0,0 1,1 3,2 3,3 1,1.5 0.0))";
    assert_eq!(Geometry::from_wkt(star).unwrap().convex_hull().unwrap().wkt().unwrap(), hull);
}

#[test]
fn test_write_features() {
    use std::fs;

    {
        let driver = Driver::get("GeoJSON").unwrap();
        let mut ds = driver.create(fixture!("output.geojson")).unwrap();
        let mut layer = ds.create_layer().unwrap();
        layer.create_feature(Geometry::from_wkt("POINT (1 2)").unwrap()).unwrap();
        // dataset is closed here
    }

    let mut ds = Dataset::open(fixture!("output.geojson")).unwrap();
    fs::remove_file(fixture!("output.geojson")).unwrap();
    let layer = ds.layer(0).unwrap();
    let wkt_list = layer.features()
        .map(|f| f.geometry().wkt().unwrap())
        .collect::<Vec<String>>();
    assert_eq!(wkt_list, vec!("POINT (1 2)"));
}
<commit_msg>Update tests to reflect changes<commit_after>use std::path::Path;
use super::{Driver, Dataset, Feature, FeatureIterator, FieldValue, Geometry, OFT_INTEGER, OFT_REAL, OFT_STRING};

mod convert_geo;

macro_rules! fixture {
    ($name:expr) => (
        Path::new(file!())
            .parent().unwrap()
            .parent().unwrap()
            .parent().unwrap()
            .parent().unwrap()
            .join("fixtures").as_path()
            .join($name).as_path()
    )
}

fn assert_almost_eq(a: f64, b: f64) {
    let f: f64 = a / b;
    assert!(f < 1.00001);
    assert!(f > 0.99999);
}

#[test]
fn test_layer_count() {
    let ds = Dataset::open(fixture!("roads.geojson")).unwrap();
    assert_eq!(ds.count(), 1);
}

fn with_features<F>(name: &str, f: F) where F: Fn(FeatureIterator) {
    let mut ds = Dataset::open(fixture!(name)).unwrap();
    let layer = ds.layer(0).unwrap();
    f(layer.features());
}

fn with_first_feature<F>(name: &str, f: F) where F: Fn(Feature) {
    with_features(name, |mut features| f(features.next().unwrap()));
}

#[test]
fn test_iterate_features() {
    with_features("roads.geojson", |features| {
        let feature_vec: Vec<Feature> = features.collect();
        assert_eq!(feature_vec.len(), 21);
    });
}

#[test]
fn test_string_field() {
    with_features("roads.geojson", |mut features| {
        let feature = features.next().unwrap();
        assert_eq!(feature.field("highway")
                          .unwrap()
                          .as_string(),
                   "footway".to_string());
        assert_eq!(
            features.filter(|field| {
                let highway = field.field("highway")
                                   .unwrap()
                                   .as_string();
                highway == "residential".to_string() })
                .count(),
            2);
    });
}

#[test]
fn test_float_field() {
    with_first_feature("roads.geojson", |feature| {
        assert_almost_eq(
            feature.field("sort_key")
                   .unwrap()
                   .as_real(),
            -9.0
        );
    });
}

#[test]
fn test_missing_field() {
    with_first_feature("roads.geojson", |feature| {
        assert!(feature.field("no such field").is_none());
    });
}

#[test]
fn test_wkt() {
    with_first_feature("roads.geojson", |feature| {
        let wkt = feature.geometry().wkt().unwrap();
        let wkt_ok = format!("{}{}",
            "LINESTRING (26.1019276 44.4302748,",
            "26.1019382 44.4303191,26.1020002 44.4304202)"
            ).to_string();
        assert_eq!(wkt, wkt_ok);
    });
}

#[test]
fn test_json() {
    with_first_feature("roads.geojson", |feature| {
        let json = feature.geometry().json();
        let json_ok = format!("{}{}{}{}",
            "{ \"type\": \"LineString\", \"coordinates\": [ ",
            "[ 26.1019276, 44.4302748 ], ",
            "[ 26.1019382, 44.4303191 ], ",
            "[ 26.1020002, 44.4304202 ] ] }"
            ).to_string();
        assert_eq!(json.unwrap(), json_ok);
    });
}

#[test]
fn test_schema() {
    let mut ds = Dataset::open(fixture!("roads.geojson")).unwrap();
    let layer = ds.layer(0).unwrap();
    let name_list: Vec<(String, i32)> = layer
        .defn().fields()
        .map(|f| (f.name(), f.get_type()))
        .collect();
    let ok_names_types: Vec<(String, i32)> = vec!(
        ("kind", OFT_STRING),
        ("sort_key", OFT_REAL),
        ("is_link", OFT_STRING),
        ("is_tunnel", OFT_STRING),
        ("is_bridge", OFT_STRING),
        ("railway", OFT_STRING),
        ("highway", OFT_STRING))
        .iter().map(|s| (s.0.to_string(), s.1)).collect();
    assert_eq!(name_list, ok_names_types);
}

#[test]
fn test_create_bbox() {
    let bbox = Geometry::bbox(-27., 33., 52., 85.).unwrap();
    assert_eq!(bbox.json().unwrap(), "{ \"type\": \"Polygon\", \"coordinates\": [ [ [ -27.0, 85.0 ], [ 52.0, 85.0 ], [ 52.0, 33.0 ], [ -27.0, 33.0 ], [ -27.0, 85.0 ] ] ] }");
}

#[test]
fn test_spatial_filter() {
    let mut ds = Dataset::open(fixture!("roads.geojson")).unwrap();
    let layer = ds.layer(0).unwrap();

    let all_features: Vec<Feature> = layer.features().collect();
    assert_eq!(all_features.len(), 21);

    let bbox = Geometry::bbox(26.1017, 44.4297, 26.1025, 44.4303).unwrap();
    layer.set_spatial_filter(&bbox);

    let some_features: Vec<Feature> = layer.features().collect();
    assert_eq!(some_features.len(), 7);

    layer.clear_spatial_filter();

    let again_all_features: Vec<Feature> = layer.features().collect();
    assert_eq!(again_all_features.len(), 21);
}

#[test]
fn test_convex_hull() {
    let star = "POLYGON ((0 1,3 1,1 3,1.5 0.0,2 3,0 1))";
    let hull = "POLYGON ((1.5 0.0,0 1,1 3,2 3,3 1,1.5 0.0))";
    assert_eq!(Geometry::from_wkt(star).unwrap().convex_hull().unwrap().wkt().unwrap(), hull);
}

#[test]
fn test_write_features() {
    use std::fs;

    {
        let driver = Driver::get("GeoJSON").unwrap();
        let mut ds = driver.create(fixture!("output.geojson")).unwrap();
        let mut layer = ds.create_layer().unwrap();
        layer.create_defn_fields(&[("Name", OFT_STRING), ("Value", OFT_REAL), ("Int_value", OFT_INTEGER)]);
        layer.create_feature_fields(
            Geometry::from_wkt("POINT (1 2)").unwrap(), &["Name", "Value", "Int_value"],
            &[FieldValue::StringValue("Feature 1".to_string()), FieldValue::RealValue(45.78), FieldValue::IntegerValue(1)]
            ).unwrap();
        // dataset is closed here
    }

    let mut ds = Dataset::open(fixture!("output.geojson")).unwrap();
    fs::remove_file(fixture!("output.geojson")).unwrap();
    let layer = ds.layer(0).unwrap();
    let ft = layer.features().next().unwrap();
    assert_eq!(ft.geometry().wkt().unwrap(), "POINT (1 2)");
    assert_eq!(ft.field("Name").unwrap().as_string(), "Feature 1");
    assert_eq!(ft.field("Value").unwrap().as_real(), 45.78);
    assert_eq!(ft.field("Int_value").unwrap().as_int(), 1);
}
<|endoftext|>


<commit_before>use futures::future::{self, Future};
use futures::executor::block_on;
use futures::stream::{self, StreamExt};
use futures::task::Poll;

#[test]
fn select() {
    fn select_and_compare(a: Vec<u32>, b: Vec<u32>, expected: Vec<u32>) {
        let a = stream::iter(a);
        let b = stream::iter(b);
        let vec = block_on(stream::select(a, b).collect::<Vec<_>>());
        assert_eq!(vec, expected);
    }

    select_and_compare(vec![1, 2, 3], vec![4, 5, 6], vec![1, 4, 2, 5, 3, 6]);
    select_and_compare(vec![1, 2, 3], vec![4, 5], vec![1, 4, 2, 5, 3]);
    select_and_compare(vec![1, 2], vec![4, 5, 6], vec![1, 4, 2, 5, 6]);
}

#[test]
fn flat_map() {
    futures::executor::block_on(async {
        let st = stream::iter(vec![
            stream::iter(0..=4u8),
            stream::iter(6..=10),
            stream::iter(0..=2),
        ]);

        let values: Vec<_> = st
            .flat_map(|s| s.filter(|v| futures::future::ready(v % 2 == 0)))
            .collect()
            .await;

        assert_eq!(values, vec![0, 2, 4, 6, 8, 10, 0, 2]);
    });
}

#[test]
fn scan() {
    futures::executor::block_on(async {
        assert_eq!(
            stream::iter(vec![1u8, 2, 3, 4, 6, 8, 2])
                .scan(1, |state, e| {
                    *state += 1;
                    futures::future::ready(if e < *state { Some(e) } else { None })
                })
                .collect::<Vec<_>>()
                .await,
            vec![1u8, 2, 3, 4]
        );
    });
}

#[test]
fn take_until() {
    fn make_stop_fut(stop_on: u32) -> impl Future<Output = ()> {
        let mut i = 0;
        future::poll_fn(move |_cx| {
            i += 1;
            if i <= stop_on {
                Poll::Pending
            } else {
                Poll::Ready(())
            }
        })
    }

    futures::executor::block_on(async {
        // Verify stopping works:
        let stream = stream::iter(1u32..=10);
        let stop_fut = make_stop_fut(5);

        let stream = stream.take_until(stop_fut);
        let last = stream.fold(0, |_, i| async move { i }).await;
        assert_eq!(last, 5);

        // Verify take_future() works:
        let stream = stream::iter(1..=10);
        let stop_fut = make_stop_fut(5);

        let mut stream = stream.take_until(stop_fut);

        assert_eq!(stream.next().await, Some(1));
        assert_eq!(stream.next().await, Some(2));

        stream.take_future();

        let last = stream.fold(0, |_, i| async move { i }).await;
        assert_eq!(last, 10);

        // Verify take_future() returns None if stream is stopped:
        let stream = stream::iter(1u32..=10);
        let stop_fut = make_stop_fut(1);
        let mut stream = stream.take_until(stop_fut);
        assert_eq!(stream.next().await, Some(1));
        assert_eq!(stream.next().await, None);
        assert!(stream.take_future().is_none());

        // Verify TakeUntil is fused:
        let mut i = 0;
        let stream = stream::poll_fn(move |_cx| {
            i += 1;
            match i {
                1 => Poll::Ready(Some(1)),
                2 => Poll::Ready(None),
                _ => panic!("TakeUntil not fused"),
            }
        });

        let stop_fut = make_stop_fut(1);
        let mut stream = stream.take_until(stop_fut);
        assert_eq!(stream.next().await, Some(1));
        assert_eq!(stream.next().await, None);
        assert_eq!(stream.next().await, None);
    });
}
<commit_msg>futures: tests/stream.rs: Don't break w/o "executor"<commit_after>#[cfg(feature = "executor")] // executor::
#[test]
fn select() {
    use futures::executor::block_on;
    use futures::stream::{self, StreamExt};

    fn select_and_compare(a: Vec<u32>, b: Vec<u32>, expected: Vec<u32>) {
        let a = stream::iter(a);
        let b = stream::iter(b);
        let vec = block_on(stream::select(a, b).collect::<Vec<_>>());
        assert_eq!(vec, expected);
    }

    select_and_compare(vec![1, 2, 3], vec![4, 5, 6], vec![1, 4, 2, 5, 3, 6]);
    select_and_compare(vec![1, 2, 3], vec![4, 5], vec![1, 4, 2, 5, 3]);
    select_and_compare(vec![1, 2], vec![4, 5, 6], vec![1, 4, 2, 5, 6]);
}

#[cfg(feature = "executor")] // executor::
#[test]
fn flat_map() {
    use futures::stream::{self, StreamExt};

    futures::executor::block_on(async {
        let st = stream::iter(vec![
            stream::iter(0..=4u8),
            stream::iter(6..=10),
            stream::iter(0..=2),
        ]);

        let values: Vec<_> = st
            .flat_map(|s| s.filter(|v| futures::future::ready(v % 2 == 0)))
            .collect()
            .await;

        assert_eq!(values, vec![0, 2, 4, 6, 8, 10, 0, 2]);
    });
}

#[cfg(feature = "executor")] // executor::
#[test]
fn scan() {
    use futures::stream::{self, StreamExt};

    futures::executor::block_on(async {
        assert_eq!(
            stream::iter(vec![1u8, 2, 3, 4, 6, 8, 2])
                .scan(1, |state, e| {
                    *state += 1;
                    futures::future::ready(if e < *state { Some(e) } else { None })
                })
                .collect::<Vec<_>>()
                .await,
            vec![1u8, 2, 3, 4]
        );
    });
}

#[cfg(feature = "executor")] // executor::
#[test]
fn take_until() {
    use futures::future::{self, Future};
    use futures::stream::{self, StreamExt};
    use futures::task::Poll;

    fn make_stop_fut(stop_on: u32) -> impl Future<Output = ()> {
        let mut i = 0;
        future::poll_fn(move |_cx| {
            i += 1;
            if i <= stop_on {
                Poll::Pending
            } else {
                Poll::Ready(())
            }
        })
    }

    futures::executor::block_on(async {
        // Verify stopping works:
        let stream = stream::iter(1u32..=10);
        let stop_fut = make_stop_fut(5);

        let stream = stream.take_until(stop_fut);
        let last = stream.fold(0, |_, i| async move { i }).await;
        assert_eq!(last, 5);

        // Verify take_future() works:
        let stream = stream::iter(1..=10);
        let stop_fut = make_stop_fut(5);

        let mut stream = stream.take_until(stop_fut);

        assert_eq!(stream.next().await, Some(1));
        assert_eq!(stream.next().await, Some(2));

        stream.take_future();

        let last = stream.fold(0, |_, i| async move { i }).await;
        assert_eq!(last, 10);

        // Verify take_future() returns None if stream is stopped:
        let stream = stream::iter(1u32..=10);
        let stop_fut = make_stop_fut(1);
        let mut stream = stream.take_until(stop_fut);
        assert_eq!(stream.next().await, Some(1));
        assert_eq!(stream.next().await, None);
        assert!(stream.take_future().is_none());

        // Verify TakeUntil is fused:
        let mut i = 0;
        let stream = stream::poll_fn(move |_cx| {
            i += 1;
            match i {
                1 => Poll::Ready(Some(1)),
                2 => Poll::Ready(None),
                _ => panic!("TakeUntil not fused"),
            }
        });

        let stop_fut = make_stop_fut(1);
        let mut stream = stream.take_until(stop_fut);
        assert_eq!(stream.next().await, Some(1));
        assert_eq!(stream.next().await, None);
        assert_eq!(stream.next().await, None);
    });
}
<|endoftext|>


<commit_before>//! Command line modules: has one function which takes input parameters from commandline
//! and parses them.
use super::config;

static APP_TITLE: &str = concat!("The audiobook finder (", env!("CARGO_PKG_NAME"), ")");

static ARG_NET: &str = "net";
static ARG_TUI: &str = "tui";
static ARG_WEBUI: &str = "webui";
static ARG_KEEP_ALIVE: &str = "keep";
static ARG_BROWSER: &str = "browser";
static ARG_BROWSER_PORT: &str = "port";

static INPUT_FOLDERS: &str = "folders";

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const AUTHORS: &'static str = env!("CARGO_PKG_AUTHORS");
const HOMEPAGE: &'static str = env!("CARGO_PKG_HOMEPAGE");
const DESCRIPTION: &'static str = env!("CARGO_PKG_DESCRIPTION");

/// Get all start values which are passed from command line
///
/// Get all start values and returns the following tuple
/// ui_paths,
/// has_tui,
/// has_webui,
/// has_net,
/// keep_alive,
/// open_browser,
/// web_port,
/// has_ui,
pub fn get_start_values() -> (Vec<String>, bool, bool, bool, bool, bool, u16, bool) {
    let parse_args = clap::App::new(APP_TITLE)
        .version(VERSION)
        .author(AUTHORS)
        .about(DESCRIPTION)
        .long_about::<&str>(
            &[
                &DESCRIPTION,
                "\n\
                 It reads data from possibly multiple given path(s). Via local network it searches \
                 for other instances of the program, and will later exchange data securely.\n\
                 All information gathered will be used to find duplicates, versions of \
                 different quality, different tags for same content (spelling, \
                 incompleteness).\n\
                 For documentation see: ",
                &HOMEPAGE,
                "\n \
                 A program to learn, embrace, and love Rust! \n\
                 Have fun!",
            ]
            .concat(),
        )
        .arg(
            clap::Arg::with_name("config")
                .short("c")
                .long("config")
                .value_name("FILE")
                .help("Sets custom config file (not implemented yet)")
                .takes_value(true),
        )
        .arg(
            clap::Arg::with_name(ARG_TUI)
                .short("t")
                .long(ARG_TUI)
                .help("Run with TUI")
                .takes_value(false),
        )
        .arg(
            clap::Arg::with_name(ARG_WEBUI)
                .short("w")
                .long(ARG_WEBUI)
                .help("Run with-in a webui.")
                .takes_value(false),
        )
        .arg(
            clap::Arg::with_name(ARG_BROWSER_PORT)
                .short("p")
                .long(ARG_BROWSER_PORT)
                .help(
                    &vec![
                        "Define port for webui (only works with webui).\nDefault port is:"
                            .to_string(),
                        config::net::WEB_PORT_DEFAULT.to_string(),
                        " but please choose from 8080 to 8099)".to_string(),
                    ]
                    .join(""),
                )
                .takes_value(true),
        )
        .arg(
            clap::Arg::with_name(ARG_NET)
                .short("n")
                .long(ARG_NET)
                .help("With net search for other audiobookfinders running in local network.")
                .takes_value(false),
        )
        .arg(
            clap::Arg::with_name(ARG_KEEP_ALIVE)
                .short("k")
                .long(ARG_KEEP_ALIVE)
                .help(
                    "With keep alive process will continue even after search has been performed.\
                     This should be used and will be turned on automatically with net, webui\
                     browser option.",
                )
                .takes_value(false),
        )
        .arg(
            clap::Arg::with_name(ARG_BROWSER)
                .short("b")
                .long(ARG_BROWSER)
                .help("Shall browser not be openend automatically (only works with webui).")
                .takes_value(false),
        )
        .arg(
            clap::Arg::with_name(INPUT_FOLDERS)
                .help(
                    &[
                        "Sets multiple input folder(s) to be searched for audio files. (Max ",
                        &config::data::PATHS_MAX.to_string(),
                        " input folders will be used!)",
                    ]
                    .concat(),
                )
                .multiple(true)
                .required(false),
        )
        .get_matches();
    // tricky thing, but I really like that
    let all_pathes = if let Some(correct_input) = parse_args.values_of(INPUT_FOLDERS) {
        correct_input.collect()
    } else {
        vec!["."]
    };

    //
    // check argments if tui and net search is needed
    //
    let has_arg = |x: &str| parse_args.is_present(x);

    let has_tui = has_arg(ARG_TUI);
    let has_webui = has_arg(ARG_WEBUI);
    let has_net = has_arg(ARG_NET);
    let has_port = has_arg(ARG_BROWSER_PORT);
    let mut keep_alive = has_arg(ARG_KEEP_ALIVE);
    let open_browser = !has_arg(ARG_BROWSER);

    //
    // section for better user experience
    // todo: think it over
    if has_webui || has_net {
        keep_alive = true;
    }
    // not mutable
    let keep_alive = keep_alive;

    let web_port = {
        let web_default_string = config::net::WEB_PORT_DEFAULT.to_string();
        // when to write to console
        let has_to_write_console = !has_tui && has_webui;

        let parsed_value = parse_args
            .value_of(ARG_BROWSER_PORT)
            .unwrap_or_else(|| {
                if has_to_write_console && has_port {
                    println!(
                        "Port argument was bad, using default port {}!",
                        config::net::WEB_ADDR
                    );
                }
                &web_default_string
            })
            .parse::<u16>()
            .unwrap_or_else(|_| {
                if has_to_write_console && has_port {
                    println!(
                        "Invalid port input, using default port {}!",
                        &web_default_string
                    );
                }
                config::net::WEB_PORT_DEFAULT
            });
        if parsed_value < config::net::WEB_PORT_MIN || parsed_value > config::net::WEB_PORT_MAX {
            if has_to_write_console {
                let web_max_string = config::net::WEB_PORT_MAX.to_string();
                let web_min_string = config::net::WEB_PORT_MIN.to_string();
                println!(
                    "Port not in range {} .. {}, using default {}!",
                    &web_min_string, &web_max_string, &web_default_string
                );
            }
            config::net::WEB_PORT_DEFAULT
        } else {
            parsed_value
        }
    };

    // extended help for certain option combinations
    if !has_tui && has_webui && !open_browser {
        println!(
            "Open http://{}:{} to start!",
            config::net::WEB_ADDR,
            config::net::WEB_PORT_DEFAULT
        );
        println!("The webui needs to get the start signal from there");
    }

    // either one will have a ui, representing data and error messages
    let has_ui = has_tui || has_webui;

    // 1) convert to strings
    let unchecked_strings = all_pathes.iter().map(|s| s.to_string()).collect();
    (
        unchecked_strings,
        has_tui,
        has_webui,
        has_net,
        keep_alive,
        open_browser,
        web_port,
        has_ui,
    )
}
<commit_msg>[fix] command line wrong port output, if it was changed<commit_after>//! Command line modules: has one function which takes input parameters from commandline
//! and parses them.
use super::config;

static APP_TITLE: &str = concat!("The audiobook finder (", env!("CARGO_PKG_NAME"), ")");

static ARG_NET: &str = "net";
static ARG_TUI: &str = "tui";
static ARG_WEBUI: &str = "webui";
static ARG_KEEP_ALIVE: &str = "keep";
static ARG_BROWSER: &str = "browser";
static ARG_BROWSER_PORT: &str = "port";

static INPUT_FOLDERS: &str = "folders";

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const AUTHORS: &'static str = env!("CARGO_PKG_AUTHORS");
const HOMEPAGE: &'static str = env!("CARGO_PKG_HOMEPAGE");
const DESCRIPTION: &'static str = env!("CARGO_PKG_DESCRIPTION");

/// Get all start values which are passed from command line
///
/// Get all start values and returns the following tuple
/// ui_paths,
/// has_tui,
/// has_webui,
/// has_net,
/// keep_alive,
/// open_browser,
/// web_port,
/// has_ui,
pub fn get_start_values() -> (Vec<String>, bool, bool, bool, bool, bool, u16, bool) {
    let parse_args = clap::App::new(APP_TITLE)
        .version(VERSION)
        .author(AUTHORS)
        .about(DESCRIPTION)
        .long_about::<&str>(
            &[
                &DESCRIPTION,
                "\n\
                 It reads data from possibly multiple given path(s). Via local network it searches \
                 for other instances of the program, and will later exchange data securely.\n\
                 All information gathered will be used to find duplicates, versions of \
                 different quality, different tags for same content (spelling, \
                 incompleteness).\n\
                 For documentation see: ",
                &HOMEPAGE,
                "\n \
                 A program to learn, embrace, and love Rust! \n\
                 Have fun!",
            ]
            .concat(),
        )
        .arg(
            clap::Arg::with_name("config")
                .short("c")
                .long("config")
                .value_name("FILE")
                .help("Sets custom config file (not implemented yet)")
                .takes_value(true),
        )
        .arg(
            clap::Arg::with_name(ARG_TUI)
                .short("t")
                .long(ARG_TUI)
                .help("Run with TUI")
                .takes_value(false),
        )
        .arg(
            clap::Arg::with_name(ARG_WEBUI)
                .short("w")
                .long(ARG_WEBUI)
                .help("Run with-in a webui.")
                .takes_value(false),
        )
        .arg(
            clap::Arg::with_name(ARG_BROWSER_PORT)
                .short("p")
                .long(ARG_BROWSER_PORT)
                .help(
                    &vec![
                        "Define port for webui (only works with webui).\nDefault port is:"
                            .to_string(),
                        config::net::WEB_PORT_DEFAULT.to_string(),
                        " but please choose from 8080 to 8099)".to_string(),
                    ]
                    .join(""),
                )
                .takes_value(true),
        )
        .arg(
            clap::Arg::with_name(ARG_NET)
                .short("n")
                .long(ARG_NET)
                .help("With net search for other audiobookfinders running in local network.")
                .takes_value(false),
        )
        .arg(
            clap::Arg::with_name(ARG_KEEP_ALIVE)
                .short("k")
                .long(ARG_KEEP_ALIVE)
                .help(
                    "With keep alive process will continue even after search has been performed.\
                     This should be used and will be turned on automatically with net, webui\
                     browser option.",
                )
                .takes_value(false),
        )
        .arg(
            clap::Arg::with_name(ARG_BROWSER)
                .short("b")
                .long(ARG_BROWSER)
                .help("Shall browser not be openend automatically (only works with webui).")
                .takes_value(false),
        )
        .arg(
            clap::Arg::with_name(INPUT_FOLDERS)
                .help(
                    &[
                        "Sets multiple input folder(s) to be searched for audio files. (Max ",
                        &config::data::PATHS_MAX.to_string(),
                        " input folders will be used!)",
                    ]
                    .concat(),
                )
                .multiple(true)
                .required(false),
        )
        .get_matches();
    // tricky thing, but I really like that
    let all_pathes = if let Some(correct_input) = parse_args.values_of(INPUT_FOLDERS) {
        correct_input.collect()
    } else {
        vec!["."]
    };

    //
    // check argments if tui and net search is needed
    //
    let has_arg = |x: &str| parse_args.is_present(x);

    let has_tui = has_arg(ARG_TUI);
    let has_webui = has_arg(ARG_WEBUI);
    let has_net = has_arg(ARG_NET);
    let has_port = has_arg(ARG_BROWSER_PORT);
    let mut keep_alive = has_arg(ARG_KEEP_ALIVE);
    let open_browser = !has_arg(ARG_BROWSER);

    //
    // section for better user experience
    // todo: think it over
    if has_webui || has_net {
        keep_alive = true;
    }
    // not mutable
    let keep_alive = keep_alive;

    let web_port = {
        let web_default_string = config::net::WEB_PORT_DEFAULT.to_string();
        // when to write to console
        let has_to_write_console = !has_tui && has_webui;

        let parsed_value = parse_args
            .value_of(ARG_BROWSER_PORT)
            .unwrap_or_else(|| {
                if has_to_write_console && has_port {
                    println!(
                        "Port argument was bad, using default port {}!",
                        config::net::WEB_ADDR
                    );
                }
                &web_default_string
            })
            .parse::<u16>()
            .unwrap_or_else(|_| {
                if has_to_write_console && has_port {
                    println!(
                        "Invalid port input, using default port {}!",
                        &web_default_string
                    );
                }
                config::net::WEB_PORT_DEFAULT
            });
        if parsed_value < config::net::WEB_PORT_MIN || parsed_value > config::net::WEB_PORT_MAX {
            if has_to_write_console {
                let web_max_string = config::net::WEB_PORT_MAX.to_string();
                let web_min_string = config::net::WEB_PORT_MIN.to_string();
                println!(
                    "Port not in range {} .. {}, using default {}!",
                    &web_min_string, &web_max_string, &web_default_string
                );
            }
            config::net::WEB_PORT_DEFAULT
        } else {
            parsed_value
        }
    };

    // extended help for certain option combinations
    if !has_tui && has_webui && !open_browser {
        println!(
            "Open http://{}:{} to start!",
            config::net::WEB_ADDR,
            web_port
        );
        println!("The webui needs to get the start signal from there");
    }

    // either one will have a ui, representing data and error messages
    let has_ui = has_tui || has_webui;

    // 1) convert to strings
    let unchecked_strings = all_pathes.iter().map(|s| s.to_string()).collect();
    (
        unchecked_strings,
        has_tui,
        has_webui,
        has_net,
        keep_alive,
        open_browser,
        web_port,
        has_ui,
    )
}
<|endoftext|>


<commit_before>use crate::utils::printing;
use std::io::ErrorKind;
use std::process::Command;

mod documents;
mod exercises;
mod feeds;
mod git;
mod homebrew;
mod notes;
mod paths;
mod processes;

pub fn setup() {
    printing::heading("Reproducing my computer's configuration.");

    // Vim
    homebrew::install_package("vim", &|| {
        let vim_plugins_path = paths::vim_plugins();

        paths::create_dir(&vim_plugins_path, &|| {
            // Enhance defaults
            git::clone(&vim_plugins_path, "https://github.com/tpope/vim-sensible");

            // Enhance languages
            git::clone(&vim_plugins_path, "https://github.com/sheerun/vim-polyglot");

            // Enhance wildignore
            git::clone(&vim_plugins_path, "https://github.com/octref/RootIgnore");

            // Enhance static analysis
            git::clone(&vim_plugins_path, "https://github.com/dense-analysis/ale");
        });
    });

    // Git
    homebrew::install_package("git", &|| {});

    // Rust
    homebrew::install_package("rustup-init", &|| {
        let rustup_path_check = match Command::new("which").arg("rustup").output() {
            Ok(output) => output.stdout,
            Err(error) => panic!("There was a problem: {:?}", error),
        };

        if rustup_path_check.is_empty() {
            match Command::new("rustup-init").output() {
                Ok(_) => (),
                Err(error) => match error.kind() {
                    ErrorKind::NotFound => panic!("The rustup-init command is missing."),
                    other_error => panic!("There was a problem: {:?}", other_error),
                },
            }
        }
    });

    // JavaScript
    homebrew::install_package("node", &|| {});
}

pub fn upgrade() {
    printing::heading("Upgrading what's installed on my computer.");

    // Unix
    homebrew::upgrade_self();

    // Vim
    homebrew::upgrade_package("vim");
    git::pull_all(&paths::vim_plugins());

    // Git
    homebrew::upgrade_package("git");

    // Rust
    homebrew::upgrade_package("rustup-init");
    homebrew::upgrade_rust();

    // JavaScript
    homebrew::upgrade_package("node");
}

pub fn end() {
    printing::heading("Cleaning up my computer's state.");

    // Unix
    homebrew::clean_artifacts();

    // Vim
    processes::stop("vim");

    // Git
    git::check_all(&paths::repos());

    // Rust
    processes::stop("rls");

    // JavaScript
    processes::stop("node");
}

pub fn study() {
    // Feeds
    printing::heading("Follow feeds so that I'm aware of industry changes.");
    feeds::next();

    // Exercises
    printing::heading("Complete an exercise so that I gain experience solving problems.");
    exercises::next();

    // Documents
    printing::heading("Read a document so that I increase what I know.");
    documents::next();

    // Notes
    printing::heading("Write a note so that I solidify the most important things I've learned.");
    notes::next();
}
<commit_msg>Use vim 8 defaults instead of vim-sensible<commit_after>use crate::utils::printing;
use std::io::ErrorKind;
use std::process::Command;

mod documents;
mod exercises;
mod feeds;
mod git;
mod homebrew;
mod notes;
mod paths;
mod processes;

pub fn setup() {
    printing::heading("Reproducing my computer's configuration.");

    // Vim
    homebrew::install_package("vim", &|| {
        let vim_plugins_path = paths::vim_plugins();

        paths::create_dir(&vim_plugins_path, &|| {
            // Enhance languages
            git::clone(&vim_plugins_path, "https://github.com/sheerun/vim-polyglot");

            // Enhance wildignore
            git::clone(&vim_plugins_path, "https://github.com/octref/RootIgnore");

            // Enhance static analysis
            git::clone(&vim_plugins_path, "https://github.com/dense-analysis/ale");
        });
    });

    // Git
    homebrew::install_package("git", &|| {});

    // Rust
    homebrew::install_package("rustup-init", &|| {
        let rustup_path_check = match Command::new("which").arg("rustup").output() {
            Ok(output) => output.stdout,
            Err(error) => panic!("There was a problem: {:?}", error),
        };

        if rustup_path_check.is_empty() {
            match Command::new("rustup-init").output() {
                Ok(_) => (),
                Err(error) => match error.kind() {
                    ErrorKind::NotFound => panic!("The rustup-init command is missing."),
                    other_error => panic!("There was a problem: {:?}", other_error),
                },
            }
        }
    });

    // JavaScript
    homebrew::install_package("node", &|| {});
}

pub fn upgrade() {
    printing::heading("Upgrading what's installed on my computer.");

    // Unix
    homebrew::upgrade_self();

    // Vim
    homebrew::upgrade_package("vim");
    git::pull_all(&paths::vim_plugins());

    // Git
    homebrew::upgrade_package("git");

    // Rust
    homebrew::upgrade_package("rustup-init");
    homebrew::upgrade_rust();

    // JavaScript
    homebrew::upgrade_package("node");
}

pub fn end() {
    printing::heading("Cleaning up my computer's state.");

    // Unix
    homebrew::clean_artifacts();

    // Vim
    processes::stop("vim");

    // Git
    git::check_all(&paths::repos());

    // Rust
    processes::stop("rls");

    // JavaScript
    processes::stop("node");
}

pub fn study() {
    // Feeds
    printing::heading("Follow feeds so that I'm aware of industry changes.");
    feeds::next();

    // Exercises
    printing::heading("Complete an exercise so that I gain experience solving problems.");
    exercises::next();

    // Documents
    printing::heading("Read a document so that I increase what I know.");
    documents::next();

    // Notes
    printing::heading("Write a note so that I solidify the most important things I've learned.");
    notes::next();
}
<|endoftext|>