<commit_before>use std::cmp::{min, max};
use std::thread;
use std::sync::Arc;
use std::sync::mpsc::{channel, Sender, Receiver};
use basetypes::*;
use moves::*;
use tt::*;
use position::Position;
use search::threading::{Command, Report, serve_simple};
use search::MAX_DEPTH;

/// The half-with of the initial aspiration window.
pub const INITIAL_ASPIRATION_WINDOW: Value = 17; // 16;

/// The `SearchExecutor` trait is used to execute consecutive searches
/// in different starting positions.
pub trait SearchExecutor {
    /// Creates a new instance.
    fn new(tt: Arc<Tt>) -> Self;

    /// Starts a new search.
    ///
    /// * `search_id`: a number identifying the new search;
    ///
    /// * `position`: the root position;
    ///
    /// * `depth`: the requested search depth;
    ///
    /// * `lower_bound`: the lower bound for the new search;
    ///
    /// * `upper_bound`: the upper bound for the new search;
    ///
    /// * `value`: the evaluation of the root position so far, or
    ///   `VALUE_UNKNOWN` if not available.
    ///
    /// * `searchmoves`: restricts the analysis to the supplied list
    ///   of moves only (no restrictions if the suppied list is
    ///   empty);
    ///
    /// * `variation_count`: specifies how many best lines of play to
    ///   calculate (the first move in each line will be different).
    ///
    /// After calling `start_search`, `wait_for_report` must be called
    /// periodically until the returned report indicates that the
    /// search is done. A new search must not be started until the
    /// previous search is done.
    fn start_search(&mut self,
                    search_id: usize,
                    position: Position,
                    depth: u8,
                    lower_bound: Value,
                    upper_bound: Value,
                    value: Value,
                    searchmoves: Vec<Move>,
                    variation_count: usize);

    /// Blocks and waits for a search report.
    fn wait_for_report(&mut self) -> Report;

    /// Requests the termination of the current search.
    ///
    /// After calling `terminate`, `wait_for_report` must continue to
    /// be called periodically until the returned report indicates
    /// that the search is done.
    fn terminate_search(&mut self);
}

/// Executes searches to a fixed depth.
pub struct SimpleSearcher {
    thread: Option<thread::JoinHandle<()>>,
    thread_commands: Sender<Command>,
    thread_reports: Receiver<Report>,
}

impl SearchExecutor for SimpleSearcher {
    fn new(tt: Arc<Tt>) -> SimpleSearcher {
        let (commands_tx, commands_rx) = channel();
        let (reports_tx, reports_rx) = channel();
        SimpleSearcher {
            thread: Some(thread::spawn(move || {
                serve_simple(tt, commands_rx, reports_tx);
            })),
            thread_commands: commands_tx,
            thread_reports: reports_rx,
        }
    }

    #[allow(unused_variables)]
    fn start_search(&mut self,
                    search_id: usize,
                    position: Position,
                    depth: u8,
                    lower_bound: Value,
                    upper_bound: Value,
                    value: Value,
                    searchmoves: Vec<Move>,
                    variation_count: usize) {
        debug_assert!(depth <= MAX_DEPTH);
        debug_assert!(lower_bound < upper_bound);
        debug_assert!(lower_bound != VALUE_UNKNOWN);
        debug_assert!(searchmoves.is_empty());
        self.thread_commands
            .send(Command::Search {
                search_id: search_id,
                position: position,
                depth: depth,
                lower_bound: lower_bound,
                upper_bound: upper_bound,
            })
            .unwrap();
    }

    fn wait_for_report(&mut self) -> Report {
        self.thread_reports.recv().unwrap()
    }

    fn terminate_search(&mut self) {
        self.thread_commands.send(Command::Stop).unwrap();
    }
}

impl Drop for SimpleSearcher {
    fn drop(&mut self) {
        self.thread_commands.send(Command::Exit).unwrap();
        self.thread.take().unwrap().join().unwrap();
    }
}

/// Executes searches to a fixed depth with intelligently reduced
/// bounds (aspiration window).
///
/// Aspiration windows are a way to reduce the search space in the
/// search. The way it works is that we get the value from the last
/// search iteration (the `value` parameter to the `start_search`
/// method), calculate a window around it, and use this as alpha-beta
/// bounds for the search. Because the window is narrower, more beta
/// cutoffs are achieved, and the search takes a shorter time. The
/// drawback is that if the true score is outside this window, then a
/// costly re-search must be made. But then most probably the
/// re-search will be much faster, because many positions will be
/// remembered from the TT.
pub struct AspirationSearcher {
    search_id: usize,
    position: Position,
    depth: u8,
    lower_bound: Value,
    upper_bound: Value,
    value: Value,

    search_is_terminated: bool,

    /// The number of positions searched during previous searches.
    searched_nodes: NodeCount,

    /// The aspiration window will be widened by this value if the
    /// aspirated search fails. We use `isize` to avoid overflows.
    delta: isize,

    /// The lower bound of the aspiration window.
    alpha: Value,

    /// The upper bound of the aspiration window.
    beta: Value,

    /// `AspirationSearcher` will hand over the real work to
    /// `SimpleSearcher`.
    simple_searcher: SimpleSearcher,
}

impl AspirationSearcher {
    /// A helper mehtod. It commands the slave thread to run a new search.
    fn start_aspirated_search(&mut self) {
        self.simple_searcher.start_search(0,
                                          self.position.clone(),
                                          self.depth,
                                          self.alpha,
                                          self.beta,
                                          self.value,
                                          vec![],
                                          1);
    }

    /// A helper method. It increases `self.delta` exponentially.
    fn increase_delta(&mut self) {
        self.delta += 3 * self.delta / 8;
        if self.delta > 1500 {
            self.delta = 1_000_000;
        }
    }

    /// A helper method. It widens the aspiration window if necessary.
    fn widen_aspiration_window(&mut self) -> bool {
        let v = self.value as isize;
        if self.value <= self.alpha && self.lower_bound < self.alpha {
            // Set smaller `self.alpha`.
            self.alpha = max(v - self.delta, self.lower_bound as isize) as Value;
            self.increase_delta();
            return true;
        } else if self.value >= self.beta && self.upper_bound > self.beta {
            // Set bigger `self.beta`.
            self.beta = min(v + self.delta, self.upper_bound as isize) as Value;
            self.increase_delta();
            return true;
        }
        false
    }
}

impl SearchExecutor for AspirationSearcher {
    fn new(tt: Arc<Tt>) -> AspirationSearcher {
        AspirationSearcher {
            search_id: 0,
            position: Position::from_fen(::STARTING_POSITION).ok().unwrap(),
            depth: 0,
            lower_bound: VALUE_MIN,
            upper_bound: VALUE_MAX,
            value: VALUE_UNKNOWN,
            search_is_terminated: false,
            searched_nodes: 0,
            delta: INITIAL_ASPIRATION_WINDOW as isize,
            alpha: VALUE_MIN,
            beta: VALUE_MAX,
            simple_searcher: SimpleSearcher::new(tt),
        }
    }

    fn start_search(&mut self,
                    search_id: usize,
                    position: Position,
                    depth: u8,
                    lower_bound: Value,
                    upper_bound: Value,
                    value: Value,
                    searchmoves: Vec<Move>,
                    variation_count: usize) {
        debug_assert!(depth <= MAX_DEPTH);
        debug_assert!(lower_bound < upper_bound);
        debug_assert!(lower_bound != VALUE_UNKNOWN);
        debug_assert!(variation_count > 0);
        self.search_id = search_id;
        self.position = position;
        self.depth = depth;
        self.lower_bound = lower_bound;
        self.upper_bound = upper_bound;
        self.value = value;
        self.search_is_terminated = false;
        self.searched_nodes = 0;
        self.delta = INITIAL_ASPIRATION_WINDOW as isize;

        // Set the initial aspiration window (`self.alpha`, `self.beta`).
        let (a, b) = if value == VALUE_UNKNOWN || value < lower_bound || value > upper_bound {
            (lower_bound, upper_bound)
        } else {
            let v = value as isize;
            (max(v - self.delta, lower_bound as isize) as Value,
             min(v + self.delta, upper_bound as isize) as Value)
        };
        self.alpha = a;
        self.beta = b;

        self.start_aspirated_search();
    }

    fn wait_for_report(&mut self) -> Report {
        loop {
            let Report { searched_nodes, depth, value, done, .. } = self.simple_searcher
                                                                        .wait_for_report();
            let searched_nodes = self.searched_nodes + searched_nodes;
            let depth = if done && !self.search_is_terminated {
                self.searched_nodes = searched_nodes;
                self.value = value;
                if self.widen_aspiration_window() {
                    // `value` is outside the aspiration window and we
                    // must start another search.
                    self.start_aspirated_search();
                    continue;
                }
                depth
            } else {
                0
            };

            return Report {
                search_id: self.search_id,
                searched_nodes: searched_nodes,
                depth: depth,
                value: self.value,
                best_moves: vec![],
                done: done,
            };
        }
    }

    fn terminate_search(&mut self) {
        self.search_is_terminated = true;
        self.simple_searcher.terminate_search();
    }
}
<commit_msg>add docs<commit_after>//! Implements iterative deepening, aspiration windows, multi-PV.
//!
//! Iterative deepening works as follows: the program starts with a
//! one ply search, then increments the search depth and does another
//! search. This process is repeated until the time allocated for the
//! search is exhausted. In case of an unfinished search, the program
//! always has the option to fall back to the move selected in the
//! last iteration of the search.
//!
//! Aspiration windows are a way to reduce the search space in the
//! search. The way it works is that we get the value from the last
//! search iteration, calculate a window around it, and use this as
//! alpha-beta bounds for the next search. Because the window is
//! narrower, more beta cutoffs are achieved, and the search takes a
//! shorter time. The drawback is that if the true score is outside
//! this window, then a costly re-search must be made. But then most
//! probably the re-search will be much faster, because many positions
//! will be remembered from the transposition table.
//!
//! In multi-PV mode the engine calculates and sends to the GUI
//! several principal variations (PV), each one starting with a
//! different first move.

use std::cmp::{min, max};
use std::thread;
use std::sync::Arc;
use std::sync::mpsc::{channel, Sender, Receiver};
use basetypes::*;
use moves::*;
use tt::*;
use position::Position;
use search::threading::{Command, Report, serve_simple};
use search::MAX_DEPTH;

/// The half-with of the initial aspiration window.
pub const INITIAL_ASPIRATION_WINDOW: Value = 17; // 16;

/// The `SearchExecutor` trait is used to execute consecutive searches
/// in different starting positions.
pub trait SearchExecutor {
    /// Creates a new instance.
    fn new(tt: Arc<Tt>) -> Self;

