<commit_before>use pam_sys::{
    acct_mgmt, authenticate, close_session, end, getenv, open_session, putenv, setcred, start,
};
use pam_sys::{PamFlag, PamHandle, PamReturnCode};
use users;

use std::{env, ptr};

use crate::{env::get_pam_env, ffi, Converse, PamError, PamResult, PasswordConv};

/// Main struct to authenticate a user
///
/// You need to create an instance of it to start an authentication process. If you
/// want a simple password-based authentication, you can use `Authenticator::with_password`,
/// and to the following flow:
///
/// ```no_run
/// use pam::Authenticator;
///
/// let mut authenticator = Authenticator::with_password("system-auth")
///         .expect("Failed to init PAM client.");
/// // Preset the login & password we will use for authentication
/// authenticator.get_handler().set_credentials("login", "password");
/// // actually try to authenticate:
/// authenticator.authenticate().expect("Authentication failed!");
/// // Now that we are authenticated, it's possible to open a sesssion:
/// authenticator.open_session().expect("Failed to open a session!");
/// ```
///
/// If you wish to customise the PAM conversation function, you should rather create your
/// authenticator with `Authenticator::with_handler`, providing a struct implementing the
/// `Converse` trait. You can then mutably access your conversation handler using the
/// `Authenticator::get_handler` method.
///
/// By default, the `Authenticator` will close any opened session when dropped. If you don't
/// want this, you can change its `close_on_drop` field to `False`.
pub struct Authenticator<'a, C: Converse> {
    /// Flag indicating whether the Authenticator should close the session on drop
    pub close_on_drop: bool,
    handle: &'a mut PamHandle,
    converse: Box<C>,
    is_authenticated: bool,
    has_open_session: bool,
    last_code: PamReturnCode,
}

impl<'a> Authenticator<'a, PasswordConv> {
    /// Create a new `Authenticator` with a given service name and a password-based conversation
    pub fn with_password(service: &str) -> PamResult<Authenticator<'a, PasswordConv>> {
        Authenticator::with_handler(service, PasswordConv::new())
    }
}

impl<'a, C: Converse> Authenticator<'a, C> {
    /// Creates a new Authenticator with a given service name and conversation callback
    pub fn with_handler(service: &str, converse: C) -> PamResult<Authenticator<'a, C>> {
        let mut converse = Box::new(converse);
        let conv = ffi::make_conversation(&mut *converse);
        let mut handle: *mut PamHandle = ptr::null_mut();

        match start(service, None, &conv, &mut handle) {
            PamReturnCode::SUCCESS => unsafe {
                Ok(Authenticator {
                    close_on_drop: true,
                    handle: &mut *handle,
                    converse,
                    is_authenticated: false,
                    has_open_session: false,
                    last_code: PamReturnCode::SUCCESS,
                })
            },
            code => Err(PamError(code)),
        }
    }

    /// Access the conversation handler of this Authenticator
    pub fn get_handler(&mut self) -> &mut C {
        &mut *self.converse
    }

    /// Perform the authentication with the provided credentials
    pub fn authenticate(&mut self) -> PamResult<()> {
        self.last_code = authenticate(self.handle, PamFlag::NONE);
        if self.last_code != PamReturnCode::SUCCESS {
            // No need to reset here
            return Err(From::from(self.last_code));
        }

        self.is_authenticated = true;

        self.last_code = acct_mgmt(self.handle, PamFlag::NONE);
        if self.last_code != PamReturnCode::SUCCESS {
            // Probably not strictly neccessary but better be sure
            return self.reset();
        }
        Ok(())
    }

    /// Open a session for a previously authenticated user and
    /// initialize the environment appropriately (in PAM and regular enviroment variables).
    pub fn open_session(&mut self) -> PamResult<()> {
        if !self.is_authenticated {
            //TODO: is this the right return code?
            return Err(PamReturnCode::PERM_DENIED.into());
        }

        self.last_code = setcred(self.handle, PamFlag::ESTABLISH_CRED);
        if self.last_code != PamReturnCode::SUCCESS {
            return self.reset();
        }

        self.last_code = open_session(self.handle, PamFlag::NONE);
        if self.last_code != PamReturnCode::SUCCESS {
            return self.reset();
        }

        // Follow openSSH and call pam_setcred before and after open_session
        self.last_code = setcred(self.handle, PamFlag::REINITIALIZE_CRED);
        if self.last_code != PamReturnCode::SUCCESS {
            return self.reset();
        }

        self.has_open_session = true;
        self.initialize_environment()
    }

    // Initialize the client environment with common variables.
    // Currently always called from Authenticator.open_session()
    fn initialize_environment(&mut self) -> PamResult<()> {
        use users::os::unix::UserExt;

        // Set PAM environment in the local process
        if let Some(mut env_list) = get_pam_env(self.handle) {
            let env = env_list.to_vec();
            for (key, value) in env {
                env::set_var(&key, &value);
            }
        }

        let user = users::get_user_by_name(self.converse.username()).unwrap_or_else(|| {
            panic!("Could not get user by name: {:?}", self.converse.username())
        });

        // Set some common environment variables
        self.set_env(
            "USER",
            user.name()
                .to_str()
                .expect("Unix usernames should be valid UTF-8"),
        )?;
        self.set_env(
            "LOGNAME",
            user.name()
                .to_str()
                .expect("Unix usernames should be valid UTF-8"),
        )?;
        self.set_env("HOME", user.home_dir().to_str().unwrap())?;
        self.set_env("PWD", user.home_dir().to_str().unwrap())?;
        self.set_env("SHELL", user.shell().to_str().unwrap())?;
        // Taken from https://github.com/gsingh93/display-manager/blob/master/pam.c
        // Should be a better way to get this. Revisit later.
        self.set_env("PATH", "$PATH:/usr/local/sbin:/usr/local/bin:/usr/bin")?;

        Ok(())
    }

    // Utility function to set an environment variable in PAM and the process
    fn set_env(&mut self, key: &str, value: &str) -> PamResult<()> {
        // Set regular environment variable
        env::set_var(key, value);

        // Set pam environment variable
        if getenv(self.handle, key).is_none() {
            let name_value = format!("{}={}", key, value);
            match putenv(self.handle, &name_value) {
                PamReturnCode::SUCCESS => Ok(()),
                code => Err(From::from(code)),
            }
        } else {
            Ok(())
        }
    }

    // Utility function to reset the pam handle in case of intermediate errors
    fn reset(&mut self) -> PamResult<()> {
        setcred(self.handle, PamFlag::DELETE_CRED);
        self.is_authenticated = false;
        Err(From::from(self.last_code))
    }
}

impl<'a, C: Converse> Drop for Authenticator<'a, C> {
    fn drop(&mut self) {
        if self.has_open_session && self.close_on_drop {
            close_session(self.handle, PamFlag::NONE);
        }
        let code = setcred(self.handle, PamFlag::DELETE_CRED);
        end(self.handle, code);
    }
}
<commit_msg>Rename get_handler to mut_handler and add method for immutable access to handler<commit_after>use pam_sys::{
    acct_mgmt, authenticate, close_session, end, getenv, open_session, putenv, setcred, start,
};
use pam_sys::{PamFlag, PamHandle, PamReturnCode};
use users;

use std::{env, ptr};

use crate::{env::get_pam_env, ffi, Converse, PamError, PamResult, PasswordConv};

/// Main struct to authenticate a user
///
/// You need to create an instance of it to start an authentication process. If you
/// want a simple password-based authentication, you can use `Authenticator::with_password`,
/// and to the following flow:
///
/// ```no_run
/// use pam::Authenticator;
///
/// let mut authenticator = Authenticator::with_password("system-auth")
///         .expect("Failed to init PAM client.");
/// // Preset the login & password we will use for authentication
/// authenticator.get_handler().set_credentials("login", "password");
/// // actually try to authenticate:
/// authenticator.authenticate().expect("Authentication failed!");
/// // Now that we are authenticated, it's possible to open a sesssion:
/// authenticator.open_session().expect("Failed to open a session!");
/// ```
///
/// If you wish to customise the PAM conversation function, you should rather create your
/// authenticator with `Authenticator::with_handler`, providing a struct implementing the
/// `Converse` trait. You can then mutably access your conversation handler using the
/// `Authenticator::get_handler` method.
///
/// By default, the `Authenticator` will close any opened session when dropped. If you don't
/// want this, you can change its `close_on_drop` field to `False`.
pub struct Authenticator<'a, C: Converse> {
    /// Flag indicating whether the Authenticator should close the session on drop
    pub close_on_drop: bool,
    handle: &'a mut PamHandle,
    converse: Box<C>,
    is_authenticated: bool,
    has_open_session: bool,
    last_code: PamReturnCode,
}

impl<'a> Authenticator<'a, PasswordConv> {
    /// Create a new `Authenticator` with a given service name and a password-based conversation
    pub fn with_password(service: &str) -> PamResult<Authenticator<'a, PasswordConv>> {
        Authenticator::with_handler(service, PasswordConv::new())
    }
}

impl<'a, C: Converse> Authenticator<'a, C> {
    /// Creates a new Authenticator with a given service name and conversation callback
    pub fn with_handler(service: &str, converse: C) -> PamResult<Authenticator<'a, C>> {
        let mut converse = Box::new(converse);
        let conv = ffi::make_conversation(&mut *converse);
        let mut handle: *mut PamHandle = ptr::null_mut();

