<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

//! This crate provides the Faucet service for creating and funding accounts on the Aptos Network.
//!
//! THIS SERVICE SHOULD NEVER BE DEPLOYED TO MAINNET.
//!
//! ## Launch service
//!
//! Launch faucet service locally and connect to Testnet:
//!
//! ```bash
//! cargo run --bin aptos-faucet -- -c TESTNET -m <mint-private-key-path> -s http://localhost:8080 -p 8081
//! ```
//!
//! Check help doc for options details:
//!
//! ```bash
//! cargo run -p aptos-faucet -- -h
//! ```

use anyhow::Result;
use aptos_config::keys::ConfigKey;
use aptos_crypto::ed25519::Ed25519PrivateKey;
use aptos_logger::info;
use aptos_rest_client::Client;
use aptos_sdk::{
    transaction_builder::{aptos_stdlib, TransactionFactory},
    types::{
        account_address::AccountAddress, account_config::aptos_test_root_address,
        chain_id::ChainId, LocalAccount,
    },
};
use clap::Parser;
use futures::lock::Mutex;
use reqwest::StatusCode;
use std::{convert::Infallible, fmt, path::PathBuf, sync::Arc};
use url::Url;
use warp::{http, Filter, Rejection, Reply};

pub mod mint;

/// Aptos Testnet utility service for creating test accounts and minting test coins
#[derive(Clone, Debug, Parser)]
#[clap(name = "Aptos Faucet", author, version)]
pub struct FaucetArgs {
    /// Faucet service listen address
    #[clap(short = 'a', long, default_value = "127.0.0.1")]
    pub address: String,
    /// Faucet service listen port
    #[clap(short = 'p', long, default_value = "80")]
    pub port: u16,
    /// Aptos fullnode/validator server URL
    #[clap(short = 's', long, default_value = "https://testnet.aptoslabs.com/")]
    pub server_url: Url,
    /// Path to the private key for creating test account and minting coins.
    /// To keep Testnet simple, we used one private key for aptos root account
    /// To manually generate a keypair, use generate-key:
    /// `cargo run -p generate-keypair -- -o <output_file_path>`
    #[clap(
        short = 'm',
        long,
        default_value = "/opt/aptos/etc/mint.key",
        parse(from_os_str)
    )]
    pub mint_key_file_path: PathBuf,
    /// Ed25519PrivateKey for minting coins
    #[clap(long, parse(try_from_str = ConfigKey::from_encoded_string))]
    pub mint_key: Option<ConfigKey<Ed25519PrivateKey>>,
    /// Address of the account to send transactions from.
    /// On Testnet, for example, this is a550c18.
    /// If not present, the mint key's address is used
    #[clap(short = 't', long, parse(try_from_str = AccountAddress::from_hex_literal))]
    pub mint_account_address: Option<AccountAddress>,
    /// Chain ID of the network this client is connecting to.
    /// For mainnet: "MAINNET" or 1, testnet: "TESTNET" or 2, devnet: "DEVNET" or 3,
    /// local swarm: "TESTING" or 4
    /// Note: Chain ID of 0 is not allowed; Use number if chain id is not predefined.
    #[clap(short = 'c', long, default_value = "2")]
    pub chain_id: ChainId,
    /// Maximum amount of coins to mint.
    #[clap(long)]
    pub maximum_amount: Option<u64>,
    #[clap(long)]
    pub do_not_delegate: bool,
}

impl FaucetArgs {
    pub async fn run(self) {
        let address: std::net::SocketAddr = format!("{}:{}", self.address, self.port)
            .parse()
            .expect("invalid address or port number");

        info!(
            "[faucet]: chain id: {}, server url: {} . Limit: {:?}",
            self.chain_id,
            self.server_url.as_str(),
            self.maximum_amount,
        );

        let key = if let Some(ref key) = self.mint_key {
            key.private_key()
        } else {
            bcs::from_bytes(
                &std::fs::read(self.mint_key_file_path.as_path())
                    .expect("Failed to read mint key file"),
            )
            .expect("Failed to deserialize mint key file")
        };

        let faucet_address: AccountAddress = self
            .mint_account_address
            .unwrap_or_else(aptos_test_root_address);
        let faucet_account = LocalAccount::new(faucet_address, key, 0);

        // Do not use maximum amount on delegation, this allows the new delegated faucet to
        // mint a lot for themselves!
        let maximum_amount = if self.do_not_delegate {
            self.maximum_amount
        } else {
            None
        };

        let service = Arc::new(Service::new(
            self.server_url.clone(),
            self.chain_id,
            faucet_account,
            maximum_amount,
        ));

        let actual_service = if self.do_not_delegate {
            service
        } else {
            delegate_mint_account(service, self.server_url, self.chain_id, self.maximum_amount)
                .await
        };

        println!("Faucet is running.  Faucet endpoint: {}", address);

        info!(
            "[faucet]: running on: {}. Minting from {}",
            address,
            actual_service.faucet_account.lock().await.address()
        );
        warp::serve(routes(actual_service)).run(address).await;
    }
}

pub struct Service {
    pub faucet_account: Mutex<LocalAccount>,
    pub transaction_factory: TransactionFactory,
    client: Client,
    endpoint: Url,
    maximum_amount: Option<u64>,
}

impl Service {
    pub fn new(
        endpoint: Url,
        chain_id: ChainId,
        faucet_account: LocalAccount,
        maximum_amount: Option<u64>,
    ) -> Self {
        let client = Client::new(endpoint.clone());
        Service {
            faucet_account: Mutex::new(faucet_account),
            transaction_factory: TransactionFactory::new(chain_id)
                .with_gas_unit_price(std::cmp::max(1, aptos_global_constants::GAS_UNIT_PRICE))
                .with_transaction_expiration_time(30),
            client,
            endpoint,
            maximum_amount,
        }
    }

    // By default the path is prefixed with the version, e.g. `v1/`. The fake
    // API used in the faucet tests doesn't have a versioned API however, so
    // we just set it to `/`.
    pub fn configure_for_testing(mut self) -> Self {
        self.client = self.client.version_path_base("/".to_string()).unwrap();
        self
    }

    pub fn endpoint(&self) -> &Url {
        &self.endpoint
    }
}

pub fn routes(
    service: Arc<Service>,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
    let mint = mint::mint_routes(service.clone());
    let health = health_route(service);

    health
        .or(mint)
        .with(warp::log::custom(|info| {
            info!(
                "{} \"{} {} {:?}\" {} \"{}\" \"{}\" {:?}",
                OptFmt(info.remote_addr()),
                info.method(),
                info.path(),
                info.version(),
                info.status().as_u16(),
                OptFmt(info.referer()),
                OptFmt(info.user_agent()),
                info.elapsed(),
            )
        }))
        .with(
            warp::cors()
                .allow_any_origin()
                .allow_headers(vec![http::header::CONTENT_TYPE])
                .allow_methods(vec!["POST"]),
        )
}

fn health_route(
    service: Arc<Service>,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
    warp::path!("health")
        .and(warp::get())
        .and(warp::any().map(move || service.clone()))
        .and_then(handle_health)
}

async fn handle_health(service: Arc<Service>) -> Result<Box<dyn warp::Reply>, Infallible> {
    let faucet_address = service.faucet_account.lock().await.address();
    let faucet_account = service.client.get_account(faucet_address).await;

    match faucet_account {
        Ok(account) => Ok(Box::new(account.inner().sequence_number.to_string())),
        Err(err) => Ok(Box::new(warp::reply::with_status(
            err.to_string(),
            StatusCode::INTERNAL_SERVER_ERROR,
        ))),
    }
}

//
// Common Types
//

struct OptFmt<T>(Option<T>);

impl<T: fmt::Display> fmt::Display for OptFmt<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(t) = &self.0 {
            fmt::Display::fmt(t, f)
        } else {
            f.write_str("-")
        }
    }
}

/// The idea is that this may be happening concurrently. If we end up in such a race, the faucets
/// might attempt to send transactions with the same sequence number, in such an event, one will
/// succeed and the other will hit an unwrap. Eventually all faucets should get online.
pub async fn delegate_mint_account(
    service: Arc<Service>,
    server_url: Url,
    chain_id: ChainId,
    maximum_amount: Option<u64>,
) -> Arc<Service> {
    // Create a new random account, then delegate to it
    let mut delegated_account = LocalAccount::generate(&mut rand::rngs::OsRng);

    // Create the account
    let response = mint::process(
        &service,
        mint::MintParams {
            amount: 100_000_000_000,
            auth_key: None,
            address: Some(
                delegated_account
                    .authentication_key()
                    .clone()
                    .derived_address()
                    .to_hex_literal(),
            ),
            pub_key: None,
            return_txns: Some(true),
        },
    )
    .await
    .expect("Failed to create new account");

    match response {
        mint::Response::SubmittedTxns(txns) => {
            for txn in txns {
                service
                    .client
                    .wait_for_signed_transaction(&txn)
                    .await
                    .unwrap();
            }
        }
        _ => panic!("Expected a set of Response::SubmittedTxns"),
    }

    // Delegate minting to the account
    {
        let mut faucet_account = service.faucet_account.lock().await;
        service
            .client
            .submit_and_wait(&faucet_account.sign_with_transaction_builder(
                service.transaction_factory.payload(
                    aptos_stdlib::aptos_coin_delegate_mint_capability(delegated_account.address()),
                ),
            ))
            .await
            .expect("Failed to delegate minting to the new account");
    }

    // claim the capability!
    service
        .client
        .submit_and_wait(
            &delegated_account.sign_with_transaction_builder(
                service
                    .transaction_factory
                    .payload(aptos_stdlib::aptos_coin_claim_mint_capability()),
            ),
        )
        .await
        .unwrap();

    Arc::new(Service::new(
        server_url,
        chain_id,
        delegated_account,
        maximum_amount,
    ))
}
<commit_msg>[aptos-faucet] Log x-forwarded-for header<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

//! This crate provides the Faucet service for creating and funding accounts on the Aptos Network.
//!
//! THIS SERVICE SHOULD NEVER BE DEPLOYED TO MAINNET.
//!
//! ## Launch service
//!
//! Launch faucet service locally and connect to Testnet:
//!
//! ```bash
//! cargo run --bin aptos-faucet -- -c TESTNET -m <mint-private-key-path> -s http://localhost:8080 -p 8081
//! ```
//!
//! Check help doc for options details:
//!
//! ```bash
//! cargo run -p aptos-faucet -- -h
//! ```