    /// Starts a new search.
    ///
    /// * `search_id`: a number identifying the new search;
    ///
    /// * `position`: the root position;
    ///
    /// * `depth`: the requested search depth;
    ///
    /// * `lower_bound`: the lower bound for the new search;
    ///
    /// * `upper_bound`: the upper bound for the new search;
    ///
    /// * `value`: the evaluation of the root position so far, or
    ///   `VALUE_UNKNOWN` if not available.
    ///
    /// * `searchmoves`: restricts the analysis to the supplied list
    ///   of moves only (no restrictions if the suppied list is
    ///   empty);
    ///
    /// * `variation_count`: specifies how many best lines of play to
    ///   calculate (the first move in each line will be different).
    ///
    /// After calling `start_search`, `wait_for_report` must be called
    /// periodically until the returned report indicates that the
    /// search is done. A new search must not be started until the
    /// previous search is done.
    fn start_search(&mut self,
                    search_id: usize,
                    position: Position,
                    depth: u8,
                    lower_bound: Value,
                    upper_bound: Value,
                    value: Value,
                    searchmoves: Vec<Move>,
                    variation_count: usize);

    /// Blocks and waits for a search report.
    fn wait_for_report(&mut self) -> Report;

    /// Requests the termination of the current search.
    ///
    /// After calling `terminate`, `wait_for_report` must continue to
    /// be called periodically until the returned report indicates
    /// that the search is done.
    fn terminate_search(&mut self);
}

/// Executes bare alpha-beta searches to a fixed depth.
pub struct SimpleSearcher {
    thread: Option<thread::JoinHandle<()>>,
    thread_commands: Sender<Command>,
    thread_reports: Receiver<Report>,
}

impl SearchExecutor for SimpleSearcher {
    fn new(tt: Arc<Tt>) -> SimpleSearcher {
        let (commands_tx, commands_rx) = channel();
        let (reports_tx, reports_rx) = channel();
        SimpleSearcher {
            thread: Some(thread::spawn(move || {
                serve_simple(tt, commands_rx, reports_tx);
            })),
            thread_commands: commands_tx,
            thread_reports: reports_rx,
        }
    }

    #[allow(unused_variables)]
    fn start_search(&mut self,
                    search_id: usize,
                    position: Position,
                    depth: u8,
                    lower_bound: Value,
                    upper_bound: Value,
                    value: Value,
                    searchmoves: Vec<Move>,
                    variation_count: usize) {
        debug_assert!(depth <= MAX_DEPTH);
        debug_assert!(lower_bound < upper_bound);
        debug_assert!(lower_bound != VALUE_UNKNOWN);
        debug_assert!(searchmoves.is_empty());
        self.thread_commands
            .send(Command::Search {
                search_id: search_id,
                position: position,
                depth: depth,
                lower_bound: lower_bound,
                upper_bound: upper_bound,
            })
            .unwrap();
    }

    fn wait_for_report(&mut self) -> Report {
        self.thread_reports.recv().unwrap()
    }

    fn terminate_search(&mut self) {
        self.thread_commands.send(Command::Stop).unwrap();
    }
}

impl Drop for SimpleSearcher {
    fn drop(&mut self) {
        self.thread_commands.send(Command::Exit).unwrap();
        self.thread.take().unwrap().join().unwrap();
    }
}

/// Executes multi-PV searches to a fixed depth.
pub struct MultipvSearcher;

/// Executes multi-PV searches to a fixed depth with aspiration.
pub struct AspirationSearcher {
    search_id: usize,
    position: Position,
    depth: u8,
    lower_bound: Value,
    upper_bound: Value,
    value: Value,

    search_is_terminated: bool,

    /// The number of positions searched during previous searches.
    searched_nodes: NodeCount,

    /// The aspiration window will be widened by this value if the
    /// aspirated search fails. We use `isize` to avoid overflows.
    delta: isize,

    /// The lower bound of the aspiration window.
    alpha: Value,

    /// The upper bound of the aspiration window.
    beta: Value,

    /// `AspirationSearcher` will hand over the real work to
    /// `SimpleSearcher`.
    simple_searcher: SimpleSearcher,
}

impl AspirationSearcher {
    /// A helper mehtod. It commands the slave thread to run a new search.
    fn start_aspirated_search(&mut self) {
        self.simple_searcher.start_search(0,
                                          self.position.clone(),
                                          self.depth,
                                          self.alpha,
                                          self.beta,
                                          self.value,
                                          vec![],
                                          1);
    }

    /// A helper method. It increases `self.delta` exponentially.
    fn increase_delta(&mut self) {
        self.delta += 3 * self.delta / 8;
        if self.delta > 1500 {
            self.delta = 1_000_000;
        }
    }

    /// A helper method. It widens the aspiration window if necessary.
    fn widen_aspiration_window(&mut self) -> bool {
        let v = self.value as isize;
        if self.value <= self.alpha && self.lower_bound < self.alpha {
            // Set smaller `self.alpha`.
            self.alpha = max(v - self.delta, self.lower_bound as isize) as Value;
            self.increase_delta();
            return true;
        } else if self.value >= self.beta && self.upper_bound > self.beta {
            // Set bigger `self.beta`.
            self.beta = min(v + self.delta, self.upper_bound as isize) as Value;
            self.increase_delta();
            return true;
        }
        false
    }
}

impl SearchExecutor for AspirationSearcher {
    fn new(tt: Arc<Tt>) -> AspirationSearcher {
        AspirationSearcher {
            search_id: 0,
            position: Position::from_fen(::STARTING_POSITION).ok().unwrap(),
            depth: 0,
            lower_bound: VALUE_MIN,
            upper_bound: VALUE_MAX,
            value: VALUE_UNKNOWN,
            search_is_terminated: false,
            searched_nodes: 0,
            delta: INITIAL_ASPIRATION_WINDOW as isize,
            alpha: VALUE_MIN,
            beta: VALUE_MAX,
            simple_searcher: SimpleSearcher::new(tt),
        }
    }

    fn start_search(&mut self,
                    search_id: usize,
                    position: Position,
                    depth: u8,
                    lower_bound: Value,
                    upper_bound: Value,
                    value: Value,
                    searchmoves: Vec<Move>,
                    variation_count: usize) {
        debug_assert!(depth <= MAX_DEPTH);
        debug_assert!(lower_bound < upper_bound);
        debug_assert!(lower_bound != VALUE_UNKNOWN);
        debug_assert!(variation_count > 0);
        self.search_id = search_id;
        self.position = position;
        self.depth = depth;
        self.lower_bound = lower_bound;
        self.upper_bound = upper_bound;
        self.value = value;
        self.search_is_terminated = false;
        self.searched_nodes = 0;
        self.delta = INITIAL_ASPIRATION_WINDOW as isize;

        // Set the initial aspiration window (`self.alpha`, `self.beta`).
        let (a, b) = if value == VALUE_UNKNOWN || value < lower_bound || value > upper_bound {
            (lower_bound, upper_bound)
        } else {
            let v = value as isize;
            (max(v - self.delta, lower_bound as isize) as Value,
             min(v + self.delta, upper_bound as isize) as Value)
        };
        self.alpha = a;
        self.beta = b;

        self.start_aspirated_search();
    }

    fn wait_for_report(&mut self) -> Report {
        loop {
            let Report { searched_nodes, depth, value, done, .. } = self.simple_searcher
                                                                        .wait_for_report();
            let searched_nodes = self.searched_nodes + searched_nodes;
            let depth = if done && !self.search_is_terminated {
                self.searched_nodes = searched_nodes;
                self.value = value;
                if self.widen_aspiration_window() {
                    // `value` is outside the aspiration window and we
                    // must start another search.
                    self.start_aspirated_search();
                    continue;
                }
                depth
            } else {
                0
            };

            return Report {
                search_id: self.search_id,
                searched_nodes: searched_nodes,
                depth: depth,
                value: self.value,
                best_moves: vec![],
                done: done,
            };
        }
    }

    fn terminate_search(&mut self) {
        self.search_is_terminated = true;
        self.simple_searcher.terminate_search();
    }
}

/// Executes deepeing multi-PV searches with aspiration.
pub struct DeepeningSearcher;
<|endoftext|>


<commit_before>//
// Copyright (c) Pirmin Kalberer. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//

use crate::core::config::ApplicationCfg;
use crate::mvt_service::MvtService;
use crate::runtime_config::{config_from_args, service_from_args};
use crate::static_files::StaticFiles;
use actix_cors::Cors;
use actix_files as fs;
use actix_web::dev::BodyEncoding;
use actix_web::http::{header, ContentEncoding};
use actix_web::middleware::Compress;
use actix_web::{guard, middleware, web, App, HttpRequest, HttpResponse, HttpServer, Result};
use clap::ArgMatches;
use log::Level;
use num_cpus;
use open;
use std::collections::HashMap;
use std::convert::Infallible;
use std::str;
use std::str::FromStr;

static DINO: &'static str = "             xxxxxxxxx
        xxxxxxxxxxxxxxxxxxxxxxxx
      xxxxxxxxxxxxxxxxxxxxxxxxxxxx
     xxxxxxxxxxxxxxxxxxxxxxxxx xxxx
     xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
   xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   xxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   xxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx     xxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx      x
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxx    xxxxxxxxxxx
xxxxxxxxxxxxxx                   xxxxxx
xxxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxxx
xxxxxxxxx
xxxxxxx
xxxxxx
xxxxxxx";

async fn mvt_metadata(service: web::Data<MvtService>) -> Result<HttpResponse> {
    let json = service.get_mvt_metadata().unwrap();
    Ok(HttpResponse::Ok().json(json))
}

/// Font list for Maputnik
async fn fontstacks() -> Result<HttpResponse> {
    Ok(HttpResponse::Ok().json(["Roboto Medium", "Roboto Regular"]))
}

// Include method fonts() which returns HashMap with embedded font files
include!(concat!(env!("OUT_DIR"), "/fonts.rs"));

/// Fonts for Maputnik
/// Example: /fonts/Open%20Sans%20Regular,Arial%20Unicode%20MS%20Regular/0-255.pbf
async fn fonts_pbf(params: web::Path<(String, String)>) -> Result<HttpResponse> {
    let fontpbfs = fonts();
    let fontlist = &params.as_ref().0;
    let range = &params.as_ref().1;
    let mut fonts = fontlist.split(",").collect::<Vec<_>>();
    fonts.push("Roboto Regular"); // Fallback
    let mut resp = HttpResponse::NotFound().finish();
    for font in fonts {
        let key = format!("fonts/{}/{}.pbf", font.replace("%20", " "), range);
        debug!("Font lookup: {}", key);
        if let Some(pbf) = fontpbfs.get(&key as &str) {
            resp = HttpResponse::Ok()
                .content_type("application/x-protobuf")
                // data is already gzip compressed
                .encoding(ContentEncoding::Identity)
                .header(header::CONTENT_ENCODING, "gzip")
                .body(*pbf); // TODO: chunked response
            break;
        }
    }
    Ok(resp)
}

fn req_baseurl(req: &HttpRequest) -> String {
    let conninfo = req.connection_info();
    format!("{}://{}", conninfo.scheme(), conninfo.host())
}