        match start(service, None, &conv, &mut handle) {
            PamReturnCode::SUCCESS => unsafe {
                Ok(Authenticator {
                    close_on_drop: true,
                    handle: &mut *handle,
                    converse,
                    is_authenticated: false,
                    has_open_session: false,
                    last_code: PamReturnCode::SUCCESS,
                })
            },
            code => Err(PamError(code)),
        }
    }

    /// Mutable access to the conversation handler of this Authenticator
    pub fn mut_handler(&mut self) -> &mut C {
        &mut *self.converse
    }

    /// Immutable access to the conversation handler of this Authenticator
    pub fn handler(&self) -> &C {
        &*self.converse
    }

    /// Perform the authentication with the provided credentials
    pub fn authenticate(&mut self) -> PamResult<()> {
        self.last_code = authenticate(self.handle, PamFlag::NONE);
        if self.last_code != PamReturnCode::SUCCESS {
            // No need to reset here
            return Err(From::from(self.last_code));
        }

        self.is_authenticated = true;

        self.last_code = acct_mgmt(self.handle, PamFlag::NONE);
        if self.last_code != PamReturnCode::SUCCESS {
            // Probably not strictly neccessary but better be sure
            return self.reset();
        }
        Ok(())
    }

    /// Open a session for a previously authenticated user and
    /// initialize the environment appropriately (in PAM and regular enviroment variables).
    pub fn open_session(&mut self) -> PamResult<()> {
        if !self.is_authenticated {
            //TODO: is this the right return code?
            return Err(PamReturnCode::PERM_DENIED.into());
        }

        self.last_code = setcred(self.handle, PamFlag::ESTABLISH_CRED);
        if self.last_code != PamReturnCode::SUCCESS {
            return self.reset();
        }

        self.last_code = open_session(self.handle, PamFlag::NONE);
        if self.last_code != PamReturnCode::SUCCESS {
            return self.reset();
        }

        // Follow openSSH and call pam_setcred before and after open_session
        self.last_code = setcred(self.handle, PamFlag::REINITIALIZE_CRED);
        if self.last_code != PamReturnCode::SUCCESS {
            return self.reset();
        }

        self.has_open_session = true;
        self.initialize_environment()
    }

    // Initialize the client environment with common variables.
    // Currently always called from Authenticator.open_session()
    fn initialize_environment(&mut self) -> PamResult<()> {
        use users::os::unix::UserExt;

        // Set PAM environment in the local process
        if let Some(mut env_list) = get_pam_env(self.handle) {
            let env = env_list.to_vec();
            for (key, value) in env {
                env::set_var(&key, &value);
            }
        }

        let user = users::get_user_by_name(self.converse.username()).unwrap_or_else(|| {
            panic!("Could not get user by name: {:?}", self.converse.username())
        });

        // Set some common environment variables
        self.set_env(
            "USER",
            user.name()
                .to_str()
                .expect("Unix usernames should be valid UTF-8"),
        )?;
        self.set_env(
            "LOGNAME",
            user.name()
                .to_str()
                .expect("Unix usernames should be valid UTF-8"),
        )?;
        self.set_env("HOME", user.home_dir().to_str().unwrap())?;
        self.set_env("PWD", user.home_dir().to_str().unwrap())?;
        self.set_env("SHELL", user.shell().to_str().unwrap())?;
        // Taken from https://github.com/gsingh93/display-manager/blob/master/pam.c
        // Should be a better way to get this. Revisit later.
        self.set_env("PATH", "$PATH:/usr/local/sbin:/usr/local/bin:/usr/bin")?;

        Ok(())
    }

    // Utility function to set an environment variable in PAM and the process
    fn set_env(&mut self, key: &str, value: &str) -> PamResult<()> {
        // Set regular environment variable
        env::set_var(key, value);

        // Set pam environment variable
        if getenv(self.handle, key).is_none() {
            let name_value = format!("{}={}", key, value);
            match putenv(self.handle, &name_value) {
                PamReturnCode::SUCCESS => Ok(()),
                code => Err(From::from(code)),
            }
        } else {
            Ok(())
        }
    }

    // Utility function to reset the pam handle in case of intermediate errors
    fn reset(&mut self) -> PamResult<()> {
        setcred(self.handle, PamFlag::DELETE_CRED);
        self.is_authenticated = false;
        Err(From::from(self.last_code))
    }
}

impl<'a, C: Converse> Drop for Authenticator<'a, C> {
    fn drop(&mut self) {
        if self.has_open_session && self.close_on_drop {
            close_session(self.handle, PamFlag::NONE);
        }
        let code = setcred(self.handle, PamFlag::DELETE_CRED);
        end(self.handle, code);
    }
}
<|endoftext|>


<commit_before>#![deny(warnings)]

extern crate coreutils;
extern crate extra;

use std::env;
use std::fs;
use std::io::{stdout, stderr, Write};
use std::process::exit;
use coreutils::ArgParser;
use extra::io::fail;
use extra::option::OptionalExt;

const MAN_PAGE: &'static str = /* @MANSTART{mkdir} */ r#"
NAME
    mkdir - make directories

SYNOPSIS
    mkdir [ -h | --help ] DIRECTORIES...

DESCRIPTION
    The mkdir utility creates the directories named as operands.

OPTIONS
    --help, -h
        print this message
"#; /* @MANEND */

fn main() {
    let stdout = stdout();
    let mut stdout = stdout.lock();
    let mut stderr = stderr();
    let mut parser = ArgParser::new(1)
        .add_flag("h", "help");
    parser.parse(env::args());

    if parser.found(&'h') || parser.found("help") {
        stdout.write_all(MAN_PAGE.as_bytes()).try(&mut stderr);
        stdout.flush().try(&mut stderr);
        exit(0);
    }
    if parser.args.is_empty() {
        fail("No arguments. Use --help to see the usage.", &mut stderr);
    }

    for ref path in &parser.args {
        fs::create_dir(path).try(&mut stderr);
    }
}
<commit_msg>mkdir add --parents option<commit_after>#![deny(warnings)]

extern crate coreutils;
extern crate extra;

use std::env;
use std::fs;
use std::io::{stdout, stderr, Write};
use std::process::exit;
use coreutils::ArgParser;
use extra::io::fail;
use extra::option::OptionalExt;

const MAN_PAGE: &'static str = /* @MANSTART{mkdir} */ r#"
NAME
    mkdir - make directories

SYNOPSIS
    mkdir [ -h | --help ] DIRECTORIES...

DESCRIPTION
    The mkdir utility creates the directories named as operands.

OPTIONS
    --help, -h
        print this message
    -p, --parents
        no error if existing, make parent directories as needed
"#; /* @MANEND */

fn main() {
    let stdout = stdout();
    let mut stdout = stdout.lock();
    let mut stderr = stderr();
    let mut parser = ArgParser::new(2)
        .add_flag("h", "help")
        .add_flag("p", "parents");
    parser.parse(env::args());

    if parser.found(&'h') || parser.found("help") {
        stdout.write_all(MAN_PAGE.as_bytes()).try(&mut stderr);
        stdout.flush().try(&mut stderr);
        exit(0);
    }
    if parser.args.is_empty() {
        fail("No arguments. Use --help to see the usage.", &mut stderr);
    }

    let mut parents = false;
    if parser.found(&'p') || parser.found("parents") {
        parents = true;
    }

    for ref path in &parser.args {
        if parents {
            fs::create_dir_all(path).try(&mut stderr);
        } else {
            fs::create_dir(path).try(&mut stderr);
        }
    }
}
<|endoftext|>


<commit_before>use crate::agents::*;
use crate::config::LimitConfig;
use crate::crypto::SigningAlgorithm;
use crate::utils::agent::*;
use crate::web::Session;
use std::collections::hash_map::{Entry, HashMap};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
use url::Url;

/// Combines any type with an `Instant` expiry time.
struct Expiring<T> {
    value: T,
    expires: Instant,
}

impl<T> Expiring<T> {
    /// Create a value from a duration.
    fn from_duration(value: T, duration: Duration) -> Self {
        let expires = Instant::now() + duration;
        Expiring { value, expires }
    }

    /// Whether this value has not yet expired.
    fn is_alive(&self) -> bool {
        self.expires > Instant::now()
    }
}

/// Message sent at an interval to collect garbage.
struct Gc;
impl Message for Gc {
    type Reply = ();
}

/// A slot in the cache `HashMap`.
///
/// We want to lock these individually while a fetch is in progress, so multiple requests for the
/// same URL result in only one fetch. Therefore, we use an `Arc<Mutex<_>>` to carry slots around,
/// and within an `Option` which indicates whether cache is actually present (despite the hash map
/// entry existing or not, which does not indicate anything).
type CacheSlot = Arc<Mutex<Option<Expiring<String>>>>;

/// A slot in the keys `HashMap`.
///
/// It's technically not necessary for us to lock keys, because the key manager is our only client,
/// but we do so any ways to ensure correct behavior.
type KeysSlot = Arc<Mutex<KeySet>>;

/// Store implementation using memory.
pub struct MemoryStore {
    /// TTL of session keys
    expire_sessions: Duration,
    /// TTL of cache keys
    expire_cache: Duration,
    /// Configuration for per-email rate limiting.
    limit_per_email_config: LimitConfig,
    /// The agent used for fetching on cache miss.
    fetcher: Addr<FetchAgent>,
    /// Key manager if rotating keys are enabled.
    key_manager: Option<Addr<RotatingKeys>>,
    /// Session storage.
    sessions: HashMap<String, Expiring<Session>>,
    /// Cache storage.
    cache: HashMap<Url, CacheSlot>,
    /// Rate limit storage.
    limits: HashMap<IncrAndTestLimit, Expiring<usize>>,
    /// Keys storage.
    keys: HashMap<SigningAlgorithm, KeysSlot>,
}

impl MemoryStore {
    pub fn new(
        expire_sessions: Duration,
        expire_cache: Duration,
        limit_per_email_config: LimitConfig,
        fetcher: Addr<FetchAgent>,
    ) -> Self {
        log::warn!("Storing sessions in memory.");
        log::warn!("Note that sessions will be lost on restart!");

        MemoryStore {
            expire_sessions,
            expire_cache,
            limit_per_email_config,
            fetcher,
            key_manager: None,
            sessions: HashMap::new(),
            cache: HashMap::new(),
            limits: HashMap::new(),
            keys: HashMap::new(),
        }
    }
}

impl Agent for MemoryStore {
    fn started(addr: &Addr<Self>) {
        // Start the garbage collection loop.
        let addr = addr.clone();
        tokio::spawn(async move {
            let mut interval = tokio::time::interval(Duration::from_secs(60));
            loop {
                interval.tick().await;
                addr.send(Gc).await;
            }
        });
    }
}

impl Handler<Gc> for MemoryStore {
    fn handle(&mut self, _message: Gc, cx: Context<Self, Gc>) {
        self.sessions = self
            .sessions
            .drain()
            .filter(|(_, ref entry)| entry.is_alive())
            .collect();
        self.cache = self
            .cache
            .drain()
            .filter(|(_, ref slot)| {
                // For cache items, only remove if we have the sole reference. Otherwise, a fetch
                // might still be in progress. Locking should be immediate if true.
                if let Ok(maybe_entry) = slot.try_lock() {
                    Arc::strong_count(slot) > 1
                        || maybe_entry
                            .as_ref()
                            .filter(|entry| entry.is_alive())
                            .is_some()
                } else {
                    true
                }
            })
            .collect();
        self.limits = self
            .limits
            .drain()
            .filter(|(_, ref entry)| entry.is_alive())
            .collect();
        cx.reply(())
    }
}

impl Handler<SaveSession> for MemoryStore {
    fn handle(&mut self, message: SaveSession, cx: Context<Self, SaveSession>) {
        self.sessions.insert(
            message.session_id,
            Expiring::from_duration(message.data, self.expire_sessions),
        );
        cx.reply(Ok(()))
    }
}

impl Handler<GetSession> for MemoryStore {
    fn handle(&mut self, message: GetSession, cx: Context<Self, GetSession>) {
        let data = self
            .sessions
            .get(&message.session_id)
            .filter(|entry| entry.is_alive())
            .map(|entry| entry.value.clone());
        cx.reply(Ok(data))
    }
}