use anyhow::Result;
use aptos_config::keys::ConfigKey;
use aptos_crypto::ed25519::Ed25519PrivateKey;
use aptos_logger::info;
use aptos_rest_client::Client;
use aptos_sdk::{
    transaction_builder::{aptos_stdlib, TransactionFactory},
    types::{
        account_address::AccountAddress, account_config::aptos_test_root_address,
        chain_id::ChainId, LocalAccount,
    },
};
use clap::Parser;
use futures::lock::Mutex;
use reqwest::StatusCode;
use std::{convert::Infallible, fmt, path::PathBuf, sync::Arc};
use url::Url;
use warp::{http, Filter, Rejection, Reply};

pub mod mint;

/// Aptos Testnet utility service for creating test accounts and minting test coins
#[derive(Clone, Debug, Parser)]
#[clap(name = "Aptos Faucet", author, version)]
pub struct FaucetArgs {
    /// Faucet service listen address
    #[clap(short = 'a', long, default_value = "127.0.0.1")]
    pub address: String,
    /// Faucet service listen port
    #[clap(short = 'p', long, default_value = "80")]
    pub port: u16,
    /// Aptos fullnode/validator server URL
    #[clap(short = 's', long, default_value = "https://testnet.aptoslabs.com/")]
    pub server_url: Url,
    /// Path to the private key for creating test account and minting coins.
    /// To keep Testnet simple, we used one private key for aptos root account
    /// To manually generate a keypair, use generate-key:
    /// `cargo run -p generate-keypair -- -o <output_file_path>`
    #[clap(
        short = 'm',
        long,
        default_value = "/opt/aptos/etc/mint.key",
        parse(from_os_str)
    )]
    pub mint_key_file_path: PathBuf,
    /// Ed25519PrivateKey for minting coins
    #[clap(long, parse(try_from_str = ConfigKey::from_encoded_string))]
    pub mint_key: Option<ConfigKey<Ed25519PrivateKey>>,
    /// Address of the account to send transactions from.
    /// On Testnet, for example, this is a550c18.
    /// If not present, the mint key's address is used
    #[clap(short = 't', long, parse(try_from_str = AccountAddress::from_hex_literal))]
    pub mint_account_address: Option<AccountAddress>,
    /// Chain ID of the network this client is connecting to.
    /// For mainnet: "MAINNET" or 1, testnet: "TESTNET" or 2, devnet: "DEVNET" or 3,
    /// local swarm: "TESTING" or 4
    /// Note: Chain ID of 0 is not allowed; Use number if chain id is not predefined.
    #[clap(short = 'c', long, default_value = "2")]
    pub chain_id: ChainId,
    /// Maximum amount of coins to mint.
    #[clap(long)]
    pub maximum_amount: Option<u64>,
    #[clap(long)]
    pub do_not_delegate: bool,
}

impl FaucetArgs {
    pub async fn run(self) {
        let address: std::net::SocketAddr = format!("{}:{}", self.address, self.port)
            .parse()
            .expect("invalid address or port number");

        info!(
            "[faucet]: chain id: {}, server url: {} . Limit: {:?}",
            self.chain_id,
            self.server_url.as_str(),
            self.maximum_amount,
        );

        let key = if let Some(ref key) = self.mint_key {
            key.private_key()
        } else {
            bcs::from_bytes(
                &std::fs::read(self.mint_key_file_path.as_path())
                    .expect("Failed to read mint key file"),
            )
            .expect("Failed to deserialize mint key file")
        };

        let faucet_address: AccountAddress = self
            .mint_account_address
            .unwrap_or_else(aptos_test_root_address);
        let faucet_account = LocalAccount::new(faucet_address, key, 0);

        // Do not use maximum amount on delegation, this allows the new delegated faucet to
        // mint a lot for themselves!
        let maximum_amount = if self.do_not_delegate {
            self.maximum_amount
        } else {
            None
        };

        let service = Arc::new(Service::new(
            self.server_url.clone(),
            self.chain_id,
            faucet_account,
            maximum_amount,
        ));

        let actual_service = if self.do_not_delegate {
            service
        } else {
            delegate_mint_account(service, self.server_url, self.chain_id, self.maximum_amount)
                .await
        };

        println!("Faucet is running.  Faucet endpoint: {}", address);

        info!(
            "[faucet]: running on: {}. Minting from {}",
            address,
            actual_service.faucet_account.lock().await.address()
        );
        warp::serve(routes(actual_service)).run(address).await;
    }
}

pub struct Service {
    pub faucet_account: Mutex<LocalAccount>,
    pub transaction_factory: TransactionFactory,
    client: Client,
    endpoint: Url,
    maximum_amount: Option<u64>,
}

impl Service {
    pub fn new(
        endpoint: Url,
        chain_id: ChainId,
        faucet_account: LocalAccount,
        maximum_amount: Option<u64>,
    ) -> Self {
        let client = Client::new(endpoint.clone());
        Service {
            faucet_account: Mutex::new(faucet_account),
            transaction_factory: TransactionFactory::new(chain_id)
                .with_gas_unit_price(std::cmp::max(1, aptos_global_constants::GAS_UNIT_PRICE))
                .with_transaction_expiration_time(30),
            client,
            endpoint,
            maximum_amount,
        }
    }

    // By default the path is prefixed with the version, e.g. `v1/`. The fake
    // API used in the faucet tests doesn't have a versioned API however, so
    // we just set it to `/`.
    pub fn configure_for_testing(mut self) -> Self {
        self.client = self.client.version_path_base("/".to_string()).unwrap();
        self
    }

    pub fn endpoint(&self) -> &Url {
        &self.endpoint
    }
}

pub fn routes(
    service: Arc<Service>,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
    let mint = mint::mint_routes(service.clone());
    let health = health_route(service);

    health
        .or(mint)
        .with(warp::log::custom(|info| {
            let actual_ip = info
                .request_headers()
                .get("x-forwarded-for")
                .map(|inner| inner.to_str().unwrap_or("-"));
            info!(
                "{} \"{} {} {:?}\" {} \"{}\" \"{}\" \"{}\" {:?}",
                OptFmt(info.remote_addr()),
                info.method(),
                info.path(),
                info.version(),
                info.status().as_u16(),
                OptFmt(info.referer()),
                OptFmt(info.user_agent()),
                OptFmt(actual_ip),
                info.elapsed(),
            )
        }))
        .with(
            warp::cors()
                .allow_any_origin()
                .allow_headers(vec![http::header::CONTENT_TYPE])
                .allow_methods(vec!["POST"]),
        )
}

fn health_route(
    service: Arc<Service>,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
    warp::path!("health")
        .and(warp::get())
        .and(warp::any().map(move || service.clone()))
        .and_then(handle_health)
}

async fn handle_health(service: Arc<Service>) -> Result<Box<dyn warp::Reply>, Infallible> {
    let faucet_address = service.faucet_account.lock().await.address();
    let faucet_account = service.client.get_account(faucet_address).await;

    match faucet_account {
        Ok(account) => Ok(Box::new(account.inner().sequence_number.to_string())),
        Err(err) => Ok(Box::new(warp::reply::with_status(
            err.to_string(),
            StatusCode::INTERNAL_SERVER_ERROR,
        ))),
    }
}

//
// Common Types
//

struct OptFmt<T>(Option<T>);

impl<T: fmt::Display> fmt::Display for OptFmt<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(t) = &self.0 {
            fmt::Display::fmt(t, f)
        } else {
            f.write_str("-")
        }
    }
}

/// The idea is that this may be happening concurrently. If we end up in such a race, the faucets
/// might attempt to send transactions with the same sequence number, in such an event, one will
/// succeed and the other will hit an unwrap. Eventually all faucets should get online.
pub async fn delegate_mint_account(
    service: Arc<Service>,
    server_url: Url,
    chain_id: ChainId,
    maximum_amount: Option<u64>,
) -> Arc<Service> {
    // Create a new random account, then delegate to it
    let mut delegated_account = LocalAccount::generate(&mut rand::rngs::OsRng);

    // Create the account
    let response = mint::process(
        &service,
        mint::MintParams {
            amount: 100_000_000_000,
            auth_key: None,
            address: Some(
                delegated_account
                    .authentication_key()
                    .clone()
                    .derived_address()
                    .to_hex_literal(),
            ),
            pub_key: None,
            return_txns: Some(true),
        },
    )
    .await
    .expect("Failed to create new account");

    match response {
        mint::Response::SubmittedTxns(txns) => {
            for txn in txns {
                service
                    .client
                    .wait_for_signed_transaction(&txn)
                    .await
                    .unwrap();
            }
        }
        _ => panic!("Expected a set of Response::SubmittedTxns"),
    }

    // Delegate minting to the account
    {
        let mut faucet_account = service.faucet_account.lock().await;
        service
            .client
            .submit_and_wait(&faucet_account.sign_with_transaction_builder(
                service.transaction_factory.payload(
                    aptos_stdlib::aptos_coin_delegate_mint_capability(delegated_account.address()),
                ),
            ))
            .await
            .expect("Failed to delegate minting to the new account");
    }

    // claim the capability!
    service
        .client
        .submit_and_wait(
            &delegated_account.sign_with_transaction_builder(
                service
                    .transaction_factory
                    .payload(aptos_stdlib::aptos_coin_claim_mint_capability()),
            ),
        )
        .await
        .unwrap();

    Arc::new(Service::new(
        server_url,
        chain_id,
        delegated_account,
        maximum_amount,
    ))
}
<|endoftext|>


<commit_before>// Copyright (C) 2017, 2018 Falco Hirschenberger, Élisabeth Henry.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use book::Book;
use text_view::view_as_text;
use style;

use std::fmt;
use std::f64;
use hyphenation;
use hyphenation::{Hyphenation, Language};
#[cfg(feature = "nightly")]
use punkt::{SentenceTokenizer, TrainingData};
#[cfg(feature = "nightly")]
use punkt::params::Standard;

/* Only collected on nightly */
struct AdvancedStats {
    pub sentence_count: usize,
    pub flesch_score: f64,
}