async fn tileset_tilejson(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let url = req_baseurl(&req);
    let json =
        web::block::<_, _, Infallible>(move || Ok(service.get_tilejson(&url, &tileset).unwrap()))
            .await
            .unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tileset_style_json(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let json = service.get_stylejson(&req_baseurl(&req), &tileset).unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tileset_metadata_json(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
) -> Result<HttpResponse> {
    let json =
        web::block::<_, _, Infallible>(move || Ok(service.get_mbtiles_metadata(&tileset).unwrap()))
            .await
            .unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tile_pbf(
    config: web::Data<ApplicationCfg>,
    service: web::Data<MvtService>,
    params: web::Path<(String, u8, u32, u32)>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let params = params.into_inner();
    let tileset = params.0;
    let z = params.1;
    let x = params.2;
    let y = params.3;
    let gzip = req
        .headers()
        .get(header::ACCEPT_ENCODING)
        .and_then(|headerval| {
            headerval
                .to_str()
                .ok()
                .and_then(|headerstr| Some(headerstr.contains("gzip")))
        })
        .unwrap_or(false);
    let tile = web::block::<_, _, Infallible>(move || {
        Ok(service.tile_cached(&tileset, x, y, z, gzip, None))
    })
    .await;

    let resp = match tile {
        Ok(Some(tile)) => {
            let cache_max_age = config.webserver.cache_control_max_age.unwrap_or(300);
            HttpResponse::Ok()
                .content_type("application/x-protobuf")
                .if_true(gzip, |r| {
                    // data is already gzip compressed
                    r.encoding(ContentEncoding::Identity)
                        .header(header::CONTENT_ENCODING, "gzip");
                })
                .header(header::CACHE_CONTROL, format!("max-age={}", cache_max_age))
                .body(tile) // TODO: chunked response
        }
        Ok(None) => HttpResponse::NoContent().finish(),
        Err(e) => {
            error!("{}", e);
            HttpResponse::InternalServerError().finish()
        }
    };
    Ok(resp)
}

lazy_static! {
    static ref STATIC_FILES: StaticFiles = StaticFiles::init();
}

async fn static_file_handler(req: HttpRequest) -> Result<HttpResponse> {
    let key = req.path()[1..].to_string();
    let resp = if let Some(ref content) = STATIC_FILES.content(None, key) {
        HttpResponse::Ok()
            .header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*") // TOOD: use Actix middleware
            .content_type(content.1)
            .body(content.0) // TODO: chunked response
    } else {
        HttpResponse::NotFound().finish()
    };
    Ok(resp)
}

#[derive(Deserialize)]
struct DrilldownParams {
    minzoom: Option<u8>,
    maxzoom: Option<u8>,
    points: String, //x1,y1,x2,y2,..
}

async fn drilldown_handler(
    service: web::Data<MvtService>,
    params: web::Query<DrilldownParams>,
) -> Result<HttpResponse> {
    let tileset = None; // all tilesets
    let progress = false;
    let points: Vec<f64> = params
        .points
        .split(",")
        .map(|v| {
            v.parse()
                .expect("Error parsing 'point' as pair of float values")
            //FIXME: map_err(|_| error::ErrorInternalServerError("...")
        })
        .collect();
    let stats = service.drilldown(tileset, params.minzoom, params.maxzoom, points, progress);
    let json = stats.as_json().unwrap();
    Ok(HttpResponse::Ok().json(json))
}

#[actix_web::main]
pub async fn webserver(args: ArgMatches<'static>) -> std::io::Result<()> {
    let config = config_from_args(&args);
    let host = config
        .webserver
        .bind
        .clone()
        .unwrap_or("127.0.0.1".to_string());
    let port = config.webserver.port.unwrap_or(6767);
    let bind_addr = format!("{}:{}", host, port);
    let workers = config.webserver.threads.unwrap_or(num_cpus::get() as u8);
    let mvt_viewer = config.service.mvt.viewer;
    let openbrowser =
        bool::from_str(args.value_of("openbrowser").unwrap_or("true")).unwrap_or(false);
    let static_dirs = config.webserver.static_.clone();

    let mut service = service_from_args(&config, &args);
    let service = web::block::<_, _, Infallible>(|| {
        service.prepare_feature_queries();
        service.init_cache();
        Ok(service)
    })
    .await
    .unwrap();

    let server = HttpServer::new(move || {
        let mut app = App::new()
            .data(config.clone())
            .data(service.clone())
            .wrap(middleware::Logger::new("%r %s %b %Dms %a"))
            .wrap(Compress::default())
            .wrap(
                Cors::new()
                    .send_wildcard()
                    .allowed_methods(vec!["GET"])
                    .finish(),
            )
            .service(
                web::resource("/index.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(mvt_metadata),
                ),
            )
            .service(
                web::resource("/fontstacks.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fontstacks),
                ),
            )
            .service(
                web::resource("/fonts.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fontstacks),
                ),
            )
            .service(
                web::resource("/fonts/{fonts}/{range}.pbf").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fonts_pbf),
                ),
            );
        for static_dir in &static_dirs {
            let dir = &static_dir.dir;
            if std::path::Path::new(dir).is_dir() {
                info!("Serving static files from directory '{}'", dir);
                app = app.service(fs::Files::new(&static_dir.path, dir));
            } else {
                warn!("Static file directory '{}' not found", dir);
            }
        }
        app = app
            .service(
                web::resource("/{tileset}.style.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_style_json),
                ),
            )
            .service(
                web::resource("/{tileset}/metadata.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_metadata_json),
                ),
            )
            .service(
                web::resource("/{tileset}.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_tilejson),
                ),
            )
            .service(
                web::resource("/{tileset}/{z}/{x}/{y}.pbf").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tile_pbf),
                ),
            );
        if mvt_viewer {
            app = app.service(
                web::resource("/drilldown").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(drilldown_handler),
                ),
            );
            app = app.default_service(web::to(static_file_handler));
        }
        app
    })
    .workers(workers as usize)
    .bind(&bind_addr)
    .expect("Can not start server on given IP/Port")
    .shutdown_timeout(3) // default: 30s
    .run();

    if log_enabled!(Level::Info) {
        println!("{}", DINO);
    }

    if openbrowser && mvt_viewer {
        let _res = open::that(format!("http://{}:{}", &host, port));
    }

    server.await
}
<commit_msg>Fix Actix deprecation warning<commit_after>//
// Copyright (c) Pirmin Kalberer. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//

use crate::core::config::ApplicationCfg;
use crate::mvt_service::MvtService;
use crate::runtime_config::{config_from_args, service_from_args};
use crate::static_files::StaticFiles;
use actix_cors::Cors;
use actix_files as fs;
use actix_web::dev::BodyEncoding;
use actix_web::http::{header, ContentEncoding};
use actix_web::middleware::Compress;
use actix_web::{guard, middleware, web, App, HttpRequest, HttpResponse, HttpServer, Result};
use clap::ArgMatches;
use log::Level;
use num_cpus;
use open;
use std::collections::HashMap;
use std::convert::Infallible;
use std::str;
use std::str::FromStr;

static DINO: &'static str = "             xxxxxxxxx
        xxxxxxxxxxxxxxxxxxxxxxxx
      xxxxxxxxxxxxxxxxxxxxxxxxxxxx
     xxxxxxxxxxxxxxxxxxxxxxxxx xxxx
     xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
   xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   xxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   xxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx     xxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx      x
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxx    xxxxxxxxxxx
xxxxxxxxxxxxxx                   xxxxxx
xxxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxxx
xxxxxxxxx
xxxxxxx
xxxxxx
xxxxxxx";

async fn mvt_metadata(service: web::Data<MvtService>) -> Result<HttpResponse> {
    let json = service.get_mvt_metadata().unwrap();
    Ok(HttpResponse::Ok().json(json))
}

/// Font list for Maputnik
async fn fontstacks() -> Result<HttpResponse> {
    Ok(HttpResponse::Ok().json(["Roboto Medium", "Roboto Regular"]))
}

// Include method fonts() which returns HashMap with embedded font files
include!(concat!(env!("OUT_DIR"), "/fonts.rs"));

/// Fonts for Maputnik
/// Example: /fonts/Open%20Sans%20Regular,Arial%20Unicode%20MS%20Regular/0-255.pbf
async fn fonts_pbf(params: web::Path<(String, String)>) -> Result<HttpResponse> {
    let fontpbfs = fonts();
    let fontlist = &params.as_ref().0;
    let range = &params.as_ref().1;
    let mut fonts = fontlist.split(",").collect::<Vec<_>>();
    fonts.push("Roboto Regular"); // Fallback
    let mut resp = HttpResponse::NotFound().finish();
    for font in fonts {
        let key = format!("fonts/{}/{}.pbf", font.replace("%20", " "), range);
        debug!("Font lookup: {}", key);
        if let Some(pbf) = fontpbfs.get(&key as &str) {
            resp = HttpResponse::Ok()
                .content_type("application/x-protobuf")
                // data is already gzip compressed
                .encoding(ContentEncoding::Identity)
                .header(header::CONTENT_ENCODING, "gzip")
                .body(*pbf); // TODO: chunked response
            break;
        }
    }
    Ok(resp)
}

fn req_baseurl(req: &HttpRequest) -> String {
    let conninfo = req.connection_info();
    format!("{}://{}", conninfo.scheme(), conninfo.host())
}