impl Handler<DeleteSession> for MemoryStore {
    fn handle(&mut self, message: DeleteSession, cx: Context<Self, DeleteSession>) {
        self.sessions.remove(&message.session_id);
        cx.reply(Ok(()))
    }
}

impl Handler<FetchUrlCached> for MemoryStore {
    fn handle(&mut self, message: FetchUrlCached, cx: Context<Self, FetchUrlCached>) {
        let fetcher = self.fetcher.clone();
        let slot = self.cache.entry(message.url.clone()).or_default().clone();
        let expire_cache = self.expire_cache;
        cx.reply_later(async move {
            let mut slot = slot.lock().await;
            if let Some(entry) = slot.as_ref().filter(|entry| entry.is_alive()) {
                return Ok(entry.value.clone());
            }
            let result = fetcher.send(FetchUrl { url: message.url }).await?;
            let ttl = std::cmp::max(expire_cache, result.max_age);
            *slot = Some(Expiring::from_duration(result.data.clone(), ttl));
            Ok(result.data)
        });
    }
}

impl Handler<IncrAndTestLimit> for MemoryStore {
    fn handle(&mut self, message: IncrAndTestLimit, cx: Context<Self, IncrAndTestLimit>) {
        let config = match message {
            IncrAndTestLimit::PerEmail { .. } => self.limit_per_email_config,
        };
        let count: usize = match self.limits.entry(message) {
            Entry::Occupied(mut entry) => {
                let mut expiring = entry.get_mut();
                if expiring.expires <= Instant::now() {
                    *expiring = Expiring::from_duration(1, config.duration);
                    1
                } else {
                    expiring.value += 1;
                    expiring.value
                }
            }
            Entry::Vacant(entry) => {
                entry.insert(Expiring::from_duration(1, config.duration));
                1
            }
        };
        cx.reply(Ok(count <= config.max_count));
    }
}

impl Handler<EnableRotatingKeys> for MemoryStore {
    fn handle(&mut self, message: EnableRotatingKeys, cx: Context<Self, EnableRotatingKeys>) {
        self.key_manager = Some(message.key_manager.clone());
        let mut update_msgs = Vec::with_capacity(message.signing_algs.len());
        for signing_alg in &message.signing_algs {
            let key_set = KeySet::empty(*signing_alg);
            update_msgs.push(UpdateKeys(key_set.clone()));
            self.keys
                .insert(*signing_alg, Arc::new(Mutex::new(key_set)));
        }
        cx.reply_later(async move {
            for update_msg in update_msgs {
                message.key_manager.send(update_msg).await;
            }
        });
    }
}

impl Handler<RotateKeysLocked> for MemoryStore {
    fn handle(&mut self, message: RotateKeysLocked, cx: Context<Self, RotateKeysLocked>) {
        let slot_rc = self.keys[&message.0].clone();
        let key_manager = self.key_manager.as_ref().unwrap().clone();
        cx.reply_later(async move {
            let mut slot = slot_rc
                .try_lock()
                .expect("Keys lock should never be locked twice");
            if let Some(key_set) = key_manager.send(RotateKeys(slot.clone())).await {
                *slot = key_set.clone();
                key_manager.send(UpdateKeys(key_set)).await;
            }
        });
    }
}

impl StoreSender for Addr<MemoryStore> {}
<commit_msg>For the memory store, ignore the first GC tick<commit_after>use crate::agents::*;
use crate::config::LimitConfig;
use crate::crypto::SigningAlgorithm;
use crate::utils::agent::*;
use crate::web::Session;
use std::collections::hash_map::{Entry, HashMap};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
use url::Url;

/// Combines any type with an `Instant` expiry time.
struct Expiring<T> {
    value: T,
    expires: Instant,
}

impl<T> Expiring<T> {
    /// Create a value from a duration.
    fn from_duration(value: T, duration: Duration) -> Self {
        let expires = Instant::now() + duration;
        Expiring { value, expires }
    }

    /// Whether this value has not yet expired.
    fn is_alive(&self) -> bool {
        self.expires > Instant::now()
    }
}

/// Message sent at an interval to collect garbage.
struct Gc;
impl Message for Gc {
    type Reply = ();
}

/// A slot in the cache `HashMap`.
///
/// We want to lock these individually while a fetch is in progress, so multiple requests for the
/// same URL result in only one fetch. Therefore, we use an `Arc<Mutex<_>>` to carry slots around,
/// and within an `Option` which indicates whether cache is actually present (despite the hash map
/// entry existing or not, which does not indicate anything).
type CacheSlot = Arc<Mutex<Option<Expiring<String>>>>;

/// A slot in the keys `HashMap`.
///
/// It's technically not necessary for us to lock keys, because the key manager is our only client,
/// but we do so any ways to ensure correct behavior.
type KeysSlot = Arc<Mutex<KeySet>>;

/// Store implementation using memory.
pub struct MemoryStore {
    /// TTL of session keys
    expire_sessions: Duration,
    /// TTL of cache keys
    expire_cache: Duration,
    /// Configuration for per-email rate limiting.
    limit_per_email_config: LimitConfig,
    /// The agent used for fetching on cache miss.
    fetcher: Addr<FetchAgent>,
    /// Key manager if rotating keys are enabled.
    key_manager: Option<Addr<RotatingKeys>>,
    /// Session storage.
    sessions: HashMap<String, Expiring<Session>>,
    /// Cache storage.
    cache: HashMap<Url, CacheSlot>,
    /// Rate limit storage.
    limits: HashMap<IncrAndTestLimit, Expiring<usize>>,
    /// Keys storage.
    keys: HashMap<SigningAlgorithm, KeysSlot>,
}

impl MemoryStore {
    pub fn new(
        expire_sessions: Duration,
        expire_cache: Duration,
        limit_per_email_config: LimitConfig,
        fetcher: Addr<FetchAgent>,
    ) -> Self {
        log::warn!("Storing sessions in memory.");
        log::warn!("Note that sessions will be lost on restart!");

        MemoryStore {
            expire_sessions,
            expire_cache,
            limit_per_email_config,
            fetcher,
            key_manager: None,
            sessions: HashMap::new(),
            cache: HashMap::new(),
            limits: HashMap::new(),
            keys: HashMap::new(),
        }
    }
}

impl Agent for MemoryStore {
    fn started(addr: &Addr<Self>) {
        // Start the garbage collection loop.
        let addr = addr.clone();
        tokio::spawn(async move {
            let mut interval = tokio::time::interval(Duration::from_secs(60));
            interval.tick().await;
            loop {
                interval.tick().await;
                addr.send(Gc).await;
            }
        });
    }
}

impl Handler<Gc> for MemoryStore {
    fn handle(&mut self, _message: Gc, cx: Context<Self, Gc>) {
        self.sessions = self
            .sessions
            .drain()
            .filter(|(_, ref entry)| entry.is_alive())
            .collect();
        self.cache = self
            .cache
            .drain()
            .filter(|(_, ref slot)| {
                // For cache items, only remove if we have the sole reference. Otherwise, a fetch
                // might still be in progress. Locking should be immediate if true.
                if let Ok(maybe_entry) = slot.try_lock() {
                    Arc::strong_count(slot) > 1
                        || maybe_entry
                            .as_ref()
                            .filter(|entry| entry.is_alive())
                            .is_some()
                } else {
                    true
                }
            })
            .collect();
        self.limits = self
            .limits
            .drain()
            .filter(|(_, ref entry)| entry.is_alive())
            .collect();
        cx.reply(())
    }
}

impl Handler<SaveSession> for MemoryStore {
    fn handle(&mut self, message: SaveSession, cx: Context<Self, SaveSession>) {
        self.sessions.insert(
            message.session_id,
            Expiring::from_duration(message.data, self.expire_sessions),
        );
        cx.reply(Ok(()))
    }
}

impl Handler<GetSession> for MemoryStore {
    fn handle(&mut self, message: GetSession, cx: Context<Self, GetSession>) {
        let data = self
            .sessions
            .get(&message.session_id)
            .filter(|entry| entry.is_alive())
            .map(|entry| entry.value.clone());
        cx.reply(Ok(data))
    }
}

impl Handler<DeleteSession> for MemoryStore {
    fn handle(&mut self, message: DeleteSession, cx: Context<Self, DeleteSession>) {
        self.sessions.remove(&message.session_id);
        cx.reply(Ok(()))
    }
}

impl Handler<FetchUrlCached> for MemoryStore {
    fn handle(&mut self, message: FetchUrlCached, cx: Context<Self, FetchUrlCached>) {
        let fetcher = self.fetcher.clone();
        let slot = self.cache.entry(message.url.clone()).or_default().clone();
        let expire_cache = self.expire_cache;
        cx.reply_later(async move {
            let mut slot = slot.lock().await;
            if let Some(entry) = slot.as_ref().filter(|entry| entry.is_alive()) {
                return Ok(entry.value.clone());
            }
            let result = fetcher.send(FetchUrl { url: message.url }).await?;
            let ttl = std::cmp::max(expire_cache, result.max_age);
            *slot = Some(Expiring::from_duration(result.data.clone(), ttl));
            Ok(result.data)
        });
    }
}

impl Handler<IncrAndTestLimit> for MemoryStore {
    fn handle(&mut self, message: IncrAndTestLimit, cx: Context<Self, IncrAndTestLimit>) {
        let config = match message {
            IncrAndTestLimit::PerEmail { .. } => self.limit_per_email_config,
        };
        let count: usize = match self.limits.entry(message) {
            Entry::Occupied(mut entry) => {
                let mut expiring = entry.get_mut();
                if expiring.expires <= Instant::now() {
                    *expiring = Expiring::from_duration(1, config.duration);
                    1
                } else {
                    expiring.value += 1;
                    expiring.value
                }
            }
            Entry::Vacant(entry) => {
                entry.insert(Expiring::from_duration(1, config.duration));
                1
            }
        };
        cx.reply(Ok(count <= config.max_count));
    }
}

impl Handler<EnableRotatingKeys> for MemoryStore {
    fn handle(&mut self, message: EnableRotatingKeys, cx: Context<Self, EnableRotatingKeys>) {
        self.key_manager = Some(message.key_manager.clone());
        let mut update_msgs = Vec::with_capacity(message.signing_algs.len());
        for signing_alg in &message.signing_algs {
            let key_set = KeySet::empty(*signing_alg);
            update_msgs.push(UpdateKeys(key_set.clone()));
            self.keys
                .insert(*signing_alg, Arc::new(Mutex::new(key_set)));
        }
        cx.reply_later(async move {
            for update_msg in update_msgs {
                message.key_manager.send(update_msg).await;
            }
        });
    }
}

impl Handler<RotateKeysLocked> for MemoryStore {
    fn handle(&mut self, message: RotateKeysLocked, cx: Context<Self, RotateKeysLocked>) {
        let slot_rc = self.keys[&message.0].clone();
        let key_manager = self.key_manager.as_ref().unwrap().clone();
        cx.reply_later(async move {
            let mut slot = slot_rc
                .try_lock()
                .expect("Keys lock should never be locked twice");
            if let Some(key_set) = key_manager.send(RotateKeys(slot.clone())).await {
                *slot = key_set.clone();
                key_manager.send(UpdateKeys(key_set)).await;
            }
        });
    }
}