struct ChapterStats {
    pub name: String,
    pub word_count: usize,
    pub char_count: usize,
    pub syllable_count: usize,
    pub advanced: Option<AdvancedStats>,
}

impl ChapterStats {
    #[cfg(feature = "nightly")]
    pub fn fill_advanced(&mut self, lang: Language, text: &str) {
        let (td, flesch_func) = Stats::language_data(lang);
        let sc = SentenceTokenizer::<Standard>::new(&text, &td).count();
        let stats = AdvancedStats {
            sentence_count: sc,
            flesch_score: f64::NAN,
        };
        self.advanced = Some(stats);
        let score = if let Some(ref f) = flesch_func {
            f(self)
        } else {
            f64::NAN
        };
        if let Some(ref mut adv) = self.advanced {
            adv.flesch_score = score;
        }
    }

    #[cfg(not(feature = "nightly"))]
    pub fn fill_advanced(&mut self, _: Language, _: &str) {
    }
}

pub struct Stats {
    chapters: Vec<ChapterStats>,
    advanced: bool,
}

impl Stats {
    pub fn new(book: &Book, advanced: bool) -> Stats {
        let lang = book.options.get_str("lang").unwrap();
        let lang = Stats::language_from_str(lang);
        let mut stats = Stats { chapters: vec![], advanced: advanced };

        for c in &book.chapters {
            let name = c.filename.clone();
            let text = view_as_text(&c.content);
            let words: Vec<_> = text.split_whitespace().collect();
            let wc = words.len();
            // Note: Don't count the bytes with `len()` count the actual (multibyte-)characters
            let cc = text.chars().count();
            let corp = hyphenation::load(lang).unwrap();
            // Count the number of syllables for earch word.
            let syl = words
                .iter()
                .fold(0, |acc, w| acc + w.opportunities(&corp).len() + 1);
            let mut chapter_stats = ChapterStats {
                name: name,
                word_count: wc,
                char_count: cc,
                syllable_count: syl,
                advanced: None,
            };
            if advanced {
                chapter_stats.fill_advanced(lang, &text);
            }
            stats.chapters.push(chapter_stats);
        }
        stats
    }

    // Returns the Languuage (defined by Hyphenation crate) according to the str code
    fn language_from_str(lang: &str) -> Language {
        // FIXME: handle case where lang is e.g. fr_FR or en_GB
        match lang {
            "cz" => Language::Czech,
            "da" => Language::Danish,
            "nl" => Language::Dutch,
            "en" => Language::English_GB,
            "et" => Language::Estonian,
            "fi" => Language::Finnish,
            "fr" => Language::French,
            "de" => Language::German_1996,
            "el" => Language::Greek_Poly,
            "it" => Language::Italian,
            "no" => Language::Norwegian_Bokmal,
            "pl" => Language::Polish,
            "pt" => Language::Portuguese,
            "sl" => Language::Slovenian,
            "es" => Language::Spanish,
            "sv" => Language::Swedish,
            "tk" => Language::Turkish,
            _ =>  {
                warn!(
                    //FIXME: display localized warning (or use Result?)
                    "Unknown language: '{}' for text statistics, using 'en' default.",
                    lang
                );
                Language::English_GB
            },
        }
    }

    // The Flesch reading index formulae for different languages are from the `YoastSEO.js` text
    // analysis library. See: https://github.com/Yoast/YoastSEO.js/issues/267
    #[cfg(feature = "nightly")]
    fn language_data(
        lang: Language
    ) -> (
        TrainingData,
        Option<Box<Fn(&ChapterStats) -> f64>>,
    ) {
        match lang {
            Language::Czech => (TrainingData::czech(), None),
            Language::Danish => (TrainingData::danish(), None),
            Language::Dutch => (
                TrainingData::dutch(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        206.84 - 77.0 * (s.syllable_count as f64 / s.word_count as f64)
                            - 0.93 * (s.word_count as f64 / adv.sentence_count as f64)
                    } else {
                        unreachable!("If nightly build is set, it should always return Some(thing)");
                    }
                })),
            ),
            Language::English_GB => (
                TrainingData::english(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                    206.835 - 0.77 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                        - 0.93 * (s.word_count as f64 / adv.sentence_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::Estonian => (TrainingData::estonian(), None),
            Language::Finnish => (TrainingData::finnish(), None),
            Language::French => (
                TrainingData::french(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        207.0 - 1.015 * (s.word_count as f64 / adv.sentence_count as f64)
                            - 73.6 * (s.syllable_count as f64 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::German_1996 => (
                TrainingData::german(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        180.0 - (s.word_count as f64 / adv.sentence_count as f64)
                            - 84.6 * (s.syllable_count as f64 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }
                })),
            ),
            Language::Greek_Poly => (TrainingData::greek(), None),
            Language::Italian => (
                TrainingData::italian(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        217.0 - 1.3 * (s.word_count as f64 / adv.sentence_count as f64)
                            - 60.0 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }
                })),
            ),
            Language::Norwegian_Bokmal => (TrainingData::norwegian(), None),
            Language::Polish => (TrainingData::polish(), None),
            Language::Portuguese => (TrainingData::portuguese(), None),
            Language::Slovenian => (TrainingData::slovene(), None),
            Language::Spanish => (
                TrainingData::spanish(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        206.84 - 1.02 * (s.word_count as f64 / adv.sentence_count as f64)
                        - 60.0 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::Swedish => (TrainingData::swedish(), None),
            Language::Turkish => (TrainingData::turkish(), None),
            _ => {
                unreachable!("language_data should handle all casess returned by language_from_str");
            },
        }
    }

    fn flesch_text(score: f64) -> String {
        String::from(match score {
            s if s.is_nan() => "Not availabe",
            s if s < 30.0 => "Very difficult",
            s if s < 50.0 => "Difficult",
            s if s < 60.0 => "Fairly difficult",
            s if s < 70.0 => "Standard",
            s if s < 80.0 => "Fairly easy",
            s if s < 90.0 => "Easy",
            _ => "Very Easy",
        })
    }
}

impl fmt::Display for Stats {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let max_chapter_length = self.chapters
            .iter()
            .max_by_key(|e| e.name.chars().count())
            .unwrap()
            .name
            .chars()
            .count() + 3;
        if self.advanced {
            write!(
                f,
                "{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11} {:>16} {:>29}\n---------\n",
                style::header(&lformat!("Chapter")),
                style::header(&lformat!("Chars")),
                style::header(&lformat!("Syllables")),
                style::header(&lformat!("Words")),
                style::header(&lformat!("Sentences")),
                style::header(&lformat!("Chars/Word")),
                style::header(&lformat!("Words/Sentence")),
                style::header(&lformat!("Flesch reading ease index")),
                width = max_chapter_length
            )?;
        } else {
            write!(
                f,
                "{:<width$} {:>8} {:>10} {:>11}\n---------\n",
                style::header(&lformat!("Chapter")),
                style::header(&lformat!("Chars")),
                style::header(&lformat!("Words")),
                style::header(&lformat!("Chars/Word")),
                width = max_chapter_length
            )?;
        }
        for c in &self.chapters {
            if let Some(ref adv) = c.advanced {
                write!(
                    f,
                    "{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11.2} {:>16.2} {:>8.1} => {:>17}\n",
                    style::element(&c.name),
                    c.char_count,
                    c.syllable_count,
                    c.word_count,
                    adv.sentence_count,
                    c.char_count as f64 / c.word_count as f64,
                    c.word_count as f64 / adv.sentence_count as f64,
                    adv.flesch_score,
                    Self::flesch_text(adv.flesch_score),
                    width = max_chapter_length
                )?;
            } else {
                write!(
                    f,
                    "{:<width$} {:>8} {:>10} {:>11.2}\n",
                    style::element(&c.name),
                    c.char_count,
                    c.word_count,
                    c.char_count as f64 / c.word_count as f64,
                    width = max_chapter_length
                )?;
            }
        }
        let total = self.chapters.iter().fold((0, 0, 0, 0, 0.0, 0), |acc, c| {
            let sentence_count;
            let flesch_score;
            if let Some(ref adv) = c.advanced {
                sentence_count = adv.sentence_count;
                flesch_score = adv.flesch_score;
            } else {
                sentence_count = 0;
                flesch_score = f64::NAN;
            };
            (
                acc.0 + c.char_count,
                acc.1 + c.syllable_count,
                acc.2 + c.word_count,
                acc.3 + sentence_count,
                acc.4 + flesch_score,
                acc.5 + 1,
            )
        });
        if self.advanced {
            write!(
                f,
                "---------\n{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11.2} {:>16.2} {:>8.1} => {:>17}\n",
                style::element(&lformat!("TOTAL:")),
                total.0,
                total.1,
                total.2,
                total.3,
                total.0 as f64 / total.2 as f64,
                total.2 as f64 / total.3 as f64,
                total.4 / total.5 as f64,
                Self::flesch_text(total.4 / total.5 as f64),
                width = max_chapter_length
            )
        } else {
            write!(
                f,
                "---------\n{:<width$} {:>8} {:>10} {:>11.2}\n",
                style::element(&lformat!("TOTAL:")),
                total.0,
                total.2,
                total.0 as f64 / total.2 as f64,
                width = max_chapter_length
            )
        }
    }
}
<commit_msg>Fix display of stats when using advanced and feature is not built<commit_after>// Copyright (C) 2017, 2018 Falco Hirschenberger, Élisabeth Henry.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use book::Book;
use text_view::view_as_text;
use style;

use std::fmt;
use std::f64;
use hyphenation;
use hyphenation::{Hyphenation, Language};
#[cfg(feature = "nightly")]
use punkt::{SentenceTokenizer, TrainingData};
#[cfg(feature = "nightly")]
use punkt::params::Standard;

/* Only collected on nightly */
struct AdvancedStats {
    pub sentence_count: usize,
    pub flesch_score: f64,
}

struct ChapterStats {
    pub name: String,
    pub word_count: usize,
    pub char_count: usize,
    pub syllable_count: usize,
    pub advanced: Option<AdvancedStats>,
}