async fn tileset_tilejson(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let url = req_baseurl(&req);
    let json =
        web::block::<_, _, Infallible>(move || Ok(service.get_tilejson(&url, &tileset).unwrap()))
            .await
            .unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tileset_style_json(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let json = service.get_stylejson(&req_baseurl(&req), &tileset).unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tileset_metadata_json(
    service: web::Data<MvtService>,
    tileset: web::Path<String>,
) -> Result<HttpResponse> {
    let json =
        web::block::<_, _, Infallible>(move || Ok(service.get_mbtiles_metadata(&tileset).unwrap()))
            .await
            .unwrap();
    Ok(HttpResponse::Ok().json(json))
}

async fn tile_pbf(
    config: web::Data<ApplicationCfg>,
    service: web::Data<MvtService>,
    params: web::Path<(String, u8, u32, u32)>,
    req: HttpRequest,
) -> Result<HttpResponse> {
    let params = params.into_inner();
    let tileset = params.0;
    let z = params.1;
    let x = params.2;
    let y = params.3;
    let gzip = req
        .headers()
        .get(header::ACCEPT_ENCODING)
        .and_then(|headerval| {
            headerval
                .to_str()
                .ok()
                .and_then(|headerstr| Some(headerstr.contains("gzip")))
        })
        .unwrap_or(false);
    let tile = web::block::<_, _, Infallible>(move || {
        Ok(service.tile_cached(&tileset, x, y, z, gzip, None))
    })
    .await;

    let resp = match tile {
        Ok(Some(tile)) => {
            let mut r = HttpResponse::Ok();
            r.content_type("application/x-protobuf");
            if gzip {
                // data is already gzip compressed
                r.encoding(ContentEncoding::Identity)
                    .header(header::CONTENT_ENCODING, "gzip");
            }
            let cache_max_age = config.webserver.cache_control_max_age.unwrap_or(300);
            r.header(header::CACHE_CONTROL, format!("max-age={}", cache_max_age));
            r.body(tile) // TODO: chunked response
        }
        Ok(None) => HttpResponse::NoContent().finish(),
        Err(e) => {
            error!("{}", e);
            HttpResponse::InternalServerError().finish()
        }
    };
    Ok(resp)
}

lazy_static! {
    static ref STATIC_FILES: StaticFiles = StaticFiles::init();
}

async fn static_file_handler(req: HttpRequest) -> Result<HttpResponse> {
    let key = req.path()[1..].to_string();
    let resp = if let Some(ref content) = STATIC_FILES.content(None, key) {
        HttpResponse::Ok()
            .header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*") // TOOD: use Actix middleware
            .content_type(content.1)
            .body(content.0) // TODO: chunked response
    } else {
        HttpResponse::NotFound().finish()
    };
    Ok(resp)
}

#[derive(Deserialize)]
struct DrilldownParams {
    minzoom: Option<u8>,
    maxzoom: Option<u8>,
    points: String, //x1,y1,x2,y2,..
}

async fn drilldown_handler(
    service: web::Data<MvtService>,
    params: web::Query<DrilldownParams>,
) -> Result<HttpResponse> {
    let tileset = None; // all tilesets
    let progress = false;
    let points: Vec<f64> = params
        .points
        .split(",")
        .map(|v| {
            v.parse()
                .expect("Error parsing 'point' as pair of float values")
            //FIXME: map_err(|_| error::ErrorInternalServerError("...")
        })
        .collect();
    let stats = service.drilldown(tileset, params.minzoom, params.maxzoom, points, progress);
    let json = stats.as_json().unwrap();
    Ok(HttpResponse::Ok().json(json))
}

#[actix_web::main]
pub async fn webserver(args: ArgMatches<'static>) -> std::io::Result<()> {
    let config = config_from_args(&args);
    let host = config
        .webserver
        .bind
        .clone()
        .unwrap_or("127.0.0.1".to_string());
    let port = config.webserver.port.unwrap_or(6767);
    let bind_addr = format!("{}:{}", host, port);
    let workers = config.webserver.threads.unwrap_or(num_cpus::get() as u8);
    let mvt_viewer = config.service.mvt.viewer;
    let openbrowser =
        bool::from_str(args.value_of("openbrowser").unwrap_or("true")).unwrap_or(false);
    let static_dirs = config.webserver.static_.clone();

    let mut service = service_from_args(&config, &args);
    let service = web::block::<_, _, Infallible>(|| {
        service.prepare_feature_queries();
        service.init_cache();
        Ok(service)
    })
    .await
    .unwrap();

    let server = HttpServer::new(move || {
        let mut app = App::new()
            .data(config.clone())
            .data(service.clone())
            .wrap(middleware::Logger::new("%r %s %b %Dms %a"))
            .wrap(Compress::default())
            .wrap(
                Cors::new()
                    .send_wildcard()
                    .allowed_methods(vec!["GET"])
                    .finish(),
            )
            .service(
                web::resource("/index.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(mvt_metadata),
                ),
            )
            .service(
                web::resource("/fontstacks.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fontstacks),
                ),
            )
            .service(
                web::resource("/fonts.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fontstacks),
                ),
            )
            .service(
                web::resource("/fonts/{fonts}/{range}.pbf").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(fonts_pbf),
                ),
            );
        for static_dir in &static_dirs {
            let dir = &static_dir.dir;
            if std::path::Path::new(dir).is_dir() {
                info!("Serving static files from directory '{}'", dir);
                app = app.service(fs::Files::new(&static_dir.path, dir));
            } else {
                warn!("Static file directory '{}' not found", dir);
            }
        }
        app = app
            .service(
                web::resource("/{tileset}.style.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_style_json),
                ),
            )
            .service(
                web::resource("/{tileset}/metadata.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_metadata_json),
                ),
            )
            .service(
                web::resource("/{tileset}.json").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tileset_tilejson),
                ),
            )
            .service(
                web::resource("/{tileset}/{z}/{x}/{y}.pbf").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(tile_pbf),
                ),
            );
        if mvt_viewer {
            app = app.service(
                web::resource("/drilldown").route(
                    web::route()
                        .guard(guard::Any(guard::Get()).or(guard::Head()))
                        .to(drilldown_handler),
                ),
            );
            app = app.default_service(web::to(static_file_handler));
        }
        app
    })
    .workers(workers as usize)
    .bind(&bind_addr)
    .expect("Can not start server on given IP/Port")
    .shutdown_timeout(3) // default: 30s
    .run();

    if log_enabled!(Level::Info) {
        println!("{}", DINO);
    }

    if openbrowser && mvt_viewer {
        let _res = open::that(format!("http://{}:{}", &host, port));
    }

    server.await
}
<|endoftext|>


<commit_before>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Martin Kysel <code@martinkysel.com>
//  *
//  * For the full copyright and license information, please view the LICENSE file
//  * that was distributed with this source code.

// spell-checker:ignore (methods) isnt

#[macro_use]
extern crate uucore;

use std::{
    fs::File,
    io::{stdin, stdout, BufReader, Read, Stdout, Write},
    path::Path,
    time::Duration,
};

#[cfg(all(unix, not(target_os = "fuchsia")))]
extern crate nix;

use clap::{crate_version, App, Arg};
use crossterm::{
    event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
    execute, queue,
    style::Attribute,
    terminal,
};

use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use uucore::display::Quotable;

const BELL: &str = "\x07";

pub mod options {
    pub const SILENT: &str = "silent";
    pub const LOGICAL: &str = "logical";
    pub const NO_PAUSE: &str = "no-pause";
    pub const PRINT_OVER: &str = "print-over";
    pub const CLEAN_PRINT: &str = "clean-print";
    pub const SQUEEZE: &str = "squeeze";
    pub const PLAIN: &str = "plain";
    pub const LINES: &str = "lines";
    pub const NUMBER: &str = "number";
    pub const PATTERN: &str = "pattern";
    pub const FROM_LINE: &str = "from-line";
    pub const FILES: &str = "files";
}

const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n";

pub fn uumain(args: impl uucore::Args) -> i32 {
    let matches = uu_app().get_matches_from(args);

    let mut buff = String::new();
    let silent = matches.is_present(options::SILENT);
    if let Some(files) = matches.values_of(options::FILES) {
        let mut stdout = setup_term();
        let length = files.len();

        let mut files_iter = files.peekable();
        while let (Some(file), next_file) = (files_iter.next(), files_iter.peek()) {
            let file = Path::new(file);
            if file.is_dir() {
                terminal::disable_raw_mode().unwrap();
                show_usage_error!("{} is a directory.", file.quote());
                return 1;
            }
            if !file.exists() {
                terminal::disable_raw_mode().unwrap();
                show_error!("cannot open {}: No such file or directory", file.quote());
                return 1;
            }
            if length > 1 {
                buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", file.to_str().unwrap()));
            }
            let mut reader = BufReader::new(File::open(file).unwrap());
            reader.read_to_string(&mut buff).unwrap();
            more(&buff, &mut stdout, next_file.copied(), silent);
            buff.clear();
        }
        reset_term(&mut stdout);
    } else if atty::isnt(atty::Stream::Stdin) {
        stdin().read_to_string(&mut buff).unwrap();
        let mut stdout = setup_term();
        more(&buff, &mut stdout, None, silent);
        reset_term(&mut stdout);
    } else {
        show_usage_error!("bad usage");
    }
    0
}

pub fn uu_app() -> App<'static, 'static> {
    App::new(uucore::util_name())
        .about("A file perusal filter for CRT viewing.")
        .version(crate_version!())
        .arg(
            Arg::with_name(options::SILENT)
                .short("d")
                .long(options::SILENT)
                .help("Display help instead of ringing bell"),
        )
        // The commented arguments below are unimplemented:
        /*
        .arg(
            Arg::with_name(options::LOGICAL)
                .short("f")
                .long(options::LOGICAL)
                .help("Count logical rather than screen lines"),
        )
        .arg(
            Arg::with_name(options::NO_PAUSE)
                .short("l")
                .long(options::NO_PAUSE)
                .help("Suppress pause after form feed"),
        )
        .arg(
            Arg::with_name(options::PRINT_OVER)
                .short("c")
                .long(options::PRINT_OVER)
                .help("Do not scroll, display text and clean line ends"),
        )
        .arg(
            Arg::with_name(options::CLEAN_PRINT)
                .short("p")
                .long(options::CLEAN_PRINT)
                .help("Do not scroll, clean screen and display text"),
        )
        .arg(
            Arg::with_name(options::SQUEEZE)
                .short("s")
                .long(options::SQUEEZE)
                .help("Squeeze multiple blank lines into one"),
        )
        .arg(
            Arg::with_name(options::PLAIN)
                .short("u")
                .long(options::PLAIN)
                .help("Suppress underlining and bold"),
        )
        .arg(
            Arg::with_name(options::LINES)
                .short("n")
                .long(options::LINES)
                .value_name("number")
                .takes_value(true)
                .help("The number of lines per screen full"),
        )
        .arg(
            Arg::with_name(options::NUMBER)
                .allow_hyphen_values(true)
                .long(options::NUMBER)
                .required(false)
                .takes_value(true)
                .help("Same as --lines"),
        )
        .arg(
            Arg::with_name(options::FROM_LINE)
                .short("F")
                .allow_hyphen_values(true)
                .required(false)
                .takes_value(true)
                .value_name("number")
                .help("Display file beginning from line number"),
        )
        .arg(
            Arg::with_name(options::PATTERN)
                .short("P")
                .allow_hyphen_values(true)
                .required(false)
                .takes_value(true)
                .help("Display file beginning from pattern match"),
        )
        */
        .arg(
            Arg::with_name(options::FILES)
                .required(false)
                .multiple(true)
                .help("Path to the files to be read"),
        )
}

#[cfg(not(target_os = "fuchsia"))]
fn setup_term() -> std::io::Stdout {
    let stdout = stdout();
    terminal::enable_raw_mode().unwrap();
    stdout
}

#[cfg(target_os = "fuchsia")]
#[inline(always)]
fn setup_term() -> usize {
    0
}

#[cfg(not(target_os = "fuchsia"))]
fn reset_term(stdout: &mut std::io::Stdout) {
    terminal::disable_raw_mode().unwrap();
    // Clear the prompt
    queue!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
    // Move cursor to the beginning without printing new line
    print!("\r");
    stdout.flush().unwrap();
}

#[cfg(target_os = "fuchsia")]
#[inline(always)]
fn reset_term(_: &mut usize) {}