impl StoreSender for Addr<MemoryStore> {}
<|endoftext|>


<commit_before>use anyhow::anyhow;
use cargo::core::{features, CliUnstable};
use cargo::{self, drop_print, drop_println, CliResult, Config};
use clap::{AppSettings, Arg, ArgMatches};
use itertools::Itertools;
use std::collections::HashMap;

use super::commands;
use super::list_commands;
use crate::command_prelude::*;
use cargo::core::features::HIDDEN;

lazy_static::lazy_static! {
    // Maps from commonly known external commands (not builtin to cargo) to their
    // description, for the help page. Reserved for external subcommands that are
    // core within the rust ecosystem (esp ones that might become internal in the future).
    static ref KNOWN_EXTERNAL_COMMAND_DESCRIPTIONS: HashMap<&'static str, &'static str> = vec![
        ("clippy", "Checks a package to catch common mistakes and improve your Rust code."),
        ("fmt", "Formats all bin and lib files of the current crate using rustfmt."),
    ].into_iter().collect();
}

pub fn main(config: &mut Config) -> CliResult {
    // CAUTION: Be careful with using `config` until it is configured below.
    // In general, try to avoid loading config values unless necessary (like
    // the [alias] table).

    if commands::help::handle_embedded_help(config) {
        return Ok(());
    }

    let args = match cli().get_matches_safe() {
        Ok(args) => args,
        Err(e) => {
            if e.kind == clap::ErrorKind::UnrecognizedSubcommand {
                // An unrecognized subcommand might be an external subcommand.
                let cmd = &e.info.as_ref().unwrap()[0].to_owned();
                return super::execute_external_subcommand(config, cmd, &[cmd, "--help"])
                    .map_err(|_| e.into());
            } else {
                return Err(e.into());
            }
        }
    };

    // Global args need to be extracted before expanding aliases because the
    // clap code for extracting a subcommand discards global options
    // (appearing before the subcommand).
    let (expanded_args, global_args) = expand_aliases(config, args, vec![])?;

    if expanded_args.value_of("unstable-features") == Some("help") {
        let options = CliUnstable::help();
        let non_hidden_options: Vec<(String, String)> = options
            .iter()
            .filter(|(_, help_message)| *help_message != HIDDEN)
            .map(|(name, help)| (name.to_string(), help.to_string()))
            .collect();
        let longest_option = non_hidden_options
            .iter()
            .map(|(option_name, _)| option_name.len())
            .max()
            .unwrap_or(0);
        let help_lines: Vec<String> = non_hidden_options
            .iter()
            .map(|(option_name, option_help_message)| {
                let option_name_kebab_case = option_name.replace("_", "-");
                let padding = " ".repeat(longest_option - option_name.len()); // safe to substract
                format!(
                    "    -Z {}{} -- {}",
                    option_name_kebab_case, padding, option_help_message
                )
            })
            .collect();
        let joined = help_lines.join("\n");
        drop_println!(
            config,
            "
Available unstable (nightly-only) flags:

{}

Run with 'cargo -Z [FLAG] [SUBCOMMAND]'",
            joined
        );
        if !config.nightly_features_allowed {
            drop_println!(
                config,
                "\nUnstable flags are only available on the nightly channel \
                 of Cargo, but this is the `{}` channel.\n\
                 {}",
                features::channel(),
                features::SEE_CHANNELS
            );
        }
        drop_println!(
            config,
            "\nSee https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \
             for more information about these flags."
        );
        return Ok(());
    }

    let is_verbose = expanded_args.occurrences_of("verbose") > 0;
    if args.is_present("version") {
        let version = get_version_string(is_verbose);
        drop_print!(config, "{}", version);
        return Ok(());
    }

    if let Some(code) = expanded_args.value_of("explain") {
        let mut procss = config.load_global_rustc(None)?.process();
        procss.arg("--explain").arg(code).exec()?;
        return Ok(());
    }

    if expanded_args.is_present("list") {
        drop_println!(config, "Installed Commands:");
        for (name, command) in list_commands(config) {
            let known_external_desc = KNOWN_EXTERNAL_COMMAND_DESCRIPTIONS.get(name.as_str());
            match command {
                CommandInfo::BuiltIn { about } => {
                    assert!(
                        known_external_desc.is_none(),
                        "KNOWN_EXTERNAL_COMMANDS shouldn't contain builtin \"{}\"",
                        name
                    );
                    let summary = about.unwrap_or_default();
                    let summary = summary.lines().next().unwrap_or(&summary); // display only the first line
                    drop_println!(config, "    {:<20} {}", name, summary);
                }
                CommandInfo::External { path } => {
                    if let Some(desc) = known_external_desc {
                        drop_println!(config, "    {:<20} {}", name, desc);
                    } else if is_verbose {
                        drop_println!(config, "    {:<20} {}", name, path.display());
                    } else {
                        drop_println!(config, "    {}", name);
                    }
                }
                CommandInfo::Alias { target } => {
                    drop_println!(config, "    {:<20} {}", name, target.iter().join(" "));
                }
            }
        }
        return Ok(());
    }

    let (cmd, subcommand_args) = match expanded_args.subcommand() {
        (cmd, Some(args)) => (cmd, args),
        _ => {
            // No subcommand provided.
            cli().print_help()?;
            return Ok(());
        }
    };
    config_configure(config, &expanded_args, subcommand_args, global_args)?;
    super::init_git_transports(config);

    execute_subcommand(config, cmd, subcommand_args)
}

pub fn get_version_string(is_verbose: bool) -> String {
    let version = cargo::version();
    let mut version_string = format!("cargo {}\n", version);
    if is_verbose {
        version_string.push_str(&format!(
            "release: {}.{}.{}\n",
            version.major, version.minor, version.patch
        ));
        if let Some(ref cfg) = version.cfg_info {
            if let Some(ref ci) = cfg.commit_info {
                version_string.push_str(&format!("commit-hash: {}\n", ci.commit_hash));
                version_string.push_str(&format!("commit-date: {}\n", ci.commit_date));
            }
        }
    }
    version_string
}

fn expand_aliases(
    config: &mut Config,
    args: ArgMatches<'static>,
    mut already_expanded: Vec<String>,
) -> Result<(ArgMatches<'static>, GlobalArgs), CliError> {
    if let (cmd, Some(args)) = args.subcommand() {
        match (
            commands::builtin_exec(cmd),
            super::aliased_command(config, cmd)?,
        ) {
            (Some(_), Some(_)) => {
                // User alias conflicts with a built-in subcommand
                config.shell().warn(format!(
                    "user-defined alias `{}` is ignored, because it is shadowed by a built-in command",
                    cmd,
                ))?;
            }
            (Some(_), None) => {
                // Command is built-in and is not conflicting with alias, but contains ignored values.
                if let Some(mut values) = args.values_of("") {
                    config.shell().warn(format!(
                        "trailing arguments after built-in command `{}` are ignored: `{}`",
                        cmd,
                        values.join(" "),
                    ))?;
                }
            }
            (None, None) => {}
            (_, Some(mut alias)) => {
                alias.extend(
                    args.values_of("")
                        .unwrap_or_default()
                        .map(|s| s.to_string()),
                );
                // new_args strips out everything before the subcommand, so
                // capture those global options now.
                // Note that an alias to an external command will not receive
                // these arguments. That may be confusing, but such is life.
                let global_args = GlobalArgs::new(args);
                let new_args = cli()
                    .setting(AppSettings::NoBinaryName)
                    .get_matches_from_safe(alias)?;

                let (new_cmd, _) = new_args.subcommand();
                already_expanded.push(cmd.to_string());
                if already_expanded.contains(&new_cmd.to_string()) {
                    // Crash if the aliases are corecursive / unresolvable
                    return Err(anyhow!(
                        "alias {} has unresolvable recursive definition: {} -> {}",
                        already_expanded[0],
                        already_expanded.join(" -> "),
                        new_cmd,
                    )
                    .into());
                }

                let (expanded_args, _) = expand_aliases(config, new_args, already_expanded)?;
                return Ok((expanded_args, global_args));
            }
        }
    };

    Ok((args, GlobalArgs::default()))
}

fn config_configure(
    config: &mut Config,
    args: &ArgMatches<'_>,
    subcommand_args: &ArgMatches<'_>,
    global_args: GlobalArgs,
) -> CliResult {
    let arg_target_dir = &subcommand_args.value_of_path("target-dir", config);
    let verbose = global_args.verbose + args.occurrences_of("verbose") as u32;
    // quiet is unusual because it is redefined in some subcommands in order
    // to provide custom help text.
    let quiet =
        args.is_present("quiet") || subcommand_args.is_present("quiet") || global_args.quiet;
    let global_color = global_args.color; // Extract so it can take reference.
    let color = args.value_of("color").or_else(|| global_color.as_deref());
    let frozen = args.is_present("frozen") || global_args.frozen;
    let locked = args.is_present("locked") || global_args.locked;
    let offline = args.is_present("offline") || global_args.offline;
    let mut unstable_flags = global_args.unstable_flags;
    if let Some(values) = args.values_of("unstable-features") {
        unstable_flags.extend(values.map(|s| s.to_string()));
    }
    let mut config_args = global_args.config_args;
    if let Some(values) = args.values_of("config") {
        config_args.extend(values.map(|s| s.to_string()));
    }
    config.configure(
        verbose,
        quiet,
        color,
        frozen,
        locked,
        offline,
        arg_target_dir,
        &unstable_flags,
        &config_args,
    )?;
    Ok(())
}

fn execute_subcommand(
    config: &mut Config,
    cmd: &str,
    subcommand_args: &ArgMatches<'_>,
) -> CliResult {
    if let Some(exec) = commands::builtin_exec(cmd) {
        return exec(config, subcommand_args);
    }

    let mut ext_args: Vec<&str> = vec![cmd];
    ext_args.extend(subcommand_args.values_of("").unwrap_or_default());
    super::execute_external_subcommand(config, cmd, &ext_args)
}

#[derive(Default)]
struct GlobalArgs {
    verbose: u32,
    quiet: bool,
    color: Option<String>,
    frozen: bool,
    locked: bool,
    offline: bool,
    unstable_flags: Vec<String>,
    config_args: Vec<String>,
}

impl GlobalArgs {
    fn new(args: &ArgMatches<'_>) -> GlobalArgs {
        GlobalArgs {
            verbose: args.occurrences_of("verbose") as u32,
            quiet: args.is_present("quiet"),
            color: args.value_of("color").map(|s| s.to_string()),
            frozen: args.is_present("frozen"),
            locked: args.is_present("locked"),
            offline: args.is_present("offline"),
            unstable_flags: args
                .values_of_lossy("unstable-features")
                .unwrap_or_default(),
            config_args: args
                .values_of("config")
                .unwrap_or_default()
                .map(|s| s.to_string())
                .collect(),
        }
    }
}