impl ChapterStats {
    #[cfg(feature = "nightly")]
    pub fn fill_advanced(&mut self, lang: Language, text: &str) {
        let (td, flesch_func) = Stats::language_data(lang);
        let sc = SentenceTokenizer::<Standard>::new(&text, &td).count();
        let stats = AdvancedStats {
            sentence_count: sc,
            flesch_score: f64::NAN,
        };
        self.advanced = Some(stats);
        let score = if let Some(ref f) = flesch_func {
            f(self)
        } else {
            f64::NAN
        };
        if let Some(ref mut adv) = self.advanced {
            adv.flesch_score = score;
        }
    }

    #[cfg(not(feature = "nightly"))]
    pub fn fill_advanced(&mut self, _: Language, _: &str) {
    }
}

pub struct Stats {
    chapters: Vec<ChapterStats>,
    advanced: bool,
}

impl Stats {
    pub fn new(book: &Book, advanced: bool) -> Stats {
        let lang = book.options.get_str("lang").unwrap();
        let lang = Stats::language_from_str(lang);

        let mut stats;

        if cfg!(not(feature = "nightly")) {
            if advanced {
                warn!("{}", lformat!("This version of crowboook has been compiled without support for advanced statistics"));
            }
            stats = Stats { chapters: vec![], advanced: false };
        } else {
            stats = Stats { chapters: vec![], advanced: advanced };
            if !advanced {
                info!("{}", lformat!("For more advanced statistics, use the --verbose or -v option"));
            }
        }

        for c in &book.chapters {
            let name = c.filename.clone();
            let text = view_as_text(&c.content);
            let words: Vec<_> = text.split_whitespace().collect();
            let wc = words.len();
            // Note: Don't count the bytes with `len()` count the actual (multibyte-)characters
            let cc = text.chars().count();
            let corp = hyphenation::load(lang).unwrap();
            // Count the number of syllables for earch word.
            let syl = words
                .iter()
                .fold(0, |acc, w| acc + w.opportunities(&corp).len() + 1);
            let mut chapter_stats = ChapterStats {
                name: name,
                word_count: wc,
                char_count: cc,
                syllable_count: syl,
                advanced: None,
            };
            if advanced {
                chapter_stats.fill_advanced(lang, &text);
            }
            stats.chapters.push(chapter_stats);
        }
        stats
    }

    // Returns the Languuage (defined by Hyphenation crate) according to the str code
    fn language_from_str(lang: &str) -> Language {
        // FIXME: handle case where lang is e.g. fr_FR or en_GB
        match lang {
            "cz" => Language::Czech,
            "da" => Language::Danish,
            "nl" => Language::Dutch,
            "en" => Language::English_GB,
            "et" => Language::Estonian,
            "fi" => Language::Finnish,
            "fr" => Language::French,
            "de" => Language::German_1996,
            "el" => Language::Greek_Poly,
            "it" => Language::Italian,
            "no" => Language::Norwegian_Bokmal,
            "pl" => Language::Polish,
            "pt" => Language::Portuguese,
            "sl" => Language::Slovenian,
            "es" => Language::Spanish,
            "sv" => Language::Swedish,
            "tk" => Language::Turkish,
            _ =>  {
                warn!(
                    //FIXME: display localized warning (or use Result?)
                    "Unknown language: '{}' for text statistics, using 'en' default.",
                    lang
                );
                Language::English_GB
            },
        }
    }

    // The Flesch reading index formulae for different languages are from the `YoastSEO.js` text
    // analysis library. See: https://github.com/Yoast/YoastSEO.js/issues/267
    #[cfg(feature = "nightly")]
    fn language_data(
        lang: Language
    ) -> (
        TrainingData,
        Option<Box<Fn(&ChapterStats) -> f64>>,
    ) {
        match lang {
            Language::Czech => (TrainingData::czech(), None),
            Language::Danish => (TrainingData::danish(), None),
            Language::Dutch => (
                TrainingData::dutch(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        206.84 - 77.0 * (s.syllable_count as f64 / s.word_count as f64)
                            - 0.93 * (s.word_count as f64 / adv.sentence_count as f64)
                    } else {
                        unreachable!("If nightly build is set, it should always return Some(thing)");
                    }
                })),
            ),
            Language::English_GB => (
                TrainingData::english(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                    206.835 - 0.77 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                        - 0.93 * (s.word_count as f64 / adv.sentence_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::Estonian => (TrainingData::estonian(), None),
            Language::Finnish => (TrainingData::finnish(), None),
            Language::French => (
                TrainingData::french(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        207.0 - 1.015 * (s.word_count as f64 / adv.sentence_count as f64)
                            - 73.6 * (s.syllable_count as f64 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::German_1996 => (
                TrainingData::german(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        180.0 - (s.word_count as f64 / adv.sentence_count as f64)
                            - 84.6 * (s.syllable_count as f64 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }
                })),
            ),
            Language::Greek_Poly => (TrainingData::greek(), None),
            Language::Italian => (
                TrainingData::italian(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        217.0 - 1.3 * (s.word_count as f64 / adv.sentence_count as f64)
                            - 60.0 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }
                })),
            ),
            Language::Norwegian_Bokmal => (TrainingData::norwegian(), None),
            Language::Polish => (TrainingData::polish(), None),
            Language::Portuguese => (TrainingData::portuguese(), None),
            Language::Slovenian => (TrainingData::slovene(), None),
            Language::Spanish => (
                TrainingData::spanish(),
                Some(Box::new(|s: &ChapterStats| {
                    if let Some(ref adv) = s.advanced {
                        206.84 - 1.02 * (s.word_count as f64 / adv.sentence_count as f64)
                        - 60.0 * (s.syllable_count as f64 * 100.0 / s.word_count as f64)
                    } else {
                        unreachable!();
                    }})),
            ),
            Language::Swedish => (TrainingData::swedish(), None),
            Language::Turkish => (TrainingData::turkish(), None),
            _ => {
                unreachable!("language_data should handle all casess returned by language_from_str");
            },
        }
    }

    fn flesch_text(score: f64) -> String {
        String::from(match score {
            s if s.is_nan() => "Not availabe",
            s if s < 30.0 => "Very difficult",
            s if s < 50.0 => "Difficult",
            s if s < 60.0 => "Fairly difficult",
            s if s < 70.0 => "Standard",
            s if s < 80.0 => "Fairly easy",
            s if s < 90.0 => "Easy",
            _ => "Very Easy",
        })
    }
}

impl fmt::Display for Stats {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let max_chapter_length = self.chapters
            .iter()
            .max_by_key(|e| e.name.chars().count())
            .unwrap()
            .name
            .chars()
            .count() + 3;
        if self.advanced {
            write!(
                f,
                "{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11} {:>16} {:>29}\n---------\n",
                style::header(&lformat!("Chapter")),
                style::header(&lformat!("Chars")),
                style::header(&lformat!("Syllables")),
                style::header(&lformat!("Words")),
                style::header(&lformat!("Sentences")),
                style::header(&lformat!("Chars/Word")),
                style::header(&lformat!("Words/Sentence")),
                style::header(&lformat!("Flesch reading ease index")),
                width = max_chapter_length
            )?;
        } else {
            write!(
                f,
                "{:<width$} {:>8} {:>10} {:>11}\n---------\n",
                style::header(&lformat!("Chapter")),
                style::header(&lformat!("Chars")),
                style::header(&lformat!("Words")),
                style::header(&lformat!("Chars/Word")),
                width = max_chapter_length
            )?;
        }
        for c in &self.chapters {
            if let Some(ref adv) = c.advanced {
                write!(
                    f,
                    "{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11.2} {:>16.2} {:>8.1} => {:>17}\n",
                    style::element(&c.name),
                    c.char_count,
                    c.syllable_count,
                    c.word_count,
                    adv.sentence_count,
                    c.char_count as f64 / c.word_count as f64,
                    c.word_count as f64 / adv.sentence_count as f64,
                    adv.flesch_score,
                    Self::flesch_text(adv.flesch_score),
                    width = max_chapter_length
                )?;
            } else {
                write!(
                    f,
                    "{:<width$} {:>8} {:>10} {:>11.2}\n",
                    style::element(&c.name),
                    c.char_count,
                    c.word_count,
                    c.char_count as f64 / c.word_count as f64,
                    width = max_chapter_length
                )?;
            }
        }
        let total = self.chapters.iter().fold((0, 0, 0, 0, 0.0, 0), |acc, c| {
            let sentence_count;
            let flesch_score;
            if let Some(ref adv) = c.advanced {
                sentence_count = adv.sentence_count;
                flesch_score = adv.flesch_score;
            } else {
                sentence_count = 0;
                flesch_score = f64::NAN;
            };
            (
                acc.0 + c.char_count,
                acc.1 + c.syllable_count,
                acc.2 + c.word_count,
                acc.3 + sentence_count,
                acc.4 + flesch_score,
                acc.5 + 1,
            )
        });
        if self.advanced {
            write!(
                f,
                "---------\n{:<width$} {:>8} {:>10} {:>7} {:>11} {:>11.2} {:>16.2} {:>8.1} => {:>17}\n",
                style::element(&lformat!("TOTAL:")),
                total.0,
                total.1,
                total.2,
                total.3,
                total.0 as f64 / total.2 as f64,
                total.2 as f64 / total.3 as f64,
                total.4 / total.5 as f64,
                Self::flesch_text(total.4 / total.5 as f64),
                width = max_chapter_length
            )
        } else {
            write!(
                f,
                "---------\n{:<width$} {:>8} {:>10} {:>11.2}\n",
                style::element(&lformat!("TOTAL:")),
                total.0,
                total.2,
                total.0 as f64 / total.2 as f64,
                width = max_chapter_length
            )
        }
    }
}
<|endoftext|>


<commit_before>use time::SteadyTime;

use std::ops::Add;
use std::time::Duration;

/// The current time
///
/// This value is similar to (and directly derived from) the
/// `time::SteadyTime`.  But it has three important properties:
///
/// 1. It has a millisecond precision
/// 2. It's size is 8 bytes (SteadyTime is 16 bytes)
/// 3. It supports math with `std::time::Duration` (more future-proof)
///
/// The size of the value is important because we are going to have a lot of
/// timeouts and lots of timestamps stored inside the state machines.
///
/// Precision of millisecond is good enough, and even better for our use case
/// as it allows faster comparison and more frequent matches.
///
/// Warning: when adding a duration that is not a multiple of a millisecond
/// we truncate (i.e. floor) the duration value. We may change this in future.
/// Note that for timeouts this works well enough, as mio already bumps the
/// timeout to at least a millisecond ahead.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Time(u64);