fn more(buff: &str, stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
    let (cols, rows) = terminal::size().unwrap();
    let lines = break_buff(buff, usize::from(cols));

    let mut pager = Pager::new(rows, lines, next_file, silent);
    pager.draw(stdout, None);
    if pager.should_close() {
        return;
    }

    loop {
        let mut wrong_key = None;
        if event::poll(Duration::from_millis(10)).unwrap() {
            match event::read().unwrap() {
                Event::Key(KeyEvent {
                    code: KeyCode::Char('q'),
                    modifiers: KeyModifiers::NONE,
                })
                | Event::Key(KeyEvent {
                    code: KeyCode::Char('c'),
                    modifiers: KeyModifiers::CONTROL,
                }) => {
                    reset_term(stdout);
                    std::process::exit(0);
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Down,
                    modifiers: KeyModifiers::NONE,
                })
                | Event::Key(KeyEvent {
                    code: KeyCode::Char(' '),
                    modifiers: KeyModifiers::NONE,
                }) => {
                    if pager.should_close() {
                        return;
                    } else {
                        pager.page_down();
                    }
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Up,
                    modifiers: KeyModifiers::NONE,
                }) => {
                    pager.page_up();
                }
                Event::Resize(col, row) => {
                    pager.page_resize(col, row);
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Char(k),
                    ..
                }) => wrong_key = Some(k),
                _ => continue,
            }

            pager.draw(stdout, wrong_key);
        }
    }
}

struct Pager<'a> {
    // The current line at the top of the screen
    upper_mark: usize,
    // The number of rows that fit on the screen
    content_rows: u16,
    lines: Vec<String>,
    next_file: Option<&'a str>,
    line_count: usize,
    silent: bool,
}

impl<'a> Pager<'a> {
    fn new(rows: u16, lines: Vec<String>, next_file: Option<&'a str>, silent: bool) -> Self {
        let line_count = lines.len();
        Self {
            upper_mark: 0,
            content_rows: rows.saturating_sub(1),
            lines,
            next_file,
            line_count,
            silent,
        }
    }

    fn should_close(&mut self) -> bool {
        self.upper_mark
            .saturating_add(self.content_rows.into())
            .ge(&self.line_count)
    }

    fn page_down(&mut self) {
        self.upper_mark = self.upper_mark.saturating_add(self.content_rows.into());
    }

    fn page_up(&mut self) {
        self.upper_mark = self.upper_mark.saturating_sub(self.content_rows.into());
    }

    // TODO: Deal with column size changes.
    fn page_resize(&mut self, _: u16, row: u16) {
        self.content_rows = row.saturating_sub(1);
    }

    fn draw(&self, stdout: &mut std::io::Stdout, wrong_key: Option<char>) {
        let lower_mark = self
            .line_count
            .min(self.upper_mark.saturating_add(self.content_rows.into()));
        self.draw_lines(stdout);
        self.draw_prompt(stdout, lower_mark, wrong_key);
        stdout.flush().unwrap();
    }

    fn draw_lines(&self, stdout: &mut std::io::Stdout) {
        execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
        let displayed_lines = self
            .lines
            .iter()
            .skip(self.upper_mark)
            .take(self.content_rows.into());

        for line in displayed_lines {
            stdout
                .write_all(format!("\r{}\n", line).as_bytes())
                .unwrap();
        }
    }

    fn draw_prompt(&self, stdout: &mut Stdout, lower_mark: usize, wrong_key: Option<char>) {
        let status_inner = if lower_mark == self.line_count {
            format!("Next file: {}", self.next_file.unwrap_or_default())
        } else {
            format!(
                "{}%",
                (lower_mark as f64 / self.line_count as f64 * 100.0).round() as u16
            )
        };

        let status = format!("--More--({})", status_inner);

        let banner = match (self.silent, wrong_key) {
            (true, Some(key)) => format!(
                "{} [Unknown key: '{}'. Press 'h' for instructions. (unimplemented)]",
                status, key
            ),
            (true, None) => format!("{}[Press space to continue, 'q' to quit.]", status),
            (false, Some(_)) => format!("{}{}", status, BELL),
            (false, None) => status,
        };

        write!(
            stdout,
            "\r{}{}{}",
            Attribute::Reverse,
            banner,
            Attribute::Reset
        )
        .unwrap();
    }
}

// Break the lines on the cols of the terminal
fn break_buff(buff: &str, cols: usize) -> Vec<String> {
    let mut lines = Vec::with_capacity(buff.lines().count());

    for l in buff.lines() {
        lines.append(&mut break_line(l, cols));
    }
    lines
}

fn break_line(line: &str, cols: usize) -> Vec<String> {
    let width = UnicodeWidthStr::width(line);
    let mut lines = Vec::new();
    if width < cols {
        lines.push(line.to_string());
        return lines;
    }

    let gr_idx = UnicodeSegmentation::grapheme_indices(line, true);
    let mut last_index = 0;
    let mut total_width = 0;
    for (index, grapheme) in gr_idx {
        let width = UnicodeWidthStr::width(grapheme);
        total_width += width;

        if total_width > cols {
            lines.push(line[last_index..index].to_string());
            last_index = index;
            total_width = width;
        }
    }

    if last_index != line.len() {
        lines.push(line[last_index..].to_string());
    }
    lines
}

#[cfg(test)]
mod tests {
    use super::break_line;
    use unicode_width::UnicodeWidthStr;

    #[test]
    fn test_break_lines_long() {
        let mut test_string = String::with_capacity(100);
        for _ in 0..200 {
            test_string.push('#');
        }

        let lines = break_line(&test_string, 80);
        let widths: Vec<usize> = lines
            .iter()
            .map(|s| UnicodeWidthStr::width(&s[..]))
            .collect();

        assert_eq!((80, 80, 40), (widths[0], widths[1], widths[2]));
    }

    #[test]
    fn test_break_lines_short() {
        let mut test_string = String::with_capacity(100);
        for _ in 0..20 {
            test_string.push('#');
        }

        let lines = break_line(&test_string, 80);

        assert_eq!(20, lines[0].len());
    }

    #[test]
    fn test_break_line_zwj() {
        let mut test_string = String::with_capacity(1100);
        for _ in 0..20 {
            test_string.push_str("👩🏻🔬");
        }

        let lines = break_line(&test_string, 80);

        let widths: Vec<usize> = lines
            .iter()
            .map(|s| UnicodeWidthStr::width(&s[..]))
            .collect();

        // Each 👩🏻🔬 is 6 character width it break line to the closest number to 80 => 6 * 13 = 78
        assert_eq!((78, 42), (widths[0], widths[1]));
    }
}
<commit_msg>more: add next-line and prev-line command.<commit_after>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Martin Kysel <code@martinkysel.com>
//  *
//  * For the full copyright and license information, please view the LICENSE file
//  * that was distributed with this source code.

// spell-checker:ignore (methods) isnt

#[macro_use]
extern crate uucore;

use std::{
    fs::File,
    io::{stdin, stdout, BufReader, Read, Stdout, Write},
    path::Path,
    time::Duration,
};

#[cfg(all(unix, not(target_os = "fuchsia")))]
extern crate nix;

use clap::{crate_version, App, Arg};
use crossterm::{
    event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
    execute, queue,
    style::Attribute,
    terminal,
};

use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use uucore::display::Quotable;

const BELL: &str = "\x07";

pub mod options {
    pub const SILENT: &str = "silent";
    pub const LOGICAL: &str = "logical";
    pub const NO_PAUSE: &str = "no-pause";
    pub const PRINT_OVER: &str = "print-over";
    pub const CLEAN_PRINT: &str = "clean-print";
    pub const SQUEEZE: &str = "squeeze";
    pub const PLAIN: &str = "plain";
    pub const LINES: &str = "lines";
    pub const NUMBER: &str = "number";
    pub const PATTERN: &str = "pattern";
    pub const FROM_LINE: &str = "from-line";
    pub const FILES: &str = "files";
}

const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n";

pub fn uumain(args: impl uucore::Args) -> i32 {
    let matches = uu_app().get_matches_from(args);

    let mut buff = String::new();
    let silent = matches.is_present(options::SILENT);
    if let Some(files) = matches.values_of(options::FILES) {
        let mut stdout = setup_term();
        let length = files.len();

        let mut files_iter = files.peekable();
        while let (Some(file), next_file) = (files_iter.next(), files_iter.peek()) {
            let file = Path::new(file);
            if file.is_dir() {
                terminal::disable_raw_mode().unwrap();
                show_usage_error!("{} is a directory.", file.quote());
                return 1;
            }
            if !file.exists() {
                terminal::disable_raw_mode().unwrap();
                show_error!("cannot open {}: No such file or directory", file.quote());
                return 1;
            }
            if length > 1 {
                buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", file.to_str().unwrap()));
            }
            let mut reader = BufReader::new(File::open(file).unwrap());
            reader.read_to_string(&mut buff).unwrap();
            more(&buff, &mut stdout, next_file.copied(), silent);
            buff.clear();
        }
        reset_term(&mut stdout);
    } else if atty::isnt(atty::Stream::Stdin) {
        stdin().read_to_string(&mut buff).unwrap();
        let mut stdout = setup_term();
        more(&buff, &mut stdout, None, silent);
        reset_term(&mut stdout);
    } else {
        show_usage_error!("bad usage");
    }
    0
}

pub fn uu_app() -> App<'static, 'static> {
    App::new(uucore::util_name())
        .about("A file perusal filter for CRT viewing.")
        .version(crate_version!())
        .arg(
            Arg::with_name(options::SILENT)
                .short("d")
                .long(options::SILENT)
                .help("Display help instead of ringing bell"),
        )
        // The commented arguments below are unimplemented:
        /*
        .arg(
            Arg::with_name(options::LOGICAL)
                .short("f")
                .long(options::LOGICAL)
                .help("Count logical rather than screen lines"),
        )
        .arg(
            Arg::with_name(options::NO_PAUSE)
                .short("l")
                .long(options::NO_PAUSE)
                .help("Suppress pause after form feed"),
        )
        .arg(
            Arg::with_name(options::PRINT_OVER)
                .short("c")
                .long(options::PRINT_OVER)
                .help("Do not scroll, display text and clean line ends"),
        )
        .arg(
            Arg::with_name(options::CLEAN_PRINT)
                .short("p")
                .long(options::CLEAN_PRINT)
                .help("Do not scroll, clean screen and display text"),
        )
        .arg(
            Arg::with_name(options::SQUEEZE)
                .short("s")
                .long(options::SQUEEZE)
                .help("Squeeze multiple blank lines into one"),
        )
        .arg(
            Arg::with_name(options::PLAIN)
                .short("u")
                .long(options::PLAIN)
                .help("Suppress underlining and bold"),
        )
        .arg(
            Arg::with_name(options::LINES)
                .short("n")
                .long(options::LINES)
                .value_name("number")
                .takes_value(true)
                .help("The number of lines per screen full"),
        )
        .arg(
            Arg::with_name(options::NUMBER)
                .allow_hyphen_values(true)
                .long(options::NUMBER)
                .required(false)
                .takes_value(true)
                .help("Same as --lines"),
        )
        .arg(
            Arg::with_name(options::FROM_LINE)
                .short("F")
                .allow_hyphen_values(true)
                .required(false)
                .takes_value(true)
                .value_name("number")
                .help("Display file beginning from line number"),
        )
        .arg(
            Arg::with_name(options::PATTERN)
                .short("P")
                .allow_hyphen_values(true)
                .required(false)
                .takes_value(true)
                .help("Display file beginning from pattern match"),
        )
        */
        .arg(
            Arg::with_name(options::FILES)
                .required(false)
                .multiple(true)
                .help("Path to the files to be read"),
        )
}