fn cli() -> App {
    let is_rustup = std::env::var_os("RUSTUP_HOME").is_some();
    let usage = if is_rustup {
        "cargo [+toolchain] [OPTIONS] [SUBCOMMAND]"
    } else {
        "cargo [OPTIONS] [SUBCOMMAND]"
    };
    App::new("cargo")
        .settings(&[
            AppSettings::UnifiedHelpMessage,
            AppSettings::DeriveDisplayOrder,
            AppSettings::VersionlessSubcommands,
            AppSettings::AllowExternalSubcommands,
        ])
        .usage(usage)
        .template(
            "\
Rust's package manager

USAGE:
    {usage}

OPTIONS:
{unified}

Some common cargo commands are (see all commands with --list):
    build, b    Compile the current package
    check, c    Analyze the current package and report errors, but don't build object files
    clean       Remove the target directory
    doc, d      Build this package's and its dependencies' documentation
    new         Create a new cargo package
    init        Create a new cargo package in an existing directory
    run, r      Run a binary or example of the local package
    test, t     Run the tests
    bench       Run the benchmarks
    update      Update dependencies listed in Cargo.lock
    search      Search registry for crates
    publish     Package and upload this package to the registry
    install     Install a Rust binary. Default location is $HOME/.cargo/bin
    uninstall   Uninstall a Rust binary

See 'cargo help <command>' for more information on a specific command.\n",
        )
        .arg(opt("version", "Print version info and exit").short("V"))
        .arg(opt("list", "List installed commands"))
        .arg(opt("explain", "Run `rustc --explain CODE`").value_name("CODE"))
        .arg(
            opt(
                "verbose",
                "Use verbose output (-vv very verbose/build.rs output)",
            )
            .short("v")
            .multiple(true)
            .global(true),
        )
        .arg(opt("quiet", "No output printed to stdout").short("q"))
        .arg(
            opt("color", "Coloring: auto, always, never")
                .value_name("WHEN")
                .global(true),
        )
        .arg(opt("frozen", "Require Cargo.lock and cache are up to date").global(true))
        .arg(opt("locked", "Require Cargo.lock is up to date").global(true))
        .arg(opt("offline", "Run without accessing the network").global(true))
        .arg(
            multi_opt(
                "config",
                "KEY=VALUE",
                "Override a configuration value (unstable)",
            )
            .global(true),
        )
        .arg(
            Arg::with_name("unstable-features")
                .help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details")
                .short("Z")
                .value_name("FLAG")
                .multiple(true)
                .number_of_values(1)
                .global(true),
        )
        .subcommands(commands::builtin())
}
<commit_msg>fix error<commit_after>use anyhow::anyhow;
use cargo::core::{features, CliUnstable};
use cargo::{self, drop_print, drop_println, CliResult, Config};
use clap::{AppSettings, Arg, ArgMatches};
use itertools::Itertools;
use std::collections::HashMap;

use super::commands;
use super::list_commands;
use crate::command_prelude::*;
use cargo::core::features::HIDDEN;

lazy_static::lazy_static! {
    // Maps from commonly known external commands (not builtin to cargo) to their
    // description, for the help page. Reserved for external subcommands that are
    // core within the rust ecosystem (esp ones that might become internal in the future).
    static ref KNOWN_EXTERNAL_COMMAND_DESCRIPTIONS: HashMap<&'static str, &'static str> = vec![
        ("clippy", "Checks a package to catch common mistakes and improve your Rust code."),
        ("fmt", "Formats all bin and lib files of the current crate using rustfmt."),
    ].into_iter().collect();
}

pub fn main(config: &mut Config) -> CliResult {
    // CAUTION: Be careful with using `config` until it is configured below.
    // In general, try to avoid loading config values unless necessary (like
    // the [alias] table).

    if commands::help::handle_embedded_help(config) {
        return Ok(());
    }

    let args = match cli().get_matches_safe() {
        Ok(args) => args,
        Err(e) => {
            if e.kind == clap::ErrorKind::UnrecognizedSubcommand {
                // An unrecognized subcommand might be an external subcommand.
                let cmd = &e.info.as_ref().unwrap()[0].to_owned();
                return super::execute_external_subcommand(config, cmd, &[cmd, "--help"])
                    .map_err(|_| e.into());
            } else {
                return Err(e.into());
            }
        }
    };

    // Global args need to be extracted before expanding aliases because the
    // clap code for extracting a subcommand discards global options
    // (appearing before the subcommand).
    let (expanded_args, global_args) = expand_aliases(config, args, vec![])?;

    if expanded_args.value_of("unstable-features") == Some("help") {
        let options = CliUnstable::help();
        let non_hidden_options: Vec<(String, String)> = options
            .iter()
            .filter(|(_, help_message)| *help_message != HIDDEN)
            .map(|(name, help)| (name.to_string(), help.to_string()))
            .collect();
        let longest_option = non_hidden_options
            .iter()
            .map(|(option_name, _)| option_name.len())
            .max()
            .unwrap_or(0);
        let help_lines: Vec<String> = non_hidden_options
            .iter()
            .map(|(option_name, option_help_message)| {
                let option_name_kebab_case = option_name.replace("_", "-");
                let padding = " ".repeat(longest_option - option_name.len()); // safe to substract
                format!(
                    "    -Z {}{} -- {}",
                    option_name_kebab_case, padding, option_help_message
                )
            })
            .collect();
        let joined = help_lines.join("\n");
        drop_println!(
            config,
            "
Available unstable (nightly-only) flags:

{}

Run with 'cargo -Z [FLAG] [SUBCOMMAND]'",
            joined
        );
        if !config.nightly_features_allowed {
            drop_println!(
                config,
                "\nUnstable flags are only available on the nightly channel \
                 of Cargo, but this is the `{}` channel.\n\
                 {}",
                features::channel(),
                features::SEE_CHANNELS
            );
        }
        drop_println!(
            config,
            "\nSee https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \
             for more information about these flags."
        );
        return Ok(());
    }

    let is_verbose = expanded_args.occurrences_of("verbose") > 0;
    if expanded_args.is_present("version") {
        let version = get_version_string(is_verbose);
        drop_print!(config, "{}", version);
        return Ok(());
    }

    if let Some(code) = expanded_args.value_of("explain") {
        let mut procss = config.load_global_rustc(None)?.process();
        procss.arg("--explain").arg(code).exec()?;
        return Ok(());
    }

    if expanded_args.is_present("list") {
        drop_println!(config, "Installed Commands:");
        for (name, command) in list_commands(config) {
            let known_external_desc = KNOWN_EXTERNAL_COMMAND_DESCRIPTIONS.get(name.as_str());
            match command {
                CommandInfo::BuiltIn { about } => {
                    assert!(
                        known_external_desc.is_none(),
                        "KNOWN_EXTERNAL_COMMANDS shouldn't contain builtin \"{}\"",
                        name
                    );
                    let summary = about.unwrap_or_default();
                    let summary = summary.lines().next().unwrap_or(&summary); // display only the first line
                    drop_println!(config, "    {:<20} {}", name, summary);
                }
                CommandInfo::External { path } => {
                    if let Some(desc) = known_external_desc {
                        drop_println!(config, "    {:<20} {}", name, desc);
                    } else if is_verbose {
                        drop_println!(config, "    {:<20} {}", name, path.display());
                    } else {
                        drop_println!(config, "    {}", name);
                    }
                }
                CommandInfo::Alias { target } => {
                    drop_println!(config, "    {:<20} {}", name, target.iter().join(" "));
                }
            }
        }
        return Ok(());
    }

    let (cmd, subcommand_args) = match expanded_args.subcommand() {
        (cmd, Some(args)) => (cmd, args),
        _ => {
            // No subcommand provided.
            cli().print_help()?;
            return Ok(());
        }
    };
    config_configure(config, &expanded_args, subcommand_args, global_args)?;
    super::init_git_transports(config);

    execute_subcommand(config, cmd, subcommand_args)
}

pub fn get_version_string(is_verbose: bool) -> String {
    let version = cargo::version();
    let mut version_string = format!("cargo {}\n", version);
    if is_verbose {
        version_string.push_str(&format!(
            "release: {}.{}.{}\n",
            version.major, version.minor, version.patch
        ));
        if let Some(ref cfg) = version.cfg_info {
            if let Some(ref ci) = cfg.commit_info {
                version_string.push_str(&format!("commit-hash: {}\n", ci.commit_hash));
                version_string.push_str(&format!("commit-date: {}\n", ci.commit_date));
            }
        }
    }
    version_string
}

fn expand_aliases(
    config: &mut Config,
    args: ArgMatches<'static>,
    mut already_expanded: Vec<String>,
) -> Result<(ArgMatches<'static>, GlobalArgs), CliError> {
    if let (cmd, Some(args)) = args.subcommand() {
        match (
            commands::builtin_exec(cmd),
            super::aliased_command(config, cmd)?,
        ) {
            (Some(_), Some(_)) => {
                // User alias conflicts with a built-in subcommand
                config.shell().warn(format!(
                    "user-defined alias `{}` is ignored, because it is shadowed by a built-in command",
                    cmd,
                ))?;
            }
            (Some(_), None) => {
                // Command is built-in and is not conflicting with alias, but contains ignored values.
                if let Some(mut values) = args.values_of("") {
                    config.shell().warn(format!(
                        "trailing arguments after built-in command `{}` are ignored: `{}`",
                        cmd,
                        values.join(" "),
                    ))?;
                }
            }
            (None, None) => {}
            (_, Some(mut alias)) => {
                alias.extend(
                    args.values_of("")
                        .unwrap_or_default()
                        .map(|s| s.to_string()),
                );
                // new_args strips out everything before the subcommand, so
                // capture those global options now.
                // Note that an alias to an external command will not receive
                // these arguments. That may be confusing, but such is life.
                let global_args = GlobalArgs::new(args);
                let new_args = cli()
                    .setting(AppSettings::NoBinaryName)
                    .get_matches_from_safe(alias)?;

                let (new_cmd, _) = new_args.subcommand();
                already_expanded.push(cmd.to_string());
                if already_expanded.contains(&new_cmd.to_string()) {
                    // Crash if the aliases are corecursive / unresolvable
                    return Err(anyhow!(
                        "alias {} has unresolvable recursive definition: {} -> {}",
                        already_expanded[0],
                        already_expanded.join(" -> "),
                        new_cmd,
                    )
                    .into());
                }

                let (expanded_args, _) = expand_aliases(config, new_args, already_expanded)?;
                return Ok((expanded_args, global_args));
            }
        }
    };

    Ok((args, GlobalArgs::default()))
}