impl Add<Duration> for Time {
    type Output = Time;
    fn add(self, rhs: Duration) -> Time {
        Time(self.0 + rhs.as_secs()*1000 + (rhs.subsec_nanos()/1000000) as u64)
    }
}

impl Time {
    /// Zero time value, should be used only as a starting point for unit
    /// tests
    pub fn zero() -> Time {
        // In fact we don't care actual value, but the 1 allows us to
        // implement NonZero in the future
        Time(1)
    }
}

pub fn make_time(base: SteadyTime, now: SteadyTime) -> Time {
    Time((now - base).num_milliseconds() as u64)
}

pub fn diff_ms(now: Time, event: Time) -> u64 {
    if event.0 > now.0 {
        event.0 - now.0
    } else {
        0
    }
}
<commit_msg>Add tests for time calculations<commit_after>use time::SteadyTime;

use std::ops::Add;
use std::time::Duration;

/// The current time
///
/// This value is similar to (and directly derived from) the
/// `time::SteadyTime`.  But it has three important properties:
///
/// 1. It has a millisecond precision
/// 2. It's size is 8 bytes (SteadyTime is 16 bytes)
/// 3. It supports math with `std::time::Duration` (more future-proof)
///
/// The size of the value is important because we are going to have a lot of
/// timeouts and lots of timestamps stored inside the state machines.
///
/// Precision of millisecond is good enough, and even better for our use case
/// as it allows faster comparison and more frequent matches.
///
/// Warning: when adding a duration that is not a multiple of a millisecond
/// we truncate (i.e. floor) the duration value. We may change this in future.
/// Note that for timeouts this works well enough, as mio already bumps the
/// timeout to at least a millisecond ahead.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Time(u64);

impl Add<Duration> for Time {
    type Output = Time;
    fn add(self, rhs: Duration) -> Time {
        Time(self.0 + rhs.as_secs()*1000 + (rhs.subsec_nanos()/1000000) as u64)
    }
}

impl Time {
    /// Zero time value, should be used only as a starting point for unit
    /// tests
    pub fn zero() -> Time {
        // In fact we don't care actual value, but the 1 allows us to
        // implement NonZero in the future
        Time(1)
    }
}

pub fn make_time(base: SteadyTime, now: SteadyTime) -> Time {
    Time((now - base).num_milliseconds() as u64)
}

pub fn diff_ms(now: Time, event: Time) -> u64 {
    if event.0 > now.0 {
        event.0 - now.0
    } else {
        0
    }
}

#[cfg(test)]
mod test {
    use super::Time;
    use std::time::Duration;

    #[test]
    fn test_add_duration() {
        let tm = Time::zero();
        assert_eq!(tm + Duration::new(10, 0), Time(10001));
        assert_eq!(tm + Duration::from_millis(150), Time(151));
        assert_eq!(tm + Duration::from_millis(12345), Time(12346));
        assert_eq!(tm + Duration::new(5, 0) + Duration::from_millis(20),
                   Time(5021));
    }

}
<|endoftext|>


<commit_before>// Copyright:: Copyright (c) 2015-2016 Chef Software, Inc.
//
// The terms of the Evaluation Agreement (Bldr) between Chef Software Inc. and the party accessing
// this file ("Licensee") apply to Licensee's use of the Software until such time that the Software
// is made available under an open source license such as the Apache 2.0 License.

use util::{self, docker};
use setup;
use regex::Regex;
use std::thread;

#[test]
fn standalone_no_options() {
    setup::gpg_import();
    setup::simple_service();

    let d = docker::run("test/simple_service");
    if d.wait_until(r"Shipping out to Boston") {
        let output = d.logs();
        assert_regex!(&output, r"Starting (.+)");
    }
}

#[test]
fn standalone_no_options_without_config() {
    setup::gpg_import();
    setup::fixture_service("simple_service_without_config");

    let d = docker::run("test/simple_service_without_config");
    if d.wait_until(r"Shipping out to Boston") {
        let output = d.logs();
        assert_regex!(&output, r"Starting (.+)");
    } else {
        // container didn't start in time or pkg doesn't exist
        assert!(false);
    }
}

#[test]
fn standalone_with_environment_config() {
    setup::gpg_import();
    setup::simple_service();

    let d = docker::run_with_env("test/simple_service",
                                 "BLDR_simple_service=setting=\"blarg\"");
    if d.wait_until(r"End Configuration") {
        let output = d.logs();
        assert_regex!(&output, r"setting: blarg");
    }
}

#[test]
fn standalone_with_discovery_config() {
    setup::gpg_import();
    setup::simple_service();

    util::discovery::clear("config");
    util::discovery::set("config", "setting = \"sepultura\"");

    let d = docker::run_with_etcd("test/simple_service");
    assert_docker_log!(d, r"setting: sepultura");
}

#[test]
fn standalone_with_discovery_config_updates() {
    setup::gpg_import();
    setup::simple_service();

    util::discovery::clear("config");

    util::discovery::set("config", "setting = \"sepultura\"");
    let d = docker::run_with_etcd("test/simple_service");
    assert_docker_log!(d, r"setting: sepultura");

    util::discovery::set("config", "setting = \"against me!\"");
    assert_docker_log!(d, r"setting: against me!");
}

#[test]
fn leader_with_discovery() {
    setup::gpg_import();
    setup::simple_service();

    util::discovery::clear("config");
    util::discovery::clear("topology");

    util::discovery::set("config", "setting = \"rustacean\"");
    let d1 = docker::run_with_etcd_topology("test/simple_service", "leader");
    let d2 = docker::run_with_etcd_topology("test/simple_service", "leader");
    let d3 = docker::run_with_etcd_topology("test/simple_service", "leader");

    assert_docker_log_count!(1, "Starting my term as leader", [d1, d2, d3]);
    assert_docker_log_count!(2, "Becoming a follower", [d1, d2, d3]);

    assert_docker_log!(d1, r"setting: rustacean");
    assert_docker_log!(d2, r"setting: rustacean");
    assert_docker_log!(d3, r"setting: rustacean");

    util::discovery::set("config", "setting = \"against me!\"");
    assert_docker_log!(d1, r"setting: against me!");
    assert_docker_log!(d2, r"setting: against me!");
    assert_docker_log!(d3, r"setting: against me!");

    let re = Regex::new(r"Starting my term as leader").unwrap();
    if re.is_match(&d1.logs()) {
        drop(d1);
        thread::sleep_ms(32000);
        assert_docker_log_count!(1, "Starting my term as leader", [d2, d3]);
    } else if re.is_match(&d2.logs()) {
        drop(d2);
        thread::sleep_ms(32000);
        assert_docker_log_count!(1, "Starting my term as leader", [d1, d3]);
    } else if re.is_match(&d3.logs()) {
        drop(d3);
        thread::sleep_ms(32000);
        assert_docker_log_count!(1, "Starting my term as leader", [d1, d2]);
    }
}
<commit_msg>These tests fail ~20% of the time<commit_after>// Copyright:: Copyright (c) 2015-2016 Chef Software, Inc.
//
// The terms of the Evaluation Agreement (Bldr) between Chef Software Inc. and the party accessing
// this file ("Licensee") apply to Licensee's use of the Software until such time that the Software
// is made available under an open source license such as the Apache 2.0 License.

use util::{self, docker};
use setup;
use regex::Regex;
use std::thread;

#[ignore]
#[test]
fn standalone_no_options() {
    setup::gpg_import();
    setup::simple_service();

    let d = docker::run("test/simple_service");
    if d.wait_until(r"Shipping out to Boston") {
        let output = d.logs();
        assert_regex!(&output, r"Starting (.+)");
    }
}

#[ignore]
#[test]
fn standalone_no_options_without_config() {
    setup::gpg_import();
    setup::fixture_service("simple_service_without_config");

    let d = docker::run("test/simple_service_without_config");
    if d.wait_until(r"Shipping out to Boston") {
        let output = d.logs();
        assert_regex!(&output, r"Starting (.+)");
    } else {
        // container didn't start in time or pkg doesn't exist
        assert!(false);
    }
}

#[ignore]
#[test]
fn standalone_with_environment_config() {
    setup::gpg_import();
    setup::simple_service();

    let d = docker::run_with_env("test/simple_service",
                                 "BLDR_simple_service=setting=\"blarg\"");
    if d.wait_until(r"End Configuration") {
        let output = d.logs();
        assert_regex!(&output, r"setting: blarg");
    }
}

#[ignore]
#[test]
fn standalone_with_discovery_config() {
    setup::gpg_import();
    setup::simple_service();

    util::discovery::clear("config");
    util::discovery::set("config", "setting = \"sepultura\"");

    let d = docker::run_with_etcd("test/simple_service");
    assert_docker_log!(d, r"setting: sepultura");
}

#[ignore]
#[test]
fn standalone_with_discovery_config_updates() {
    setup::gpg_import();
    setup::simple_service();

    util::discovery::clear("config");

    util::discovery::set("config", "setting = \"sepultura\"");
    let d = docker::run_with_etcd("test/simple_service");
    assert_docker_log!(d, r"setting: sepultura");

    util::discovery::set("config", "setting = \"against me!\"");
    assert_docker_log!(d, r"setting: against me!");
}