#[cfg(not(target_os = "fuchsia"))]
fn setup_term() -> std::io::Stdout {
    let stdout = stdout();
    terminal::enable_raw_mode().unwrap();
    stdout
}

#[cfg(target_os = "fuchsia")]
#[inline(always)]
fn setup_term() -> usize {
    0
}

#[cfg(not(target_os = "fuchsia"))]
fn reset_term(stdout: &mut std::io::Stdout) {
    terminal::disable_raw_mode().unwrap();
    // Clear the prompt
    queue!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
    // Move cursor to the beginning without printing new line
    print!("\r");
    stdout.flush().unwrap();
}

#[cfg(target_os = "fuchsia")]
#[inline(always)]
fn reset_term(_: &mut usize) {}

fn more(buff: &str, stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
    let (cols, rows) = terminal::size().unwrap();
    let lines = break_buff(buff, usize::from(cols));

    let mut pager = Pager::new(rows, lines, next_file, silent);
    pager.draw(stdout, None);
    if pager.should_close() {
        return;
    }

    loop {
        let mut wrong_key = None;
        if event::poll(Duration::from_millis(10)).unwrap() {
            match event::read().unwrap() {
                Event::Key(KeyEvent {
                    code: KeyCode::Char('q'),
                    modifiers: KeyModifiers::NONE,
                })
                | Event::Key(KeyEvent {
                    code: KeyCode::Char('c'),
                    modifiers: KeyModifiers::CONTROL,
                }) => {
                    reset_term(stdout);
                    std::process::exit(0);
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Down,
                    modifiers: KeyModifiers::NONE,
                })
                | Event::Key(KeyEvent {
                    code: KeyCode::Char(' '),
                    modifiers: KeyModifiers::NONE,
                }) => {
                    if pager.should_close() {
                        return;
                    } else {
                        pager.page_down();
                    }
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Up,
                    modifiers: KeyModifiers::NONE,
                }) => {
                    pager.page_up();
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Char('j'),
                    modifiers: KeyModifiers::NONE,
                }) => {
                    if pager.should_close() {
                        return;
                    } else {
                        pager.next_line();
                    }
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Char('k'),
                    modifiers: KeyModifiers::NONE,
                }) => {
                    pager.prev_line();
                }
                Event::Resize(col, row) => {
                    pager.page_resize(col, row);
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Char(k),
                    ..
                }) => wrong_key = Some(k),
                _ => continue,
            }

            pager.draw(stdout, wrong_key);
        }
    }
}

struct Pager<'a> {
    // The current line at the top of the screen
    upper_mark: usize,
    // The number of rows that fit on the screen
    content_rows: u16,
    lines: Vec<String>,
    next_file: Option<&'a str>,
    line_count: usize,
    silent: bool,
}

impl<'a> Pager<'a> {
    fn new(rows: u16, lines: Vec<String>, next_file: Option<&'a str>, silent: bool) -> Self {
        let line_count = lines.len();
        Self {
            upper_mark: 0,
            content_rows: rows.saturating_sub(1),
            lines,
            next_file,
            line_count,
            silent,
        }
    }

    fn should_close(&mut self) -> bool {
        self.upper_mark
            .saturating_add(self.content_rows.into())
            .ge(&self.line_count)
    }

    fn page_down(&mut self) {
        // If the next page down position __after redraw__ is greater than the total line count,
        // the upper mark must not grow past top of the screen at the end of the open file.
        if self
            .upper_mark
            .saturating_add(self.content_rows as usize * 2)
            .ge(&self.line_count)
        {
            self.upper_mark = self.line_count - self.content_rows as usize;
            return;
        }

        self.upper_mark = self.upper_mark.saturating_add(self.content_rows.into());
    }

    fn page_up(&mut self) {
        self.upper_mark = self.upper_mark.saturating_sub(self.content_rows.into());
    }

    fn next_line(&mut self) {
        self.upper_mark = self.upper_mark.saturating_add(1);
    }

    fn prev_line(&mut self) {
        self.upper_mark = self.upper_mark.saturating_sub(1);
    }

    // TODO: Deal with column size changes.
    fn page_resize(&mut self, _: u16, row: u16) {
        self.content_rows = row.saturating_sub(1);
    }

    fn draw(&self, stdout: &mut std::io::Stdout, wrong_key: Option<char>) {
        let lower_mark = self
            .line_count
            .min(self.upper_mark.saturating_add(self.content_rows.into()));
        self.draw_lines(stdout);
        self.draw_prompt(stdout, lower_mark, wrong_key);
        stdout.flush().unwrap();
    }

    fn draw_lines(&self, stdout: &mut std::io::Stdout) {
        execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
        let displayed_lines = self
            .lines
            .iter()
            .skip(self.upper_mark)
            .take(self.content_rows.into());

        for line in displayed_lines {
            stdout
                .write_all(format!("\r{}\n", line).as_bytes())
                .unwrap();
        }
    }

    fn draw_prompt(&self, stdout: &mut Stdout, lower_mark: usize, wrong_key: Option<char>) {
        let status_inner = if lower_mark == self.line_count {
            format!("Next file: {}", self.next_file.unwrap_or_default())
        } else {
            format!(
                "{}%",
                (lower_mark as f64 / self.line_count as f64 * 100.0).round() as u16
            )
        };

        let status = format!("--More--({})", status_inner);

        let banner = match (self.silent, wrong_key) {
            (true, Some(key)) => format!(
                "{} [Unknown key: '{}'. Press 'h' for instructions. (unimplemented)]",
                status, key
            ),
            (true, None) => format!("{}[Press space to continue, 'q' to quit.]", status),
            (false, Some(_)) => format!("{}{}", status, BELL),
            (false, None) => status,
        };

        write!(
            stdout,
            "\r{}{}{}",
            Attribute::Reverse,
            banner,
            Attribute::Reset
        )
        .unwrap();
    }
}

// Break the lines on the cols of the terminal
fn break_buff(buff: &str, cols: usize) -> Vec<String> {
    let mut lines = Vec::with_capacity(buff.lines().count());

    for l in buff.lines() {
        lines.append(&mut break_line(l, cols));
    }
    lines
}

fn break_line(line: &str, cols: usize) -> Vec<String> {
    let width = UnicodeWidthStr::width(line);
    let mut lines = Vec::new();
    if width < cols {
        lines.push(line.to_string());
        return lines;
    }

    let gr_idx = UnicodeSegmentation::grapheme_indices(line, true);
    let mut last_index = 0;
    let mut total_width = 0;
    for (index, grapheme) in gr_idx {
        let width = UnicodeWidthStr::width(grapheme);
        total_width += width;

        if total_width > cols {
            lines.push(line[last_index..index].to_string());
            last_index = index;
            total_width = width;
        }
    }

    if last_index != line.len() {
        lines.push(line[last_index..].to_string());
    }
    lines
}

#[cfg(test)]
mod tests {
    use super::break_line;
    use unicode_width::UnicodeWidthStr;

    #[test]
    fn test_break_lines_long() {
        let mut test_string = String::with_capacity(100);
        for _ in 0..200 {
            test_string.push('#');
        }

        let lines = break_line(&test_string, 80);
        let widths: Vec<usize> = lines
            .iter()
            .map(|s| UnicodeWidthStr::width(&s[..]))
            .collect();

        assert_eq!((80, 80, 40), (widths[0], widths[1], widths[2]));
    }

    #[test]
    fn test_break_lines_short() {
        let mut test_string = String::with_capacity(100);
        for _ in 0..20 {
            test_string.push('#');
        }

        let lines = break_line(&test_string, 80);

        assert_eq!(20, lines[0].len());
    }

    #[test]
    fn test_break_line_zwj() {
        let mut test_string = String::with_capacity(1100);
        for _ in 0..20 {
            test_string.push_str("👩🏻🔬");
        }

        let lines = break_line(&test_string, 80);

        let widths: Vec<usize> = lines
            .iter()
            .map(|s| UnicodeWidthStr::width(&s[..]))
            .collect();

        // Each 👩🏻🔬 is 6 character width it break line to the closest number to 80 => 6 * 13 = 78
        assert_eq!((78, 42), (widths[0], widths[1]));
    }
}
<|endoftext|>


<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::account_address::{AccountAddress, ADDRESS_LENGTH};
use anyhow::Error;
use libra_crypto::{test_utils::TEST_SEED, HashValue, *};
use rand::{rngs::StdRng, SeedableRng};
use std::convert::TryFrom;

/// ValidatorSigner associates an author with public and private keys with helpers for signing and
/// validating. This struct can be used for all signing operations including block and network
/// signing, respectively.
#[derive(Debug)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Clone))]
pub struct ValidatorSigner<PrivateKey: SigningKey> {
    author: AccountAddress,
    public_key: PrivateKey::VerifyingKeyMaterial,
    private_key: PrivateKey,
}

impl<PrivateKey: SigningKey> ValidatorSigner<PrivateKey> {
    pub fn new(
        opt_account_address: impl Into<Option<AccountAddress>>,
        private_key: PrivateKey,
    ) -> Self {
        let public_key: PrivateKey::VerifyingKeyMaterial = private_key.public_key();

        let account_address = opt_account_address
            .into()
            .unwrap_or_else(|| AccountAddress::from_public_key(&public_key));
        ValidatorSigner {
            author: account_address,
            public_key,
            private_key,
        }
    }

    /// Constructs a signature for `message` using `private_key`.
    pub fn sign_message(&self, message: HashValue) -> Result<PrivateKey::SignatureMaterial, Error> {
        Ok(self.private_key.sign_message(&message))
    }

    /// Returns the author associated with this signer.
    pub fn author(&self) -> AccountAddress {
        self.author
    }

    /// Returns the public key associated with this signer.
    pub fn public_key(&self) -> PrivateKey::VerifyingKeyMaterial {
        self.public_key.clone()
    }

    /// Returns the private key associated with this signer. Only available for testing purposes.
    #[cfg(any(test, feature = "fuzzing"))]
    pub fn private_key(&self) -> &PrivateKey {
        &self.private_key
    }
}

impl<PrivateKey: SigningKey + Genesis> ValidatorSigner<PrivateKey> {
    /// Generate the genesis block signer information.
    pub fn genesis() -> Self {
        let genesis_key = PrivateKey::genesis();
        Self::new(None, genesis_key)
    }
}