fn config_configure(
    config: &mut Config,
    args: &ArgMatches<'_>,
    subcommand_args: &ArgMatches<'_>,
    global_args: GlobalArgs,
) -> CliResult {
    let arg_target_dir = &subcommand_args.value_of_path("target-dir", config);
    let verbose = global_args.verbose + args.occurrences_of("verbose") as u32;
    // quiet is unusual because it is redefined in some subcommands in order
    // to provide custom help text.
    let quiet =
        args.is_present("quiet") || subcommand_args.is_present("quiet") || global_args.quiet;
    let global_color = global_args.color; // Extract so it can take reference.
    let color = args.value_of("color").or_else(|| global_color.as_deref());
    let frozen = args.is_present("frozen") || global_args.frozen;
    let locked = args.is_present("locked") || global_args.locked;
    let offline = args.is_present("offline") || global_args.offline;
    let mut unstable_flags = global_args.unstable_flags;
    if let Some(values) = args.values_of("unstable-features") {
        unstable_flags.extend(values.map(|s| s.to_string()));
    }
    let mut config_args = global_args.config_args;
    if let Some(values) = args.values_of("config") {
        config_args.extend(values.map(|s| s.to_string()));
    }
    config.configure(
        verbose,
        quiet,
        color,
        frozen,
        locked,
        offline,
        arg_target_dir,
        &unstable_flags,
        &config_args,
    )?;
    Ok(())
}

fn execute_subcommand(
    config: &mut Config,
    cmd: &str,
    subcommand_args: &ArgMatches<'_>,
) -> CliResult {
    if let Some(exec) = commands::builtin_exec(cmd) {
        return exec(config, subcommand_args);
    }

    let mut ext_args: Vec<&str> = vec![cmd];
    ext_args.extend(subcommand_args.values_of("").unwrap_or_default());
    super::execute_external_subcommand(config, cmd, &ext_args)
}

#[derive(Default)]
struct GlobalArgs {
    verbose: u32,
    quiet: bool,
    color: Option<String>,
    frozen: bool,
    locked: bool,
    offline: bool,
    unstable_flags: Vec<String>,
    config_args: Vec<String>,
}

impl GlobalArgs {
    fn new(args: &ArgMatches<'_>) -> GlobalArgs {
        GlobalArgs {
            verbose: args.occurrences_of("verbose") as u32,
            quiet: args.is_present("quiet"),
            color: args.value_of("color").map(|s| s.to_string()),
            frozen: args.is_present("frozen"),
            locked: args.is_present("locked"),
            offline: args.is_present("offline"),
            unstable_flags: args
                .values_of_lossy("unstable-features")
                .unwrap_or_default(),
            config_args: args
                .values_of("config")
                .unwrap_or_default()
                .map(|s| s.to_string())
                .collect(),
        }
    }
}

fn cli() -> App {
    let is_rustup = std::env::var_os("RUSTUP_HOME").is_some();
    let usage = if is_rustup {
        "cargo [+toolchain] [OPTIONS] [SUBCOMMAND]"
    } else {
        "cargo [OPTIONS] [SUBCOMMAND]"
    };
    App::new("cargo")
        .settings(&[
            AppSettings::UnifiedHelpMessage,
            AppSettings::DeriveDisplayOrder,
            AppSettings::VersionlessSubcommands,
            AppSettings::AllowExternalSubcommands,
        ])
        .usage(usage)
        .template(
            "\
Rust's package manager

USAGE:
    {usage}

OPTIONS:
{unified}

Some common cargo commands are (see all commands with --list):
    build, b    Compile the current package
    check, c    Analyze the current package and report errors, but don't build object files
    clean       Remove the target directory
    doc, d      Build this package's and its dependencies' documentation
    new         Create a new cargo package
    init        Create a new cargo package in an existing directory
    run, r      Run a binary or example of the local package
    test, t     Run the tests
    bench       Run the benchmarks
    update      Update dependencies listed in Cargo.lock
    search      Search registry for crates
    publish     Package and upload this package to the registry
    install     Install a Rust binary. Default location is $HOME/.cargo/bin
    uninstall   Uninstall a Rust binary

See 'cargo help <command>' for more information on a specific command.\n",
        )
        .arg(opt("version", "Print version info and exit").short("V"))
        .arg(opt("list", "List installed commands"))
        .arg(opt("explain", "Run `rustc --explain CODE`").value_name("CODE"))
        .arg(
            opt(
                "verbose",
                "Use verbose output (-vv very verbose/build.rs output)",
            )
            .short("v")
            .multiple(true)
            .global(true),
        )
        .arg(opt("quiet", "No output printed to stdout").short("q"))
        .arg(
            opt("color", "Coloring: auto, always, never")
                .value_name("WHEN")
                .global(true),
        )
        .arg(opt("frozen", "Require Cargo.lock and cache are up to date").global(true))
        .arg(opt("locked", "Require Cargo.lock is up to date").global(true))
        .arg(opt("offline", "Run without accessing the network").global(true))
        .arg(
            multi_opt(
                "config",
                "KEY=VALUE",
                "Override a configuration value (unstable)",
            )
            .global(true),
        )
        .arg(
            Arg::with_name("unstable-features")
                .help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details")
                .short("Z")
                .value_name("FLAG")
                .multiple(true)
                .number_of_values(1)
                .global(true),
        )
        .subcommands(commands::builtin())
}
<|endoftext|>


<commit_before>use lexer::{Symbol, Token, Tokens};
use function::{Function, Functions, functions};
use error::EvaluatorError;

pub struct Evaluator {
    functions: Functions,
}

impl Evaluator {
    pub fn new() -> Evaluator {
        Evaluator { functions: Functions::new() }
    }

    pub fn evaluate(&self, tokens: Tokens) -> Result<Vec<String>, EvaluatorError> {
        let mut result: Vec<String> = Vec::with_capacity(3);
        let mut stack: Vec<i32> = Vec::with_capacity(3);

        if tokens.is_empty() {
            return Ok(result);
        }

        for (position, ref token) in tokens {
            match *token {
                Token::Decimal(ref dec) => {
                    stack.push(dec.parse().unwrap());
                }
                Token::Operator(ref op) => {
                    let function = self.functions.get(&op).unwrap();
                    if stack.len() >= function.arity {
                        let stack_len = stack.len();
                        let args: Vec<i32> = stack.split_off(stack_len - function.arity);
                        let interm_result = try!((function.handle)(args, position));
                        stack.push(interm_result.0);
                        result.push(interm_result.1);
                        result.push(interm_result.0.to_string());
                    } else {
                        return Err(EvaluatorError::MissingArgument(position));
                    }
                }
                _ => continue,

            }
        }
        if stack.len() == 1 {
            result.push(stack.pop().unwrap().to_string());
        } else {
            return Err(EvaluatorError::TooManyArguments);
        }
        Ok(result)
    }
}

impl Default for Evaluator {
    fn default() -> Evaluator {
        let mut evaluator = Evaluator::new();

        evaluator.functions.insert(Symbol::NOT, Function::new(1, Box::new(functions::not)));
        evaluator.functions.insert(Symbol::XOR, Function::new(2, Box::new(functions::xor)));
        evaluator.functions.insert(Symbol::OR, Function::new(2, Box::new(functions::or)));
        evaluator.functions.insert(Symbol::AND, Function::new(2, Box::new(functions::and)));
        evaluator.functions.insert(Symbol::LSHIFT,
                                   Function::new(2, Box::new(functions::lshift)));
        evaluator.functions.insert(Symbol::RSHIFT,
                                   Function::new(2, Box::new(functions::rshift)));

        evaluator
    }
}
<commit_msg>Deduplicate the last result during evaluation<commit_after>use lexer::{Symbol, Token, Tokens};
use function::{Function, Functions, functions};
use error::EvaluatorError;

pub struct Evaluator {
    functions: Functions,
}

impl Evaluator {
    pub fn new() -> Evaluator {
        Evaluator { functions: Functions::new() }
    }

    pub fn evaluate(&self, tokens: Tokens) -> Result<Vec<String>, EvaluatorError> {
        let mut result: Vec<String> = Vec::with_capacity(3);
        let mut stack: Vec<i32> = Vec::with_capacity(3);

        if tokens.is_empty() {
            return Ok(result);
        }

        for (position, ref token) in tokens {
            match *token {
                Token::Decimal(ref dec) => {
                    stack.push(dec.parse().unwrap());
                }
                Token::Operator(ref op) => {
                    let function = self.functions.get(&op).unwrap();
                    if stack.len() >= function.arity {
                        let stack_len = stack.len();
                        let args: Vec<i32> = stack.split_off(stack_len - function.arity);
                        let interm_result = try!((function.handle)(args, position));
                        stack.push(interm_result.0);
                        result.push(interm_result.1);
                        result.push(interm_result.0.to_string());
                    } else {
                        return Err(EvaluatorError::MissingArgument(position));
                    }
                }
                _ => continue,

            }
        }

        // The last element on the stack should be the final result
        if stack.len() != 1 {
            return Err(EvaluatorError::TooManyArguments);
        }

        Ok(result)
    }
}

impl Default for Evaluator {
    fn default() -> Evaluator {
        let mut evaluator = Evaluator::new();

        evaluator.functions.insert(Symbol::NOT, Function::new(1, Box::new(functions::not)));
        evaluator.functions.insert(Symbol::XOR, Function::new(2, Box::new(functions::xor)));
        evaluator.functions.insert(Symbol::OR, Function::new(2, Box::new(functions::or)));
        evaluator.functions.insert(Symbol::AND, Function::new(2, Box::new(functions::and)));
        evaluator.functions.insert(Symbol::LSHIFT,
                                   Function::new(2, Box::new(functions::lshift)));
        evaluator.functions.insert(Symbol::RSHIFT,
                                   Function::new(2, Box::new(functions::rshift)));

        evaluator
    }
}
<|endoftext|>


<commit_before>use std::io::Cursor;

use kuchiki::NodeRef;
use kuchiki::NodeDataRef;
use kuchiki::NodeData;
use kuchiki::ElementData;

use model::ListTopicItem;
use model::ListTopicTitleItem;
use model::ListTopicAuthorItem;
use model::ShowItem;
use model::ShowReplyItem;
use model::UrlQueryItem;
use reply_model::*;

use regex::Regex;
use url::Url;
use std::collections::HashMap;

#[derive(Clone)]
pub struct Show { }

impl Show {
    pub fn new() -> Self {
        Show {}
    }

    pub fn build(&mut self, document: &NodeRef,  url: &str) -> Result<ShowItem, &'static str> {

        let url_query = match self.parse_url_query_item(&url) {
            Ok(url_query) => url_query,
            Err(e) =>  {
                error!("{}", e);
                return Err(e)
            }
        };

        let (title, reply_count) = match self.parse_title_and_reply_count(&document, &url) {
            Ok((title,reply_count)) => (title, reply_count),
            Err(e) =>  {
                error!("{}", e);
                return Err(e)
            }
        };