#[ignore]
#[test]
fn leader_with_discovery() {
    setup::gpg_import();
    setup::simple_service();

    util::discovery::clear("config");
    util::discovery::clear("topology");

    util::discovery::set("config", "setting = \"rustacean\"");
    let d1 = docker::run_with_etcd_topology("test/simple_service", "leader");
    let d2 = docker::run_with_etcd_topology("test/simple_service", "leader");
    let d3 = docker::run_with_etcd_topology("test/simple_service", "leader");

    assert_docker_log_count!(1, "Starting my term as leader", [d1, d2, d3]);
    assert_docker_log_count!(2, "Becoming a follower", [d1, d2, d3]);

    assert_docker_log!(d1, r"setting: rustacean");
    assert_docker_log!(d2, r"setting: rustacean");
    assert_docker_log!(d3, r"setting: rustacean");

    util::discovery::set("config", "setting = \"against me!\"");
    assert_docker_log!(d1, r"setting: against me!");
    assert_docker_log!(d2, r"setting: against me!");
    assert_docker_log!(d3, r"setting: against me!");

    let re = Regex::new(r"Starting my term as leader").unwrap();
    if re.is_match(&d1.logs()) {
        drop(d1);
        thread::sleep_ms(32000);
        assert_docker_log_count!(1, "Starting my term as leader", [d2, d3]);
    } else if re.is_match(&d2.logs()) {
        drop(d2);
        thread::sleep_ms(32000);
        assert_docker_log_count!(1, "Starting my term as leader", [d1, d3]);
    } else if re.is_match(&d3.logs()) {
        drop(d3);
        thread::sleep_ms(32000);
        assert_docker_log_count!(1, "Starting my term as leader", [d1, d2]);
    }
}
<|endoftext|>


<commit_before>use scene::{BoxedScene, Scene, SceneChangeEvent, SceneName};
use engine::EngineData;
use text_renderer::TextRenderer;
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::surface::Surface;
use std::io::Result;
use std::path::Path;
use uorustlibs::art::{ArtReader, Art};
use uorustlibs::hues::{HueReader, HueGroup, Hue};
use uorustlibs::color::Color as ColorTrait;
use uorustlibs::map::{MapReader, Block};

use std::io::{Error, };
use sdl2::render::{Renderer, Texture, TextureQuery};
use sdl2::rect::Rect;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;

const MAX_BLOCKS_WIDTH: usize = 1024 / 8;
const MAX_BLOCKS_HEIGHT: usize = 768 / 8;
const STEP_X: usize = MAX_BLOCKS_WIDTH / 4;
const STEP_Y: usize = MAX_BLOCKS_HEIGHT / 4;

enum MapRenderMode {
    HeightMap
}

pub struct MapScene {
    map_reader: Result<MapReader>,
    x: u32,
    y: u32,
    mode: MapRenderMode,
    texture: Option<Texture>
}

pub fn draw_block(block: &Block) -> Surface {
    let mut surface = Surface::new(8, 8, PixelFormatEnum::RGBA8888).unwrap();
    surface.with_lock_mut(|bitmap| {
        let mut read_idx = 0;

        for y in 0..8 {
            for x in 0..8 {
                let target = (x + (y * 8));
                let height = (block.cells[target].altitude as i16 + 128) as u8;
                bitmap[target * 4] = 255;
                bitmap[target * 4 + 1] = height;
                bitmap[target * 4 + 2] = height;
                bitmap[target * 4 + 3] = height;
            }
        };
    });
    surface
}

pub fn draw_region<F>(blocks: &Vec<Result<Block>>, renderer: F) -> Surface
    where F: Fn(&Block) -> Surface {
    let mut surface = Surface::new(1024, 768, PixelFormatEnum::RGBA8888).unwrap();
    for y in 0..MAX_BLOCKS_HEIGHT {
        for x in 0..MAX_BLOCKS_WIDTH {
            match blocks[x + (y * MAX_BLOCKS_WIDTH)] {
                Ok(ref block) => {
                    let block_surface = renderer(block);
                    block_surface.blit(None, &mut surface, Some(Rect::new(x as i32 * 8, y as i32* 8, block_surface.width(), block_surface.height()))).unwrap();
                },
                _ => ()
            }
        }
    }
    surface
}

impl MapScene {
    pub fn new<'a>(renderer: &mut Renderer, engine_data: &mut EngineData<'a>) -> BoxedScene<SceneName, EngineData<'a>> {
        let mut scene = Box::new(MapScene {
            map_reader: MapReader::new(&Path::new("./assets/map0.mul"), 768, 512),
            x: 0,
            y: 0,
            texture: None,
            mode: MapRenderMode::HeightMap
        });
        scene.draw_page(renderer, engine_data);
        scene
    }

    pub fn get_blocks(&mut self) -> Vec<Result<Block>> {
        let mut blocks = vec![];

        match self.map_reader {
            Ok(ref mut map_reader) => {
                for y in 0..MAX_BLOCKS_HEIGHT {
                    for x in 0..MAX_BLOCKS_WIDTH {
                        let block = map_reader.read_block_from_coordinates(self.x + x as u32, self.y + y as u32);
                        blocks.push(block);
                    }
                }
            },
            _ => ()
        };
        blocks
    }

    pub fn draw_page(&mut self, renderer: &mut Renderer, engine_data: &mut EngineData) {
        let blocks = self.get_blocks();
        let block_drawer = match self.mode {
            MapRenderMode::HeightMap => draw_block
        };
        let surface = draw_region(&blocks, block_drawer);
        self.texture = Some(renderer.create_texture_from_surface(&surface).unwrap())
    }
}

impl<'a> Scene<SceneName, EngineData<'a>> for MapScene {
    fn render(&self, renderer: &mut Renderer, engine_data: &mut EngineData) {
        renderer.clear();
        match self.texture {
            Some(ref texture) => {
                renderer.copy(texture, None, None).unwrap();
            },
            None => ()
        };
        renderer.present();
    }

    fn handle_event(&mut self, event: &Event, renderer: &mut Renderer, engine_data: &mut EngineData) -> Option<SceneChangeEvent<SceneName>> {
        match *event {
            Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                Some(SceneChangeEvent::PopScene)
            },
            Event::KeyDown { keycode: Some(Keycode::Left), .. } => {
                if self.x >= STEP_X as u32 {
                    self.x -= STEP_X as u32;
                    self.draw_page(renderer, engine_data);
                }
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Right), .. } => {
                self.x += STEP_X as u32;
                self.draw_page(renderer, engine_data);
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Up), .. } => {
                if self.y >= STEP_Y as u32 {
                    self.y -= STEP_Y as u32;
                    self.draw_page(renderer, engine_data);
                }
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Down), .. } => {
                self.y += STEP_Y as u32;
                self.draw_page(renderer, engine_data);
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Num1), .. } => {
                self.mode = MapRenderMode::HeightMap;
                self.draw_page(renderer, engine_data);
                None
            },
             _ => None
        }
    }
}
<commit_msg>Getting ready to take another view<commit_after>use scene::{BoxedScene, Scene, SceneChangeEvent, SceneName};
use engine::EngineData;
use text_renderer::TextRenderer;
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::surface::Surface;
use std::io::Result;
use std::path::Path;
use uorustlibs::art::{ArtReader, Art};
use uorustlibs::hues::{HueReader, HueGroup, Hue};
use uorustlibs::color::Color as ColorTrait;
use uorustlibs::map::{MapReader, Block};

use std::io::{Error, };
use sdl2::render::{Renderer, Texture, TextureQuery};
use sdl2::rect::Rect;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;

const MAX_BLOCKS_WIDTH: usize = 1024 / 8;
const MAX_BLOCKS_HEIGHT: usize = 768 / 8;
const STEP_X: usize = MAX_BLOCKS_WIDTH / 4;
const STEP_Y: usize = MAX_BLOCKS_HEIGHT / 4;

enum MapRenderMode {
    HeightMap,
    RadarMap,
}

pub struct MapScene {
    map_reader: Result<MapReader>,
    x: u32,
    y: u32,
    mode: MapRenderMode,
    texture: Option<Texture>
}

pub fn draw_heightmap_block(block: &Block) -> Surface {
    let mut surface = Surface::new(8, 8, PixelFormatEnum::RGBA8888).unwrap();
    surface.with_lock_mut(|bitmap| {
        let mut read_idx = 0;

        for y in 0..8 {
            for x in 0..8 {
                let target = (x + (y * 8));
                let height = (block.cells[target].altitude as i16 + 128) as u8;
                bitmap[target * 4] = 255;
                bitmap[target * 4 + 1] = height;
                bitmap[target * 4 + 2] = height;
                bitmap[target * 4 + 3] = height;
            }
        };
    });
    surface
}

pub fn draw_region<F>(blocks: &Vec<Result<Block>>, renderer: F) -> Surface
    where F: Fn(&Block) -> Surface {
    let mut surface = Surface::new(1024, 768, PixelFormatEnum::RGBA8888).unwrap();
    for y in 0..MAX_BLOCKS_HEIGHT {
        for x in 0..MAX_BLOCKS_WIDTH {
            match blocks[x + (y * MAX_BLOCKS_WIDTH)] {
                Ok(ref block) => {
                    let block_surface = renderer(block);
                    block_surface.blit(None, &mut surface, Some(Rect::new(x as i32 * 8, y as i32* 8, block_surface.width(), block_surface.height()))).unwrap();
                },
                _ => ()
            }
        }
    }
    surface
}

impl MapScene {
    pub fn new<'a>(renderer: &mut Renderer, engine_data: &mut EngineData<'a>) -> BoxedScene<SceneName, EngineData<'a>> {
        let mut scene = Box::new(MapScene {
            map_reader: MapReader::new(&Path::new("./assets/map0.mul"), 768, 512),
            x: 0,
            y: 0,
            texture: None,
            mode: MapRenderMode::HeightMap
        });
        scene.draw_page(renderer, engine_data);
        scene
    }

    pub fn get_blocks(&mut self) -> Vec<Result<Block>> {
        let mut blocks = vec![];

        match self.map_reader {
            Ok(ref mut map_reader) => {
                for y in 0..MAX_BLOCKS_HEIGHT {
                    for x in 0..MAX_BLOCKS_WIDTH {
                        let block = map_reader.read_block_from_coordinates(self.x + x as u32, self.y + y as u32);
                        blocks.push(block);
                    }
                }
            },
            _ => ()
        };
        blocks
    }

    pub fn draw_page(&mut self, renderer: &mut Renderer, engine_data: &mut EngineData) {
        let blocks = self.get_blocks();
        let block_drawer = match self.mode {
            MapRenderMode::HeightMap => draw_heightmap_block,
            MapRenderMode::RadarMap => draw_heightmap_block,
        };
        let surface = draw_region(&blocks, block_drawer);
        self.texture = Some(renderer.create_texture_from_surface(&surface).unwrap())
    }
}