impl<PrivateKey: SigningKey + Uniform> ValidatorSigner<PrivateKey> {
    /// Generate a random set of public and private keys and author
    /// information.
    /// This takes an optional seed, which it initializes to
    /// `test_utils::TEST_SEED` if passed `None`
    pub fn random(opt_rng_seed: impl for<'a> Into<Option<[u8; 32]>>) -> Self {
        let mut rng = StdRng::from_seed(opt_rng_seed.into().unwrap_or(TEST_SEED));
        let private_key = PrivateKey::generate_for_testing(&mut rng);
        Self::new(None, private_key)
    }

    /// For test only - makes signer with nicely looking account address that has specified integer
    /// as fist byte, and rest are zeroes
    pub fn from_int(num: u8) -> Self {
        let mut address = [0; ADDRESS_LENGTH];
        address[0] = num;
        let mut rng = StdRng::from_seed(TEST_SEED);
        let private_key = PrivateKey::generate_for_testing(&mut rng);
        Self::new(AccountAddress::try_from(&address[..]).unwrap(), private_key)
    }
}

#[cfg(any(test, feature = "fuzzing"))]
pub mod proptests {
    use super::*;
    #[cfg(test)]
    use libra_crypto::ed25519::*;
    use proptest::{prelude::*, sample, strategy::LazyJust};

    #[allow(clippy::redundant_closure)]
    pub fn arb_signing_key<PrivateKey: SigningKey + Uniform + Genesis + 'static>(
    ) -> impl Strategy<Value = PrivateKey> {
        prop_oneof![
            // The no_shrink here reflects that particular keypair choices out
            // of random options are irrelevant.
            LazyJust::new(|| PrivateKey::generate_for_testing(&mut StdRng::from_seed(TEST_SEED))),
            LazyJust::new(|| PrivateKey::genesis()),
        ]
    }

    pub fn signer_strategy<PrivateKey: SigningKey + Uniform + Genesis>(
        signing_key_strategy: impl Strategy<Value = PrivateKey>,
    ) -> impl Strategy<Value = ValidatorSigner<PrivateKey>> {
        signing_key_strategy.prop_map(|signing_key| ValidatorSigner::new(None, signing_key))
    }

    #[allow(clippy::redundant_closure)]
    pub fn rand_signer<PrivateKey: SigningKey + Uniform + Genesis + 'static>(
    ) -> impl Strategy<Value = ValidatorSigner<PrivateKey>> {
        signer_strategy(arb_signing_key())
    }

    #[allow(clippy::redundant_closure)]
    pub fn arb_signer<PrivateKey: SigningKey + Uniform + Genesis + 'static>(
    ) -> impl Strategy<Value = ValidatorSigner<PrivateKey>> {
        prop_oneof![rand_signer(), LazyJust::new(|| ValidatorSigner::genesis()),]
    }

    fn select_keypair<PrivateKey: SigningKey + Uniform + Genesis + Clone + 'static>(
        keys: Vec<PrivateKey>,
    ) -> impl Strategy<Value = PrivateKey> {
        sample::select(keys)
    }

    pub fn mostly_in_keypair_pool<PrivateKey: SigningKey + Uniform + Genesis + Clone + 'static>(
        keys: Vec<PrivateKey>,
    ) -> impl Strategy<Value = ValidatorSigner<PrivateKey>> {
        prop::strategy::Union::new_weighted(vec![
            (9, signer_strategy(select_keypair(keys)).boxed()),
            (1, arb_signer().boxed()),
        ])
    }

    proptest! {
        #[test]
        fn test_new_signer(signing_key in arb_signing_key::<Ed25519PrivateKey>()){
            let public_key = signing_key.public_key();
            let signer = ValidatorSigner::new(None, signing_key);
            prop_assert_eq!(public_key, signer.public_key());
        }

    }
}
<commit_msg>[types] ValidatorSigner::new accepts AccountAddress instead of Option<AccountAddress><commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::account_address::{AccountAddress, ADDRESS_LENGTH};
use anyhow::Error;
use libra_crypto::{test_utils::TEST_SEED, HashValue, *};
use rand::{rngs::StdRng, SeedableRng};
use std::convert::TryFrom;

/// ValidatorSigner associates an author with public and private keys with helpers for signing and
/// validating. This struct can be used for all signing operations including block and network
/// signing, respectively.
#[derive(Debug)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Clone))]
pub struct ValidatorSigner<PrivateKey: SigningKey> {
    author: AccountAddress,
    public_key: PrivateKey::VerifyingKeyMaterial,
    private_key: PrivateKey,
}

impl<PrivateKey: SigningKey> ValidatorSigner<PrivateKey> {
    pub fn new(author: AccountAddress, private_key: PrivateKey) -> Self {
        let public_key: PrivateKey::VerifyingKeyMaterial = private_key.public_key();
        ValidatorSigner {
            author,
            public_key,
            private_key,
        }
    }

    /// Constructs a signature for `message` using `private_key`.
    pub fn sign_message(&self, message: HashValue) -> Result<PrivateKey::SignatureMaterial, Error> {
        Ok(self.private_key.sign_message(&message))
    }

    /// Returns the author associated with this signer.
    pub fn author(&self) -> AccountAddress {
        self.author
    }

    /// Returns the public key associated with this signer.
    pub fn public_key(&self) -> PrivateKey::VerifyingKeyMaterial {
        self.public_key.clone()
    }

    /// Returns the private key associated with this signer. Only available for testing purposes.
    #[cfg(any(test, feature = "fuzzing"))]
    pub fn private_key(&self) -> &PrivateKey {
        &self.private_key
    }
}

impl<PrivateKey: SigningKey + Uniform> ValidatorSigner<PrivateKey> {
    /// Generate a random set of public and private keys and author
    /// information.
    /// This takes an optional seed, which it initializes to
    /// `test_utils::TEST_SEED` if passed `None`
    pub fn random(opt_rng_seed: impl for<'a> Into<Option<[u8; 32]>>) -> Self {
        let mut rng = StdRng::from_seed(opt_rng_seed.into().unwrap_or(TEST_SEED));
        Self::new(
            AccountAddress::random(),
            PrivateKey::generate_for_testing(&mut rng),
        )
    }

    /// For test only - makes signer with nicely looking account address that has specified integer
    /// as fist byte, and rest are zeroes
    pub fn from_int(num: u8) -> Self {
        let mut address = [0; ADDRESS_LENGTH];
        address[0] = num;
        let mut rng = StdRng::from_seed(TEST_SEED);
        let private_key = PrivateKey::generate_for_testing(&mut rng);
        Self::new(AccountAddress::try_from(&address[..]).unwrap(), private_key)
    }
}

#[cfg(any(test, feature = "fuzzing"))]
pub mod proptests {
    use super::*;
    #[cfg(test)]
    use libra_crypto::ed25519::*;
    use proptest::{prelude::*, sample, strategy::LazyJust};

    #[allow(clippy::redundant_closure)]
    pub fn arb_signing_key<PrivateKey: SigningKey + Uniform + Genesis + 'static>(
    ) -> impl Strategy<Value = PrivateKey> {
        prop_oneof![
            // The no_shrink here reflects that particular keypair choices out
            // of random options are irrelevant.
            LazyJust::new(|| PrivateKey::generate_for_testing(&mut StdRng::from_seed(TEST_SEED))),
            LazyJust::new(|| PrivateKey::genesis()),
        ]
    }

    pub fn signer_strategy<PrivateKey: SigningKey + Uniform + Genesis>(
        signing_key_strategy: impl Strategy<Value = PrivateKey>,
    ) -> impl Strategy<Value = ValidatorSigner<PrivateKey>> {
        signing_key_strategy
            .prop_map(|signing_key| ValidatorSigner::new(AccountAddress::random(), signing_key))
    }

    #[allow(clippy::redundant_closure)]
    pub fn rand_signer<PrivateKey: SigningKey + Uniform + Genesis + 'static>(
    ) -> impl Strategy<Value = ValidatorSigner<PrivateKey>> {
        signer_strategy(arb_signing_key())
    }

    #[allow(clippy::redundant_closure)]
    pub fn arb_signer<PrivateKey: SigningKey + Uniform + Genesis + 'static>(
    ) -> impl Strategy<Value = ValidatorSigner<PrivateKey>> {
        prop_oneof![
            rand_signer(),
            LazyJust::new(|| {
                let genesis_key = PrivateKey::genesis();
                ValidatorSigner::new(AccountAddress::random(), genesis_key)
            })
        ]
    }

    fn select_keypair<PrivateKey: SigningKey + Uniform + Genesis + Clone + 'static>(
        keys: Vec<PrivateKey>,
    ) -> impl Strategy<Value = PrivateKey> {
        sample::select(keys)
    }

    pub fn mostly_in_keypair_pool<PrivateKey: SigningKey + Uniform + Genesis + Clone + 'static>(
        keys: Vec<PrivateKey>,
    ) -> impl Strategy<Value = ValidatorSigner<PrivateKey>> {
        prop::strategy::Union::new_weighted(vec![
            (9, signer_strategy(select_keypair(keys)).boxed()),
            (1, arb_signer().boxed()),
        ])
    }

    proptest! {
        #[test]
        fn test_new_signer(signing_key in arb_signing_key::<Ed25519PrivateKey>()){
            let public_key = signing_key.public_key();
            let signer = ValidatorSigner::new(AccountAddress::random(), signing_key);
            prop_assert_eq!(public_key, signer.public_key());
        }

    }
}
<|endoftext|>


<commit_before>#[macro_export]
macro_rules! matches {
    ($expression:expr, $($pattern:tt)+) => {
        _matches_tt_as_expr_hack! {
            match $expression {
                $($pattern)+ => true,
                _ => false
            }
        }
    }
}

/// Work around "error: unexpected token: `an interpolated tt`", whatever that means.
#[macro_export]
macro_rules! _matches_tt_as_expr_hack {
    ($value:expr) => ($value)
}

#[macro_export]
macro_rules! assert_matches {
    ($expression:expr, $($pattern:tt)+) => {
        _matches_tt_as_expr_hack! {
            match $expression {
                $($pattern)+ => (),
                ref e => panic!("assertion failed: `{:?}` does not match `{}`", e, stringify!($($pattern)+)),
            }
        }
    }
}

#[macro_export]
macro_rules! debug_assert_matches {
    ($($arg:tt)*) => (if cfg!(debug_assertions) { assert_matches!($($arg)*); })
}

#[test]
fn matches_works() {
    let foo = Some("-12");
    assert!(matches!(foo, Some(bar) if
        matches!(bar.as_bytes()[0], b'+' | b'-') &&
        matches!(bar.as_bytes()[1], b'0'...b'9')
    ));
}

#[test]
fn assert_matches_works() {
    let foo = Some("-12");
    assert_matches!(foo, Some(bar) if
        matches!(bar.as_bytes()[0], b'+' | b'-') &&
        matches!(bar.as_bytes()[1], b'0'...b'9')
    );
}