        let (page, max_page) = {

            let page_select_option = document.select("select[name='page']").ok().map_or(None, |x| x.last());

            if page_select_option.is_none() {
                return Err("fail to build page and max_page, reason: 'page_select' not found");
            }

            let page_select = page_select_option.unwrap();

            let page_str_option = page_select.as_node().select("option[selected='selected']").ok().map_or(None, |mut x| x.next());

            if page_str_option.is_none() {
                return Err("fail to build page and max_page, reason: 'page_str' not found");
            }

            let page_str = page_str_option.unwrap();

            let max_page_str_option = page_select.as_node().select("option").ok().map_or(None, |x| x.last());

            if max_page_str_option.is_none() {
                return Err("fail to build page and max_page, reason: 'max_page_str' not found");
            }

            let max_page_str = max_page_str_option.unwrap();

            let page = page_str.text_contents().trim().to_string().parse::<usize>().unwrap_or(0);
            let max_page = max_page_str.text_contents()
                                       .trim()
                                       .to_string()
                                       .parse::<usize>()
                                       .unwrap_or(0);

            (page, max_page)
        };

        let replies = {
            let replies_option = self.parse_show_reply_items(&document);

            if replies_option.is_err() {
                let e = replies_option.err().unwrap();
                error!("{:?}", e);
                return Err(e);
            }

            replies_option.unwrap()
        };

        let show_item = ShowItem {
            url_query: url_query,
            replies: replies,
            page: page,
            max_page: max_page,
            reply_count: String::from(reply_count),
            title: String::from(title),
        };

        Ok(show_item)
    }

}

impl Show {

    fn parse_url_query_item(&self, url_str: &str) -> Result<UrlQueryItem, &'static str> {

        let url_option = Url::parse(&url_str);
        if url_option.is_err() {
            return Err("fail to parse url query item, reason: invalid url");
        }
        let url = url_option.unwrap();

        let query_option = url.query();
        if query_option.is_none() {
            return Err("fail to parse url query item, reason: invalid url query");
        }
        let query = query_option.unwrap();

        let re = Regex::new(r"(\\?|&)(?P<key>[^&=]+)=(?P<value>[^&]+)").expect("fail to parse url query item, reason: invalid regex");

        let (channel, message) = {

            let mut map = HashMap::new();

            for cap in re.captures_iter(query) {
                let key = cap.name("key").unwrap_or("").to_string();
                let value = cap.name("value").unwrap_or("").to_string();
                map.entry(key).or_insert(value);
            }

            let count = map.len();
            if count < 2 {
                error!("length of map is invalid. length: {}", count);
                return Err(&"length of map is invalid.");
            }

            let type_option = map.get("type");
            if type_option.is_none() {
                return Err(&"fail to parse url query item, reason: can not get value of 'type' attribute");
            }

            let message_option = map.get("message");
            if message_option.is_none() {
                return Err(&"fail to parse url query item, reason: can not get value of 'message' attribute");
            }

            (
                type_option.unwrap().to_string(),
                message_option.unwrap().to_string()
            )
        };

        Ok(
            UrlQueryItem {
                channel: String::from(channel),
                message: String::from(message)
            }
        )
    }

    fn parse_title_and_reply_count (&self, document: &NodeRef,  url: &str) -> Result<(String, String), &'static str> {

        return match document.select(".repliers tr") {
            Ok(mut trs) => {
                return match trs.next()  {
                    Some(repliers_tr) => {

                        let repliers_header_option = repliers_tr.as_node().select(".repliers_header").ok().map_or(None, |x| x.last() );

                        if repliers_header_option.is_none() {
                            return Err("fail to build title and reply_count, reason: 'repliers_header' not found");
                        }

                        let repliers_header = repliers_header_option.unwrap();

                        let divs_option = repliers_header.as_node().select("div").ok().map_or(None, |x| Some(x.collect::<Vec<_>>()));

                        if divs_option.is_none() {
                            return Err("fail to build title and reply_count, reason: 'divs' not found");
                        }

                        let divs = divs_option.unwrap();

                        let mut divs_enumerator = divs.iter().enumerate();

                        let count = divs_enumerator.clone().count();
                        if  count < 2 {
                            error!("length of topic_data is invalid. length: {}", count);
                            return Err(&"length of topic_data is invalid.");
                        }

                        let title_option = match divs_enumerator.clone().filter(|&(i, _)| i == 0).map(|(i, e)| (i,e)).next() {
                            Some((i, div)) => {
                                info!("{} => {:?}", i, div.text_contents());
                                Some(div.text_contents().trim().to_string())
                            },
                            None => None
                        };

                        let reply_count = match divs_enumerator.clone().filter(|&(i, _)| i == 1).map(|(i, e)| (i,e)).next() {
                            Some((i, div)) => {
                                info!("{} => {:?}", i, div.text_contents());
                                let re = Regex::new(r"^(?P<count>\d+)個回應$").expect("fail to build title and reply_count, reason: invalid regex");
                                let s_trimmed = div.text_contents().trim().to_string();
                                let cap_option = re.captures(&s_trimmed);
                                if cap_option.is_none() {
                                    None
                                } else {
                                    Some(cap_option.unwrap().name("count").unwrap_or("0").to_string())
                                }
                            },
                            None => None
                        };

                        if  title_option.is_none() || reply_count.is_none() {
                            return Err(&"fail to build title and reply_count, reason: 'topic_data' not found");
                        }

                        Ok(
                            (
                                title_option.unwrap().to_string(),
                                reply_count.unwrap().to_string()
                            )
                        )
                    },
                    None => Err(&"fail to build title and reply_count, reason: 'repliers_tr' not found")
                }
            },
            Err(e) =>  Err("fail to build title and reply_count, reason: 'repliers_tr' not found")
        };

    }

    fn parse_show_reply_items(&self, document: &NodeRef) -> Result<Vec<ShowReplyItem>, &'static str>  {

        let replies_data_option = document.select(".repliers tr[userid][username]").ok().map_or(None, |x| Some(x.collect::<Vec<_>>()) );

        if replies_data_option.is_none() {
            return Err(&"fail to parse show reply items, reason: 'replies_data' not found");
        }

        let replies_data = replies_data_option.unwrap();

        let show_replies = replies_data.iter().enumerate().map(reply_items_handler).collect::<Vec<_>>();

        let err_show_reply_option = show_replies.iter().filter(|x| x.is_err()).next();

        if err_show_reply_option.is_some() {
            return Err(&"fail to parse show reply items, reason: 'error show reply item' was found");
        }

        let result = show_replies.iter().map(|x| x.clone().unwrap() ).collect::<Vec<_>>();

        Ok(result)
    }
}

fn reply_items_handler((index,tr): (usize, &::kuchiki::NodeDataRef<::kuchiki::ElementData>)) -> Result<ShowReplyItem, &'static str> {
    let tr_attrs = (&tr.attributes).borrow();
    let userid_option = tr_attrs.get("userid");

    if userid_option.is_none() {
        return Err("fail to parse show reply item, reason: 'userid' not found");
    }

    let userid = userid_option.unwrap();

    let username_option = tr_attrs.get("username");

    if username_option.is_none() {
        return Err("fail to parse show reply item, reason: 'userame' not found");
    }

    let username = username_option.unwrap();

    let content_elm_option = tr.as_node().select(".repliers_right .ContentGrid").ok().map_or(None, |mut x| x.next());

    if content_elm_option.is_none() {
        return Err("fail to parse show reply item, reason: 'content_elm' not found");
    }
    let content_elm = content_elm_option.unwrap();

    let mut buff = Cursor::new(Vec::new());
    let serialize_result = content_elm.as_node().serialize(&mut buff);
    let vec = buff.into_inner();
    let content_result = String::from_utf8(vec);

    if content_result.is_err() {
        return Err("fail to parse show reply item, reason: 'content' invalid");
    }

    let content = content_result.unwrap();

    let datatime_option = tr.as_node().select(".repliers_right span").ok()
                    .map_or(None, |mut x| x.last() )
                    .map_or(None, |mut x| Some(x.text_contents()));

    if datatime_option.is_none() {
        return Err("fail to parse show reply item, reason: 'datatime' not found");
    }

    let datatime = datatime_option.unwrap();

    let mut vec: Vec<NodeType> = Vec::new();

    vec = recursive(content_elm.as_node());

    Ok(
        ShowReplyItem {
            userid: String::from(userid),
            username: String::from(username),
            content: String::from(content),
            body: vec,
            published_at: String::from(datatime),
        }
    )
}

fn recursive(elm: &NodeRef) -> Vec<NodeType> {

    let mut vec: Vec<NodeType> = Vec::new();

    for (index, child) in elm.children().enumerate() {
        // println!("[{}] => {:?}", index, child);
        let node_data = child.data().clone();

        match node_data {
            NodeData::Element(element_data) => {
                // println!("[{}] => [ELEMENT] => {:?}", index, element_data);
                // println!("[{}] => [ELEMENT] => {:?}", index, child);

                if element_data.name.local.trim().eq("blockquote") {

                    // println!("[{}] => [ELEMENT] => {:?}", index, child.children());
                    let subvec = recursive(&child);
                    let node = NodeType::BlockQuote(BlockQuoteNode { data: subvec });
                    vec.push(node);
                } else if element_data.name.local.trim().eq("br") {
                    let node = NodeType::Br(BrNode {});
                    vec.push(node);
                } else if element_data.name.local.trim().eq("img") {

                    let attrs = (&element_data.attributes).borrow();
                    let url = attrs.get("src").unwrap_or("");
                    let alt = attrs.get("alt").unwrap_or("");
                    let node = NodeType::Image(ImageNode { data: url.to_string(), alt: alt.to_string() });
                    vec.push(node);

                } else {
                    // println!("[{}] => [ELEMENT] => {:?}", index, child);
                    let mut subvec = recursive(&child);
                    vec.append(&mut subvec);
                }
            }
            NodeData::Text(rc) => {
                // println!("[{}] => [TEXT] => {:?}", index, rc);
                let d = rc.clone();
                let b = d.borrow();

                let s = b.trim().to_string();

                if s == "\n" {
                    // s = "\\n".to_string()
                    continue;
                }

                let node = NodeType::Text(TextNode { data: s });
                vec.push(node);
            }
            _ => {}
        }
    }
    vec
}
<commit_msg>[refactor] show builder<commit_after>use std::io::Cursor;

use kuchiki::NodeRef;
use kuchiki::NodeDataRef;
use kuchiki::NodeData;
use kuchiki::ElementData;

use model::ShowItem;
use model::ShowReplyItem;
use model::UrlQueryItem;
use reply_model::*;

use regex::Regex;
use url::Url;
use std::collections::HashMap;

#[derive(Clone)]
pub struct Show { }