impl<'a> Scene<SceneName, EngineData<'a>> for MapScene {
    fn render(&self, renderer: &mut Renderer, engine_data: &mut EngineData) {
        renderer.clear();
        match self.texture {
            Some(ref texture) => {
                renderer.copy(texture, None, None).unwrap();
            },
            None => ()
        };
        renderer.present();
    }

    fn handle_event(&mut self, event: &Event, renderer: &mut Renderer, engine_data: &mut EngineData) -> Option<SceneChangeEvent<SceneName>> {
        match *event {
            Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                Some(SceneChangeEvent::PopScene)
            },
            Event::KeyDown { keycode: Some(Keycode::Left), .. } => {
                if self.x >= STEP_X as u32 {
                    self.x -= STEP_X as u32;
                    self.draw_page(renderer, engine_data);
                }
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Right), .. } => {
                self.x += STEP_X as u32;
                self.draw_page(renderer, engine_data);
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Up), .. } => {
                if self.y >= STEP_Y as u32 {
                    self.y -= STEP_Y as u32;
                    self.draw_page(renderer, engine_data);
                }
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Down), .. } => {
                self.y += STEP_Y as u32;
                self.draw_page(renderer, engine_data);
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Num1), .. } => {
                self.mode = MapRenderMode::HeightMap;
                self.draw_page(renderer, engine_data);
                None
            },
            Event::KeyDown { keycode: Some(Keycode::Num2), .. } => {
                self.mode = MapRenderMode::RadarMap;
                self.draw_page(renderer, engine_data);
                None
            },
             _ => None
        }
    }
}
<|endoftext|>


<commit_before>use super::{EdgeKey, FaceKey, Features, Mesh, PointId};
use crate::util::{GridSearch, GsNdiv, GsTol};
use crate::StrError;
use std::collections::{HashMap, HashSet};

/// Defines the location of points
pub enum At {
    /// Fixed x, any (y,z). 2D (vertical-line) or 3D (yz-plane)
    X(f64),

    /// Fixed y, any (x,z). 2D (horizontal-line) or 3D (xz-plane)
    Y(f64),

    /// Fixed z, any (y,z). 3D only (xy-plane)
    Z(f64),

    /// Fixed (x,y), any z. 2D (point) or 3D (z-line)
    XY(f64, f64),

    /// Fixed (y,z), any x. 3D only (x-line)
    YZ(f64, f64),

    /// Fixed (x,z), any y. 3D only (y-line)
    XZ(f64, f64),

    /// At (x,y,z). 3D only (point)
    XYZ(f64, f64, f64),

    /// Circumference of circle at (x,y,radius). 2D only
    Circle(f64, f64, f64),

    /// Surface of cylinder given two axes. 3D only
    ///
    /// Holds: (axis_1_x, axis_1_y, axis_1_z, axis_2_x, axis_2_y, axis_2_z, radius).
    Cylinder(f64, f64, f64, f64, f64, f64, f64),
}

/// Implements functions to find mesh features (points, edges, faces)
///
/// # Warning
///
/// All public members are **read-only**.
pub struct Find {
    /// Space number of dimension (needed for the find functions)
    space_ndim: usize,

    /// Total number of points in the mesh (needed to generate face keys of Tri3)
    num_points: usize,

    /// Tool to quickly find points by coordinates
    grid: GridSearch,

    /// Maps a point id to edges sharing the point
    pub point_to_edges: HashMap<PointId, HashSet<EdgeKey>>,

    /// Maps a point id to faces sharing the point
    pub point_to_faces: HashMap<PointId, HashSet<FaceKey>>,
}

impl Find {
    /// Allocates a new instance
    ///
    /// **Note:** This function is to be used by [Region] and not by the end-user.
    ///
    /// # Panics
    ///
    /// If the `features` does not correspond to `mesh`, a panic will occur.
    pub(crate) fn new(mesh: &Mesh, features: &Features) -> Result<Self, StrError> {
        // expand limits a little bit to accommodate imprecisions on the (min,max) values
        const PCT: f64 = 1.0 / 100.0; // 1% is enough
        let (mut min, mut max) = (vec![0.0; mesh.space_ndim], vec![0.0; mesh.space_ndim]);
        for i in 0..mesh.space_ndim {
            let del = features.max[i] - features.min[i];
            min[i] = features.min[i] - PCT * del;
            max[i] = features.max[i] + PCT * del;
        }

        // add point ids to grid
        let mut grid = GridSearch::new(&min, &max, GsNdiv::Default, GsTol::Default)?;
        for point_id in &features.points {
            grid.insert(*point_id, &mesh.points[*point_id].coords)?;
        }

        // map point ids to edges
        let mut point_to_edges: HashMap<PointId, HashSet<EdgeKey>> = HashMap::new();
        for (edge_key, edge) in &features.edges {
            for point_id in &edge.points {
                point_to_edges
                    .entry(*point_id)
                    .or_insert(HashSet::new())
                    .insert(*edge_key);
            }
        }

        // map point ids to faces
        let mut point_to_faces: HashMap<PointId, HashSet<FaceKey>> = HashMap::new();
        for (face_key, face) in &features.faces {
            for point_id in &face.points {
                point_to_faces
                    .entry(*point_id)
                    .or_insert(HashSet::new())
                    .insert(*face_key);
            }
        }

        // done
        Ok(Find {
            space_ndim: mesh.space_ndim,
            num_points: mesh.points.len(),
            grid,
            point_to_edges,
            point_to_faces,
        })
    }

    /// Finds points
    ///
    /// # Input
    ///
    /// * `at` -- the location constraint
    ///
    /// # Output
    ///
    /// * Returns a set of point ids.
    ///   You may sort point ids using the following code snippet:
    ///
    /// ``` text
    /// let mut ids: Vec<_> = point_ids.iter().copied().collect();
    /// ids.sort();
    /// ```
    pub fn points(&self, at: At) -> Result<HashSet<PointId>, StrError> {
        let mut point_ids: HashSet<PointId> = HashSet::new();
        match at {
            At::X(x) => {
                if self.space_ndim == 2 {
                    for id in self.grid.find_on_line(&[x, 0.0], &[x, 1.0])? {
                        point_ids.insert(id);
                    }
                } else {
                    for id in self.grid.find_on_plane_yz(x)? {
                        point_ids.insert(id);
                    }
                }
            }
            At::Y(y) => {
                if self.space_ndim == 2 {
                    for id in self.grid.find_on_line(&[0.0, y], &[1.0, y])? {
                        point_ids.insert(id);
                    }
                } else {
                    for id in self.grid.find_on_plane_xz(y)? {
                        point_ids.insert(id);
                    }
                }
            }
            At::Z(z) => {
                if self.space_ndim == 2 {
                    return Err("At::Z works in 3D only");
                } else {
                    for id in self.grid.find_on_plane_xy(z)? {
                        point_ids.insert(id);
                    }
                }
            }
            At::XY(x, y) => {
                if self.space_ndim == 2 {
                    if let Some(id) = self.grid.find(&[x, y])? {
                        point_ids.insert(id);
                    }
                } else {
                    for id in self.grid.find_on_line(&[x, y, 0.0], &[x, y, 1.0])? {
                        point_ids.insert(id);
                    }
                }
            }
            At::YZ(y, z) => {
                if self.space_ndim == 2 {
                    return Err("At::YZ works in 3D only");
                } else {
                    for id in self.grid.find_on_line(&[0.0, y, z], &[1.0, y, z])? {
                        point_ids.insert(id);
                    }
                }
            }
            At::XZ(x, z) => {
                if self.space_ndim == 2 {
                    return Err("At::XZ works in 3D only");
                } else {
                    for id in self.grid.find_on_line(&[x, 0.0, z], &[x, 1.0, z])? {
                        point_ids.insert(id);
                    }
                }
            }
            At::XYZ(x, y, z) => {
                if self.space_ndim == 2 {
                    return Err("At::XYZ works in 3D only");
                } else {
                    if let Some(id) = self.grid.find(&[x, y, z])? {
                        point_ids.insert(id);
                    }
                }
            }
            At::Circle(x, y, r) => {
                if self.space_ndim == 2 {
                    for id in self.grid.find_on_circle(&[x, y], r)? {
                        point_ids.insert(id);
                    }
                } else {
                    return Err("At::Circle works in 2D only");
                }
            }
            At::Cylinder(ax, ay, az, bx, by, bz, r) => {
                if self.space_ndim == 2 {
                    return Err("At::Cylinder works in 3D only");
                } else {
                    for id in self.grid.find_on_cylinder(&[ax, ay, az], &[bx, by, bz], r)? {
                        point_ids.insert(id);
                    }
                }
            }
        }
        Ok(point_ids)
    }

    /// Finds edges
    ///
    /// # Input
    ///
    /// * `at` -- the location constraint
    ///
    /// # Output
    ///
    /// * Returns a set of edge keys.
    ///   You may sort the edge keys using the following code snippet:
    ///
    /// ``` text
    /// let mut keys: Vec<_> = edge_keys.iter().copied().collect();
    /// keys.sort();
    /// ```
    pub fn edges(&self, at: At) -> Result<HashSet<EdgeKey>, StrError> {
        let mut edge_keys: HashSet<EdgeKey> = HashSet::new();
        // find all points constrained by "at"
        let point_ids = self.points(at)?;
        for point_id in &point_ids {
            // select all edges connected to the found points
            let edges = self.point_to_edges.get(point_id).unwrap(); // unwrap here because there should be no hanging edges
            for edge_key in edges {
                // accept edge when at least two edge points validate "At"
                if point_ids.contains(&edge_key.0) && point_ids.contains(&edge_key.1) {
                    edge_keys.insert(*edge_key);
                }
            }
        }
        Ok(edge_keys)
    }