#[test]
#[should_panic(expected = "assertion failed: `Some(\"-AB\")` does not match ")]
fn assert_matches_panics() {
    let foo = Some("-AB");
    assert_matches!(foo, Some(bar) if
        matches!(bar.as_bytes()[0], b'+' | b'-') &&
        matches!(bar.as_bytes()[1], b'0'...b'9')
    );
}
<commit_msg>matches: Remove _matches_tt_as_expr_hack, it is redundant in Rust 1.12+<commit_after>#[macro_export]
macro_rules! matches {
    ($expression:expr, $($pattern:tt)+) => {
        match $expression {
            $($pattern)+ => true,
            _ => false
        }
    }
}

#[macro_export]
macro_rules! assert_matches {
    ($expression:expr, $($pattern:tt)+) => {
        match $expression {
            $($pattern)+ => (),
            ref e => panic!("assertion failed: `{:?}` does not match `{}`", e, stringify!($($pattern)+)),
        }
    }
}

#[macro_export]
macro_rules! debug_assert_matches {
    ($($arg:tt)*) => (if cfg!(debug_assertions) { assert_matches!($($arg)*); })
}

#[test]
fn matches_works() {
    let foo = Some("-12");
    assert!(matches!(foo, Some(bar) if
        matches!(bar.as_bytes()[0], b'+' | b'-') &&
        matches!(bar.as_bytes()[1], b'0'...b'9')
    ));
}

#[test]
fn assert_matches_works() {
    let foo = Some("-12");
    assert_matches!(foo, Some(bar) if
        matches!(bar.as_bytes()[0], b'+' | b'-') &&
        matches!(bar.as_bytes()[1], b'0'...b'9')
    );
}

#[test]
#[should_panic(expected = "assertion failed: `Some(\"-AB\")` does not match ")]
fn assert_matches_panics() {
    let foo = Some("-AB");
    assert_matches!(foo, Some(bar) if
        matches!(bar.as_bytes()[0], b'+' | b'-') &&
        matches!(bar.as_bytes()[1], b'0'...b'9')
    );
}
<|endoftext|>


<commit_before>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

extern crate bragi;
extern crate iron_test;
extern crate serde_json;
use super::count_types;
use super::get_types;
use super::get_value;
use super::get_values;
use super::BragiHandler;

pub fn bragi_osm_test(es_wrapper: ::ElasticSearchWrapper) {
    let bragi = BragiHandler::new(format!("{}/munin", es_wrapper.host()));

    // *********************************
    // We load the OSM dataset (including ways)
    // *********************************
    let osm2mimir = concat!(env!("OUT_DIR"), "/../../../osm2mimir");
    ::launch_and_assert(
        osm2mimir,
        vec![
            "--input=./tests/fixtures/osm_fixture.osm.pbf".into(),
            "--import-admin".into(),
            "--import-way".into(),
            "--level=8".into(),
            "--level=7".into(),
            format!("--connection-string={}", es_wrapper.host()),
        ],
        &es_wrapper,
    );

    zip_code_test(&bragi);
    zip_code_street_test(&bragi);
    zip_code_admin_test(&bragi);
    bbox_admin_test(&bragi);
    city_admin_test(&bragi);
    administrative_region_test(&bragi);
}

fn zip_code_test(bragi: &BragiHandler) {
    let all_20 = bragi.get("/autocomplete?q=77000");
    assert_eq!(all_20.len(), 10);
    for postcodes in get_values(&all_20, "postcode") {
        assert!(postcodes.split(';').any(|p| p == "77000"));
    }
    assert!(get_values(&all_20, "postcode")
        .iter()
        .any(|r| *r == "77000;77003;77008;CP77001"));

    let types = get_types(&all_20);
    let count = count_types(&types, "street");
    assert_eq!(count, 7);

    let count = count_types(&types, "city");
    assert_eq!(count, 3);

    let count = count_types(&types, "house");
    assert_eq!(count, 0);
}

fn zip_code_street_test(bragi: &BragiHandler) {
    let res = bragi.get("/autocomplete?q=77000 Lotissement le Clos de Givry");
    assert_eq!(res.len(), 1);
    let le_clos = &res[0];
    assert_eq!(le_clos["postcode"], "77000");
    assert_eq!(
        le_clos["label"],
        "Lotissement le Clos de Givry (Livry-sur-Seine)"
    );
    assert_eq!(le_clos["name"], "Lotissement le Clos de Givry");
    assert_eq!(le_clos["street"], "Lotissement le Clos de Givry");

    let boundary = le_clos["administrative_regions"].pointer("/0/boundary");
    assert_eq!(boundary, None);

    assert_eq!(le_clos["type"], "street");
    assert_eq!(le_clos["city"], "Livry-sur-Seine");
    assert_eq!(le_clos["citycode"], "77255");
}

fn zip_code_admin_test(bragi: &BragiHandler) {
    let all_20 = bragi.get("/autocomplete?q=77000 Vaux-le-Pénil");
    assert_eq!(all_20.len(), 4);
    assert!(get_values(&all_20, "postcode")
        .iter()
        .all(|r| *r == "77000",));
    let types = get_types(&all_20);
    let count = count_types(&types, "street");
    assert_eq!(count, 3);

    let count = count_types(&types, "city");
    assert_eq!(count, 1);
    let first_city = all_20.iter().find(|e| get_value(e, "type") == "city");
    assert_eq!(get_value(first_city.unwrap(), "citycode"), "77487");

    let count = count_types(&types, "house");
    assert_eq!(count, 0);
}

fn bbox_admin_test(bragi: &BragiHandler) {
    let all_20 = bragi.get("/autocomplete?q=77000 Vaux-le-Pénil");
    let first_city = all_20.iter().find(|e| get_value(e, "type") == "city");
    let result = first_city.unwrap().get("bbox").unwrap().as_array().unwrap();
    let expected = vec![2.663446, 48.5064094, 2.7334936, 48.5419672];
    assert_eq!(expected.len(), result.len());
    //check that coords are "close" from the expected value
    for i in 0..expected.len() {
        let r = result[i].as_f64().unwrap();
        let e = expected[i];
        assert_f64_near!(e, r, 1);
    }
}

fn city_admin_test(bragi: &BragiHandler) {
    let all_melun = bragi.get("/autocomplete?q=Melun Rp");
    let types = get_types(&all_melun);
    let count = count_types(&types, "city");
    assert!(count > 0);
}

fn administrative_region_test(bragi: &BragiHandler) {
    let all_creteil = bragi.get("/autocomplete?q=Créteil");
    let types = get_types(&all_creteil);
    let count = count_types(&types, "administrative_region");
    assert_eq!(count, 1);
}
<commit_msg>apply remarks from texitoi<commit_after>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

extern crate bragi;
extern crate iron_test;
extern crate serde_json;
use super::count_types;
use super::get_types;
use super::get_value;
use super::get_values;
use super::BragiHandler;

pub fn bragi_osm_test(es_wrapper: ::ElasticSearchWrapper) {
    let bragi = BragiHandler::new(format!("{}/munin", es_wrapper.host()));

    // *********************************
    // We load the OSM dataset (including ways)
    // *********************************
    let osm2mimir = concat!(env!("OUT_DIR"), "/../../../osm2mimir");
    ::launch_and_assert(
        osm2mimir,
        vec![
            "--input=./tests/fixtures/osm_fixture.osm.pbf".into(),
            "--import-admin".into(),
            "--import-way".into(),
            "--level=8".into(),
            "--level=7".into(),
            format!("--connection-string={}", es_wrapper.host()),
        ],
        &es_wrapper,
    );

    zip_code_test(&bragi);
    zip_code_street_test(&bragi);
    zip_code_admin_test(&bragi);
    bbox_admin_test(&bragi);
    city_admin_test(&bragi);
    administrative_region_test(&bragi);
}

fn zip_code_test(bragi: &BragiHandler) {
    let all_20 = bragi.get("/autocomplete?q=77000");
    assert_eq!(all_20.len(), 10);
    for postcodes in get_values(&all_20, "postcode") {
        assert!(postcodes.split(';').any(|p| p == "77000"));
    }
    assert!(get_values(&all_20, "postcode")
        .iter()
        .any(|r| *r == "77000;77003;77008;CP77001"));

    let types = get_types(&all_20);
    let count = count_types(&types, "street");
    assert_eq!(count, 7);

    let count = count_types(&types, "city");
    assert_eq!(count, 3);

    let count = count_types(&types, "house");
    assert_eq!(count, 0);
}

fn zip_code_street_test(bragi: &BragiHandler) {
    let res = bragi.get("/autocomplete?q=77000 Lotissement le Clos de Givry");
    assert_eq!(res.len(), 1);
    let le_clos = &res[0];
    assert_eq!(le_clos["postcode"], "77000");
    assert_eq!(
        le_clos["label"],
        "Lotissement le Clos de Givry (Livry-sur-Seine)"
    );
    assert_eq!(le_clos["name"], "Lotissement le Clos de Givry");
    assert_eq!(le_clos["street"], "Lotissement le Clos de Givry");

    let boundary = le_clos["administrative_regions"].pointer("/0/boundary");
    assert_eq!(boundary, None);

    assert_eq!(le_clos["type"], "street");
    assert_eq!(le_clos["city"], "Livry-sur-Seine");
    assert_eq!(le_clos["citycode"], "77255");
}

fn zip_code_admin_test(bragi: &BragiHandler) {
    let all_20 = bragi.get("/autocomplete?q=77000 Vaux-le-Pénil");
    assert_eq!(all_20.len(), 4);
    assert!(get_values(&all_20, "postcode")
        .iter()
        .all(|r| *r == "77000",));
    let types = get_types(&all_20);
    let count = count_types(&types, "street");
    assert_eq!(count, 3);

    let count = count_types(&types, "city");
    assert_eq!(count, 1);
    let first_city = all_20.iter().find(|e| get_value(e, "type") == "city");
    assert_eq!(get_value(first_city.unwrap(), "citycode"), "77487");

    let count = count_types(&types, "house");
    assert_eq!(count, 0);
}

fn bbox_admin_test(bragi: &BragiHandler) {
    let all_20 = bragi.get("/autocomplete?q=77000 Vaux-le-Pénil");
    let first_city = all_20.iter().find(|e| get_value(e, "type") == "city");
    let result = first_city.unwrap().get("bbox").unwrap().as_array().unwrap();
    let expected = vec![2.663446, 48.5064094, 2.7334936, 48.5419672];
    assert_eq!(expected.len(), result.len());
    //check that coords are "close" from the expected value
    for (r, e) in result.iter().map(|r| r.as_f64().unwrap()).zip(expected) {
        assert_f64_near!(e, r, 1);
    }
}

fn city_admin_test(bragi: &BragiHandler) {
    let all_melun = bragi.get("/autocomplete?q=Melun Rp");
    let types = get_types(&all_melun);
    let count = count_types(&types, "city");
    assert!(count > 0);
}

fn administrative_region_test(bragi: &BragiHandler) {
    let all_creteil = bragi.get("/autocomplete?q=Créteil");
    let types = get_types(&all_creteil);
    let count = count_types(&types, "administrative_region");
    assert_eq!(count, 1);
}
<|endoftext|>