impl Show {
    pub fn new() -> Self {
        Show {}
    }

    pub fn build(&mut self, document: &NodeRef,  url: &str) -> Result<ShowItem, &'static str> {

        let url_query = match self.parse_url_query_item(&url) {
            Ok(url_query) => url_query,
            Err(e) =>  {
                error!("{}", e);
                return Err(e)
            }
        };

        let (title, reply_count) = match self.parse_title_and_reply_count(&document, &url) {
            Ok((title,reply_count)) => (title, reply_count),
            Err(e) =>  {
                error!("{}", e);
                return Err(e)
            }
        };

        let (page, max_page) = {

            let page_select_option = document.select("select[name='page']").ok().map_or(None, |x| x.last());

            if page_select_option.is_none() {
                return Err("fail to build page and max_page, reason: 'page_select' not found");
            }

            let page_select = page_select_option.unwrap();

            let page_str_option = page_select.as_node().select("option[selected='selected']").ok().map_or(None, |mut x| x.next());

            if page_str_option.is_none() {
                return Err("fail to build page and max_page, reason: 'page_str' not found");
            }

            let page_str = page_str_option.unwrap();

            let max_page_str_option = page_select.as_node().select("option").ok().map_or(None, |x| x.last());

            if max_page_str_option.is_none() {
                return Err("fail to build page and max_page, reason: 'max_page_str' not found");
            }

            let max_page_str = max_page_str_option.unwrap();

            let page = page_str.text_contents().trim().to_string().parse::<usize>().unwrap_or(0);
            let max_page = max_page_str.text_contents()
                                       .trim()
                                       .to_string()
                                       .parse::<usize>()
                                       .unwrap_or(0);

            (page, max_page)
        };

        let replies = {
            let replies_option = self.parse_show_reply_items(&document);

            if replies_option.is_err() {
                let e = replies_option.err().unwrap();
                error!("{:?}", e);
                return Err(e);
            }

            replies_option.unwrap()
        };

        let show_item = ShowItem {
            url_query: url_query,
            replies: replies,
            page: page,
            max_page: max_page,
            reply_count: String::from(reply_count),
            title: String::from(title),
        };

        Ok(show_item)
    }

}

impl Show {

    fn parse_url_query_item(&self, url_str: &str) -> Result<UrlQueryItem, &'static str> {

        let url_option = Url::parse(&url_str);
        if url_option.is_err() {
            return Err("fail to parse url query item, reason: invalid url");
        }
        let url = url_option.unwrap();

        let query_option = url.query();
        if query_option.is_none() {
            return Err("fail to parse url query item, reason: invalid url query");
        }
        let query = query_option.unwrap();

        let re = Regex::new(r"(\\?|&)(?P<key>[^&=]+)=(?P<value>[^&]+)").expect("fail to parse url query item, reason: invalid regex");

        let (channel, message) = {

            let mut map = HashMap::new();

            for cap in re.captures_iter(query) {
                let key = cap.name("key").unwrap_or("").to_string();
                let value = cap.name("value").unwrap_or("").to_string();
                map.entry(key).or_insert(value);
            }

            let count = map.len();
            if count < 2 {
                error!("length of map is invalid. length: {}", count);
                return Err(&"length of map is invalid.");
            }

            let type_option = map.get("type");
            if type_option.is_none() {
                return Err(&"fail to parse url query item, reason: can not get value of 'type' attribute");
            }

            let message_option = map.get("message");
            if message_option.is_none() {
                return Err(&"fail to parse url query item, reason: can not get value of 'message' attribute");
            }

            (
                type_option.unwrap().to_string(),
                message_option.unwrap().to_string()
            )
        };

        Ok(
            UrlQueryItem {
                channel: String::from(channel),
                message: String::from(message)
            }
        )
    }

    fn parse_title_and_reply_count (&self, document: &NodeRef,  url: &str) -> Result<(String, String), &'static str> {

        return match document.select(".repliers tr") {
            Ok(mut trs) => {
                return match trs.next()  {
                    Some(repliers_tr) => {

                        let repliers_header_option = repliers_tr.as_node().select(".repliers_header").ok().map_or(None, |x| x.last() );

                        if repliers_header_option.is_none() {
                            return Err("fail to build title and reply_count, reason: 'repliers_header' not found");
                        }

                        let repliers_header = repliers_header_option.unwrap();

                        let divs_option = repliers_header.as_node().select("div").ok().map_or(None, |x| Some(x.collect::<Vec<_>>()));

                        if divs_option.is_none() {
                            return Err("fail to build title and reply_count, reason: 'divs' not found");
                        }

                        let divs = divs_option.unwrap();

                        let mut divs_enumerator = divs.iter().enumerate();

                        let count = divs_enumerator.clone().count();
                        if  count < 2 {
                            error!("length of topic_data is invalid. length: {}", count);
                            return Err(&"length of topic_data is invalid.");
                        }

                        let title_option = match divs_enumerator.clone().filter(|&(i, _)| i == 0).map(|(i, e)| (i,e)).next() {
                            Some((i, div)) => {
                                info!("{} => {:?}", i, div.text_contents());
                                Some(div.text_contents().trim().to_string())
                            },
                            None => None
                        };

                        let reply_count = match divs_enumerator.clone().filter(|&(i, _)| i == 1).map(|(i, e)| (i,e)).next() {
                            Some((i, div)) => {
                                info!("{} => {:?}", i, div.text_contents());
                                let re = Regex::new(r"^(?P<count>\d+)個回應$").expect("fail to build title and reply_count, reason: invalid regex");
                                let s_trimmed = div.text_contents().trim().to_string();
                                let cap_option = re.captures(&s_trimmed);
                                if cap_option.is_none() {
                                    None
                                } else {
                                    Some(cap_option.unwrap().name("count").unwrap_or("0").to_string())
                                }
                            },
                            None => None
                        };

                        if  title_option.is_none() || reply_count.is_none() {
                            return Err(&"fail to build title and reply_count, reason: 'topic_data' not found");
                        }

                        Ok(
                            (
                                title_option.unwrap().to_string(),
                                reply_count.unwrap().to_string()
                            )
                        )
                    },
                    None => Err(&"fail to build title and reply_count, reason: 'repliers_tr' not found")
                }
            },
            Err(e) =>  Err("fail to build title and reply_count, reason: 'repliers_tr' not found")
        };

    }

    fn parse_show_reply_items(&self, document: &NodeRef) -> Result<Vec<ShowReplyItem>, &'static str>  {

        let replies_data_option = document.select(".repliers tr[userid][username]").ok().map_or(None, |x| Some(x.collect::<Vec<_>>()) );

        if replies_data_option.is_none() {
            return Err(&"fail to parse show reply items, reason: 'replies_data' not found");
        }

        let replies_data = replies_data_option.unwrap();

        let show_replies = replies_data.iter().enumerate().map(reply_items_handler).collect::<Vec<_>>();

        let err_show_reply_option = show_replies.iter().filter(|x| x.is_err()).next();

        if err_show_reply_option.is_some() {
            return Err(&"fail to parse show reply items, reason: 'error show reply item' was found");
        }

        let result = show_replies.iter().map(|x| x.clone().unwrap() ).collect::<Vec<_>>();

        Ok(result)
    }
}

fn reply_items_handler((index,tr): (usize, &::kuchiki::NodeDataRef<::kuchiki::ElementData>)) -> Result<ShowReplyItem, &'static str> {
    let tr_attrs = (&tr.attributes).borrow();
    let userid_option = tr_attrs.get("userid");

    if userid_option.is_none() {
        return Err("fail to parse show reply item, reason: 'userid' not found");
    }

    let userid = userid_option.unwrap();

    let username_option = tr_attrs.get("username");

    if username_option.is_none() {
        return Err("fail to parse show reply item, reason: 'userame' not found");
    }

    let username = username_option.unwrap();

    let content_elm_option = tr.as_node().select(".repliers_right .ContentGrid").ok().map_or(None, |mut x| x.next());

    if content_elm_option.is_none() {
        return Err("fail to parse show reply item, reason: 'content_elm' not found");
    }
    let content_elm = content_elm_option.unwrap();

    let mut buff = Cursor::new(Vec::new());
    let serialize_result = content_elm.as_node().serialize(&mut buff);
    let vec = buff.into_inner();
    let content_result = String::from_utf8(vec);

    if content_result.is_err() {
        return Err("fail to parse show reply item, reason: 'content' invalid");
    }

    let content = content_result.unwrap();

    let datatime_option = tr.as_node().select(".repliers_right span").ok()
                    .map_or(None, |mut x| x.last() )
                    .map_or(None, |mut x| Some(x.text_contents()));

    if datatime_option.is_none() {
        return Err("fail to parse show reply item, reason: 'datatime' not found");
    }

    let datatime = datatime_option.unwrap();

    let mut vec: Vec<NodeType> = Vec::new();

    vec = recursive(content_elm.as_node());

    Ok(
        ShowReplyItem {
            userid: String::from(userid),
            username: String::from(username),
            content: String::from(content),
            body: vec,
            published_at: String::from(datatime),
        }
    )
}

fn recursive(elm: &NodeRef) -> Vec<NodeType> {

    let mut vec: Vec<NodeType> = Vec::new();

    for (index, child) in elm.children().enumerate() {
        // println!("[{}] => {:?}", index, child);
        let node_data = child.data().clone();

        match node_data {
            NodeData::Element(element_data) => {
                // println!("[{}] => [ELEMENT] => {:?}", index, element_data);
                // println!("[{}] => [ELEMENT] => {:?}", index, child);

                if element_data.name.local.trim().eq("blockquote") {

                    // println!("[{}] => [ELEMENT] => {:?}", index, child.children());
                    let subvec = recursive(&child);
                    let node = NodeType::BlockQuote(BlockQuoteNode { data: subvec });
                    vec.push(node);
                } else if element_data.name.local.trim().eq("br") {
                    let node = NodeType::Br(BrNode {});
                    vec.push(node);
                } else if element_data.name.local.trim().eq("img") {

                    let attrs = (&element_data.attributes).borrow();
                    let url = attrs.get("src").unwrap_or("");
                    let alt = attrs.get("alt").unwrap_or("");
                    let node = NodeType::Image(ImageNode { data: url.to_string(), alt: alt.to_string() });
                    vec.push(node);

                } else {
                    // println!("[{}] => [ELEMENT] => {:?}", index, child);
                    let mut subvec = recursive(&child);
                    vec.append(&mut subvec);
                }
            }
            NodeData::Text(rc) => {
                // println!("[{}] => [TEXT] => {:?}", index, rc);
                let d = rc.clone();
                let b = d.borrow();

                let s = b.trim().to_string();

                if s == "\n" {
                    // s = "\\n".to_string()
                    continue;
                }

                let node = NodeType::Text(TextNode { data: s });
                vec.push(node);
            }
            _ => {}
        }
    }
    vec
}
<|endoftext|>