    /// Finds faces
    ///
    /// # Input
    ///
    /// * `at` -- the location constraint
    ///
    /// # Output
    ///
    /// * Returns a set of face keys.
    ///   You may sort the face keys using the following code snippet:
    ///
    /// ``` text
    /// let mut keys: Vec<_> = face_keys.iter().copied().collect();
    /// keys.sort();
    /// ```
    pub fn faces(&self, at: At) -> Result<HashSet<FaceKey>, StrError> {
        let mut face_keys: HashSet<FaceKey> = HashSet::new();
        if self.space_ndim != 3 {
            return Ok(face_keys);
        }
        // find all points constrained by "at"
        let point_ids = self.points(at)?;
        for point_id in &point_ids {
            // select all faces connected to the found points
            let faces = self.point_to_faces.get(point_id).unwrap(); // unwrap here because there should be no hanging faces
            for face_key in faces {
                // accept face when at least four face points validate "At"
                let fourth_is_ok = if face_key.3 == self.num_points {
                    true
                } else {
                    point_ids.contains(&face_key.3)
                };
                if point_ids.contains(&face_key.0)
                    && point_ids.contains(&face_key.1)
                    && point_ids.contains(&face_key.2)
                    && fourth_is_ok
                {
                    face_keys.insert(*face_key);
                }
            }
        }
        Ok(face_keys)
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {}
<commit_msg>Remove public in Find<commit_after>use super::{EdgeKey, FaceKey, Features, Mesh, PointId};
use crate::util::{GridSearch, GsNdiv, GsTol};
use crate::StrError;
use std::collections::{HashMap, HashSet};

/// Defines the location of points
pub enum At {
    /// Fixed x, any (y,z). 2D (vertical-line) or 3D (yz-plane)
    X(f64),

    /// Fixed y, any (x,z). 2D (horizontal-line) or 3D (xz-plane)
    Y(f64),

    /// Fixed z, any (y,z). 3D only (xy-plane)
    Z(f64),

    /// Fixed (x,y), any z. 2D (point) or 3D (z-line)
    XY(f64, f64),

    /// Fixed (y,z), any x. 3D only (x-line)
    YZ(f64, f64),

    /// Fixed (x,z), any y. 3D only (y-line)
    XZ(f64, f64),

    /// At (x,y,z). 3D only (point)
    XYZ(f64, f64, f64),

    /// Circumference of circle at (x,y,radius). 2D only
    Circle(f64, f64, f64),

    /// Surface of cylinder given two axes. 3D only
    ///
    /// Holds: (axis_1_x, axis_1_y, axis_1_z, axis_2_x, axis_2_y, axis_2_z, radius).
    Cylinder(f64, f64, f64, f64, f64, f64, f64),
}

/// Implements functions to find mesh features (points, edges, faces)
pub struct Find {
    /// Space number of dimension (needed for the find functions)
    space_ndim: usize,

    /// Total number of points in the mesh (needed to generate face keys of Tri3)
    num_points: usize,

    /// Tool to quickly find points by coordinates
    grid: GridSearch,

    /// Maps a point id to edges sharing the point
    point_to_edges: HashMap<PointId, HashSet<EdgeKey>>,

    /// Maps a point id to faces sharing the point
    point_to_faces: HashMap<PointId, HashSet<FaceKey>>,
}

impl Find {
    /// Allocates a new instance
    ///
    /// **Note:** This function is to be used by [Region] and not by the end-user.
    ///
    /// # Panics
    ///
    /// If the `features` does not correspond to `mesh`, a panic will occur.
    pub(crate) fn new(mesh: &Mesh, features: &Features) -> Result<Self, StrError> {
        // expand limits a little bit to accommodate imprecisions on the (min,max) values
        const PCT: f64 = 1.0 / 100.0; // 1% is enough
        let (mut min, mut max) = (vec![0.0; mesh.space_ndim], vec![0.0; mesh.space_ndim]);
        for i in 0..mesh.space_ndim {
            let del = features.max[i] - features.min[i];
            min[i] = features.min[i] - PCT * del;
            max[i] = features.max[i] + PCT * del;
        }

        // add point ids to grid
        let mut grid = GridSearch::new(&min, &max, GsNdiv::Default, GsTol::Default)?;
        for point_id in &features.points {
            grid.insert(*point_id, &mesh.points[*point_id].coords)?;
        }

        // map point ids to edges
        let mut point_to_edges: HashMap<PointId, HashSet<EdgeKey>> = HashMap::new();
        for (edge_key, edge) in &features.edges {
            for point_id in &edge.points {
                point_to_edges
                    .entry(*point_id)
                    .or_insert(HashSet::new())
                    .insert(*edge_key);
            }
        }

        // map point ids to faces
        let mut point_to_faces: HashMap<PointId, HashSet<FaceKey>> = HashMap::new();
        for (face_key, face) in &features.faces {
            for point_id in &face.points {
                point_to_faces
                    .entry(*point_id)
                    .or_insert(HashSet::new())
                    .insert(*face_key);
            }
        }

        // done
        Ok(Find {
            space_ndim: mesh.space_ndim,
            num_points: mesh.points.len(),
            grid,
            point_to_edges,
            point_to_faces,
        })
    }

    /// Finds points
    ///
    /// # Input
    ///
    /// * `at` -- the location constraint
    ///
    /// # Output
    ///
    /// * Returns a set of point ids.
    ///   You may sort point ids using the following code snippet:
    ///
    /// ``` text
    /// let mut ids: Vec<_> = point_ids.iter().copied().collect();
    /// ids.sort();
    /// ```
    pub fn points(&self, at: At) -> Result<HashSet<PointId>, StrError> {
        let mut point_ids: HashSet<PointId> = HashSet::new();
        match at {
            At::X(x) => {
                if self.space_ndim == 2 {
                    for id in self.grid.find_on_line(&[x, 0.0], &[x, 1.0])? {
                        point_ids.insert(id);
                    }
                } else {
                    for id in self.grid.find_on_plane_yz(x)? {
                        point_ids.insert(id);
                    }
                }
            }
            At::Y(y) => {
                if self.space_ndim == 2 {
                    for id in self.grid.find_on_line(&[0.0, y], &[1.0, y])? {
                        point_ids.insert(id);
                    }
                } else {
                    for id in self.grid.find_on_plane_xz(y)? {
                        point_ids.insert(id);
                    }
                }
            }
            At::Z(z) => {
                if self.space_ndim == 2 {
                    return Err("At::Z works in 3D only");
                } else {
                    for id in self.grid.find_on_plane_xy(z)? {
                        point_ids.insert(id);
                    }
                }
            }
            At::XY(x, y) => {
                if self.space_ndim == 2 {
                    if let Some(id) = self.grid.find(&[x, y])? {
                        point_ids.insert(id);
                    }
                } else {
                    for id in self.grid.find_on_line(&[x, y, 0.0], &[x, y, 1.0])? {
                        point_ids.insert(id);
                    }
                }
            }
            At::YZ(y, z) => {
                if self.space_ndim == 2 {
                    return Err("At::YZ works in 3D only");
                } else {
                    for id in self.grid.find_on_line(&[0.0, y, z], &[1.0, y, z])? {
                        point_ids.insert(id);
                    }
                }
            }
            At::XZ(x, z) => {
                if self.space_ndim == 2 {
                    return Err("At::XZ works in 3D only");
                } else {
                    for id in self.grid.find_on_line(&[x, 0.0, z], &[x, 1.0, z])? {
                        point_ids.insert(id);
                    }
                }
            }
            At::XYZ(x, y, z) => {
                if self.space_ndim == 2 {
                    return Err("At::XYZ works in 3D only");
                } else {
                    if let Some(id) = self.grid.find(&[x, y, z])? {
                        point_ids.insert(id);
                    }
                }
            }
            At::Circle(x, y, r) => {
                if self.space_ndim == 2 {
                    for id in self.grid.find_on_circle(&[x, y], r)? {
                        point_ids.insert(id);
                    }
                } else {
                    return Err("At::Circle works in 2D only");
                }
            }
            At::Cylinder(ax, ay, az, bx, by, bz, r) => {
                if self.space_ndim == 2 {
                    return Err("At::Cylinder works in 3D only");
                } else {
                    for id in self.grid.find_on_cylinder(&[ax, ay, az], &[bx, by, bz], r)? {
                        point_ids.insert(id);
                    }
                }
            }
        }
        Ok(point_ids)
    }

    /// Finds edges
    ///
    /// # Input
    ///
    /// * `at` -- the location constraint
    ///
    /// # Output
    ///
    /// * Returns a set of edge keys.
    ///   You may sort the edge keys using the following code snippet:
    ///
    /// ``` text
    /// let mut keys: Vec<_> = edge_keys.iter().copied().collect();
    /// keys.sort();
    /// ```
    pub fn edges(&self, at: At) -> Result<HashSet<EdgeKey>, StrError> {
        let mut edge_keys: HashSet<EdgeKey> = HashSet::new();
        // find all points constrained by "at"
        let point_ids = self.points(at)?;
        for point_id in &point_ids {
            // select all edges connected to the found points
            let edges = self.point_to_edges.get(point_id).unwrap(); // unwrap here because there should be no hanging edges
            for edge_key in edges {
                // accept edge when at least two edge points validate "At"
                if point_ids.contains(&edge_key.0) && point_ids.contains(&edge_key.1) {
                    edge_keys.insert(*edge_key);
                }
            }
        }
        Ok(edge_keys)
    }

    /// Finds faces
    ///
    /// # Input
    ///
    /// * `at` -- the location constraint
    ///
    /// # Output
    ///
    /// * Returns a set of face keys.
    ///   You may sort the face keys using the following code snippet:
    ///
    /// ``` text
    /// let mut keys: Vec<_> = face_keys.iter().copied().collect();
    /// keys.sort();
    /// ```
    pub fn faces(&self, at: At) -> Result<HashSet<FaceKey>, StrError> {
        let mut face_keys: HashSet<FaceKey> = HashSet::new();
        if self.space_ndim != 3 {
            return Ok(face_keys);
        }
        // find all points constrained by "at"
        let point_ids = self.points(at)?;
        for point_id in &point_ids {
            // select all faces connected to the found points
            let faces = self.point_to_faces.get(point_id).unwrap(); // unwrap here because there should be no hanging faces
            for face_key in faces {
                // accept face when at least four face points validate "At"
                let fourth_is_ok = if face_key.3 == self.num_points {
                    true
                } else {
                    point_ids.contains(&face_key.3)
                };
                if point_ids.contains(&face_key.0)
                    && point_ids.contains(&face_key.1)
                    && point_ids.contains(&face_key.2)
                    && fourth_is_ok
                {
                    face_keys.insert(*face_key);
                }
            }
        }
        Ok(face_keys)
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {}
<|endoftext|>