<commit_before>use crate::action_client::State;
use crate::static_messages::MUTEX_LOCK_FAIL;
use crate::{
    Action, ActionClient, ClientGoalHandle, FeedbackBody, GoalBody, GoalState, ResultBody,
};
use rosrust::error::Result;
use rosrust::{Duration, Time};
use std::sync::{Arc, Mutex};

#[derive(Copy, Clone, Debug)]
pub enum SimpleGoalState {
    Pending,
    Active,
    Done,
}

#[allow(dead_code)]
pub struct SimpleActionClient<T: Action> {
    action_client: ActionClient<T>,
    goal_handle: Option<ClientGoalHandle<T>>,
    callback_handle: Option<Arc<Mutex<CallbackStatus<T>>>>,
}

impl<T: Action> SimpleActionClient<T> {
    pub fn new(namespace: &str) -> Result<Self> {
        Ok(Self {
            action_client: ActionClient::new(namespace)?,
            goal_handle: None,
            callback_handle: None,
        })
    }

    pub fn wait_for_server(&self, timeout: Option<Duration>) -> bool {
        if let Some(timeout) = timeout {
            self.action_client.wait_for_server(timeout)
        } else {
            self.action_client.wait_for_server_forever();
            true
        }
    }

    pub fn build_goal_sender<'a>(
        &'a mut self,
        goal: GoalBody<T>,
    ) -> SendGoalBuilder<
        'a,
        T,
        impl Fn(GoalState, Option<ResultBody<T>>) + Send + 'static,
        impl Fn() + Send + 'static,
        impl Fn(FeedbackBody<T>) + Send + 'static,
    > {
        SendGoalBuilder::new(self, goal, |_, _| {}, || {}, |_| {})
    }

    pub fn send_goal<Fdone, Factive, Ffeedback>(
        &mut self,
        goal: GoalBody<T>,
        on_done: Option<Fdone>,
        on_active: Option<Factive>,
        on_feedback: Option<Ffeedback>,
    ) where
        Fdone: Fn(GoalState, Option<ResultBody<T>>) + Send + 'static,
        Factive: Fn() + Send + 'static,
        Ffeedback: Fn(FeedbackBody<T>) + Send + 'static,
    {
        self.stop_tracking_goal();

        let callback_handle = Arc::new(Mutex::new(CallbackStatus {
            expired: false,
            namespace: self.action_client.namespace().into(),
            state: SimpleGoalState::Pending,
            on_done: on_done.map(|f| Box::new(f) as Box<_>),
            on_active: on_active.map(|f| Box::new(f) as Box<_>),
            on_feedback: on_feedback.map(|f| Box::new(f) as Box<_>),
        }));

        let handle_transition = {
            let callback_handle = Arc::clone(&callback_handle);
            move |gh| {
                callback_handle
                    .lock()
                    .expect(MUTEX_LOCK_FAIL)
                    .handle_transition(gh)
            }
        };

        let handle_feedback = {
            let callback_handle = Arc::clone(&callback_handle);
            move |gh, fb| {
                callback_handle
                    .lock()
                    .expect(MUTEX_LOCK_FAIL)
                    .handle_feedback(gh, fb)
            }
        };

        self.callback_handle = Some(callback_handle);

        let goal_handle =
            self.action_client
                .send_goal(goal, Some(handle_transition), Some(handle_feedback));

        self.goal_handle = Some(goal_handle);
    }

    pub fn send_goal_and_wait(
        &mut self,
        goal: GoalBody<T>,
        execute_timeout: Option<Duration>,
        preempt_timeout: Option<Duration>,
    ) -> GoalState {
        self.build_goal_sender(goal).send();

        if self.wait_for_result(execute_timeout) {
            return self.state();
        }

        rosrust::ros_debug!("Canceling goal");
        self.cancel_goal();
        let timeout_time = preempt_timeout
            .as_ref()
            .map(Duration::seconds)
            .unwrap_or_default();
        let finished = self.wait_for_result(preempt_timeout);
        rosrust::ros_debug!(
            "Preempt {} within specified preempt_timeout [{}]",
            if finished {
                "finished"
            } else {
                "didn't finish"
            },
            timeout_time
        );

        self.state()
    }

    pub fn wait_for_result(&self, _timeout: Option<Duration>) -> bool {
        unimplemented!()
    }

    pub fn result(&self) -> Option<ResultBody<T>> {
        let result = self.goal_handle.as_ref().and_then(|gh| gh.result());
        if result.is_none() {
            rosrust::ros_err!("Called result when no goal is running");
        }
        result
    }

    pub fn state(&self) -> GoalState {
        let inner_goal_state = self
            .goal_handle
            .as_ref()
            .map(|gh| gh.goal_state())
            .unwrap_or(GoalState::Lost);
        match inner_goal_state {
            GoalState::Recalling => GoalState::Pending,
            GoalState::Preempting => GoalState::Active,
            other => other,
        }
    }

    pub fn goal_status_text(&self) -> Option<String> {
        let status_text = self.goal_handle.as_ref().map(|gh| gh.goal_status_text());
        if status_text.is_none() {
            rosrust::ros_err!("Called goal_status_text when no goal is running");
        }
        status_text
    }

    pub fn cancel_all_goals(&self) -> Result<()> {
        self.action_client.cancel_all_goals()
    }

    pub fn cancel_goals_at_and_before_time(&self, time: Time) -> Result<()> {
        self.action_client.cancel_goals_at_and_before_time(time)
    }

    pub fn cancel_goal(&self) {
        if let Some(ref gh) = self.goal_handle {
            gh.cancel();
        }
    }

    pub fn stop_tracking_goal(&mut self) {
        if let Some(ref cb_handle) = self.callback_handle {
            cb_handle.lock().expect(MUTEX_LOCK_FAIL).expired = true;
        }
        self.callback_handle = None;
        self.goal_handle = None;
    }
}

#[allow(dead_code)]
struct CallbackStatus<T: Action> {
    expired: bool,
    namespace: String,
    state: SimpleGoalState,
    on_done: Option<Box<dyn Fn(GoalState, Option<ResultBody<T>>) + Send>>,
    on_active: Option<Box<dyn Fn() + Send>>,
    on_feedback: Option<Box<dyn Fn(FeedbackBody<T>) + Send>>,
}

impl<T: Action> CallbackStatus<T> {
    fn handle_transition(&mut self, gh: ClientGoalHandle<T>) {
        let comm_state = gh.comm_state();

        match (comm_state, self.state) {
            (State::Active, SimpleGoalState::Done)
            | (State::Recalling, SimpleGoalState::Active)
            | (State::Recalling, SimpleGoalState::Done)
            | (State::Preempting, SimpleGoalState::Done) => {
                rosrust::ros_err!("Received comm state {:?} when in simple state {:?} with SimpleActionClient in NS {}", comm_state, self.state, self.namespace);
            }
            (State::Done, SimpleGoalState::Done) => {
                rosrust::ros_err!("SimpleActionClient received {:?} twice", comm_state);
            }
            (State::Active, SimpleGoalState::Pending)
            | (State::Preempting, SimpleGoalState::Pending) => {
                self.state = SimpleGoalState::Active;
                if let Some(ref on_active) = self.on_active {
                    (*on_active)();
                }
            }
            (State::Done, SimpleGoalState::Pending) | (State::Done, SimpleGoalState::Active) => {
                self.state = SimpleGoalState::Done;
                if let Some(ref on_done) = self.on_done {
                    (*on_done)(gh.goal_state(), gh.result());
                }
                // TODO: trigger done notification
            }
            _ => {}
        }
    }

    fn handle_feedback(&mut self, _gh: ClientGoalHandle<T>, feedback: FeedbackBody<T>) {
        if self.expired {
            return;
        }
        if let Some(ref on_feedback) = self.on_feedback {
            (*on_feedback)(feedback);
        }
    }
}

pub struct SendGoalBuilder<'a, T: Action, Fd, Fa, Ff> {
    client: &'a mut SimpleActionClient<T>,
    goal: GoalBody<T>,
    on_done: Option<Fd>,
    on_active: Option<Fa>,
    on_feedback: Option<Ff>,
}

impl<'a, T: Action, Fd, Fa, Ff> SendGoalBuilder<'a, T, Fd, Fa, Ff>
where
    Fd: Fn(GoalState, Option<ResultBody<T>>) + Send + 'static,
    Fa: Fn() + Send + 'static,
    Ff: Fn(FeedbackBody<T>) + Send + 'static,
{
    fn new(client: &'a mut SimpleActionClient<T>, goal: GoalBody<T>, _: Fd, _: Fa, _: Ff) -> Self {
        Self {
            client,
            goal,
            on_done: None,
            on_active: None,
            on_feedback: None,
        }
    }

    #[inline]
    pub fn on_done<Fnew>(self, callback: Fnew) -> SendGoalBuilder<'a, T, Fnew, Fa, Ff>
    where
        Fnew: Fn(GoalState, Option<ResultBody<T>>) + Send + 'static,
    {
        SendGoalBuilder {
            client: self.client,
            goal: self.goal,
            on_done: Some(callback),
            on_active: self.on_active,
            on_feedback: self.on_feedback,
        }
    }

    #[inline]
    pub fn on_active<Fnew>(self, callback: Fnew) -> SendGoalBuilder<'a, T, Fd, Fnew, Ff>
    where
        Fnew: Fn() + Send + 'static,
    {
        SendGoalBuilder {
            client: self.client,
            goal: self.goal,
            on_done: self.on_done,
            on_active: Some(callback),
            on_feedback: self.on_feedback,
        }
    }

    #[inline]
    pub fn on_feedback<Fnew>(self, callback: Fnew) -> SendGoalBuilder<'a, T, Fd, Fa, Fnew>
    where
        Fnew: Fn(FeedbackBody<T>) + Send + 'static,
    {
        SendGoalBuilder {
            client: self.client,
            goal: self.goal,
            on_done: self.on_done,
            on_active: self.on_active,
            on_feedback: Some(callback),
        }
    }

    #[inline]
    pub fn send(self) {
        self.client
            .send_goal(self.goal, self.on_done, self.on_active, self.on_feedback)
    }
}
<commit_msg>Implement waiting for results<commit_after>use crate::action_client::State;
use crate::static_messages::MUTEX_LOCK_FAIL;
use crate::{
    Action, ActionClient, ClientGoalHandle, FeedbackBody, GoalBody, GoalState, ResultBody,
};
use rosrust::error::Result;
use rosrust::{Duration, Time};
use std::sync::{Arc, Condvar, Mutex};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SimpleGoalState {
    Pending,
    Active,
    Done,
}

type DoneCondition = (Mutex<()>, Condvar);

#[allow(dead_code)]
pub struct SimpleActionClient<T: Action> {
    action_client: ActionClient<T>,
    goal_handle: Option<ClientGoalHandle<T>>,
    callback_handle: Option<Arc<Mutex<CallbackStatus<T>>>>,
    done_condition: Arc<DoneCondition>,
    simple_state: Arc<Mutex<SimpleGoalState>>,
}

impl<T: Action> SimpleActionClient<T> {
    pub fn new(namespace: &str) -> Result<Self> {
        Ok(Self {
            action_client: ActionClient::new(namespace)?,
            goal_handle: None,
            callback_handle: None,
            done_condition: Arc::new((Mutex::new(()), Condvar::new())),
            simple_state: Arc::new(Mutex::new(SimpleGoalState::Done)),
        })
    }

    pub fn wait_for_server(&self, timeout: Option<Duration>) -> bool {
        if let Some(timeout) = timeout {
            self.action_client.wait_for_server(timeout)
        } else {
            self.action_client.wait_for_server_forever();
            true
        }
    }

    pub fn build_goal_sender<'a>(
        &'a mut self,
        goal: GoalBody<T>,
    ) -> SendGoalBuilder<
        'a,
        T,
        impl Fn(GoalState, Option<ResultBody<T>>) + Send + 'static,
        impl Fn() + Send + 'static,
        impl Fn(FeedbackBody<T>) + Send + 'static,
    > {
        SendGoalBuilder::new(self, goal, |_, _| {}, || {}, |_| {})
    }

    pub fn send_goal<Fdone, Factive, Ffeedback>(
        &mut self,
        goal: GoalBody<T>,
        on_done: Option<Fdone>,
        on_active: Option<Factive>,
        on_feedback: Option<Ffeedback>,
    ) where
        Fdone: Fn(GoalState, Option<ResultBody<T>>) + Send + 'static,
        Factive: Fn() + Send + 'static,
        Ffeedback: Fn(FeedbackBody<T>) + Send + 'static,
    {
        self.stop_tracking_goal();

        self.simple_state = Arc::new(Mutex::new(SimpleGoalState::Pending));

        let callback_handle = Arc::new(Mutex::new(CallbackStatus {
            expired: false,
            namespace: self.action_client.namespace().into(),
            state: Arc::clone(&self.simple_state),
            on_done: on_done.map(|f| Box::new(f) as Box<_>),
            on_active: on_active.map(|f| Box::new(f) as Box<_>),
            on_feedback: on_feedback.map(|f| Box::new(f) as Box<_>),
            done_condition: Arc::clone(&self.done_condition),
        }));

        let handle_transition = {
            let callback_handle = Arc::clone(&callback_handle);
            move |gh| {
                callback_handle
                    .lock()
                    .expect(MUTEX_LOCK_FAIL)
                    .handle_transition(gh)
            }
        };

        let handle_feedback = {
            let callback_handle = Arc::clone(&callback_handle);
            move |gh, fb| {
                callback_handle
                    .lock()
                    .expect(MUTEX_LOCK_FAIL)
                    .handle_feedback(gh, fb)
            }
        };

        self.callback_handle = Some(callback_handle);

        let goal_handle =
            self.action_client
                .send_goal(goal, Some(handle_transition), Some(handle_feedback));

        self.goal_handle = Some(goal_handle);
    }

    pub fn send_goal_and_wait(
        &mut self,
        goal: GoalBody<T>,
        execute_timeout: Option<Duration>,
        preempt_timeout: Option<Duration>,
    ) -> GoalState {
        self.build_goal_sender(goal).send();

        if self.wait_for_result(execute_timeout) {
            return self.state();
        }

        rosrust::ros_debug!("Canceling goal");
        self.cancel_goal();
        let timeout_time = preempt_timeout
            .as_ref()
            .map(Duration::seconds)
            .unwrap_or_default();
        let finished = self.wait_for_result(preempt_timeout);
        rosrust::ros_debug!(
            "Preempt {} within specified preempt_timeout [{}]",
            if finished {
                "finished"
            } else {
                "didn't finish"
            },
            timeout_time
        );

        self.state()
    }

    pub fn wait_for_result(&self, timeout: Option<Duration>) -> bool {
        if self.goal_handle.is_none() {
            rosrust::ros_err!("Called wait_for_result when no goal exists");
            return false;
        }
        let timeout_time = timeout.map(|timeout| (rosrust::now() + timeout).seconds());
        let loop_period_seconds = 0.1;

        let (ref lock, ref condition) = *self.done_condition;
        let mut condition_lock_guard = lock.lock().expect(MUTEX_LOCK_FAIL);
        while rosrust::is_ok() {
            let time_left = match timeout_time {
                None => loop_period_seconds,
                Some(tt) => (tt - rosrust::now().seconds()).min(loop_period_seconds),
            };

            if self.simple_state() == SimpleGoalState::Done {
                return true;
            }

            if time_left < 0.0 {
                return false;
            }

            condition_lock_guard = condition
                .wait_timeout(
                    condition_lock_guard,
                    std::time::Duration::from_millis((time_left * 1000.0) as u64),
                )
                .expect(MUTEX_LOCK_FAIL)
                .0;
        }

        false
    }

    fn simple_state(&self) -> SimpleGoalState {
        *self.simple_state.lock().expect(MUTEX_LOCK_FAIL)
    }

    pub fn result(&self) -> Option<ResultBody<T>> {
        let result = self.goal_handle.as_ref().and_then(|gh| gh.result());
        if result.is_none() {
            rosrust::ros_err!("Called result when no goal is running");
        }
        result
    }

    pub fn state(&self) -> GoalState {
        let inner_goal_state = self
            .goal_handle
            .as_ref()
            .map(|gh| gh.goal_state())
            .unwrap_or(GoalState::Lost);
        match inner_goal_state {
            GoalState::Recalling => GoalState::Pending,
            GoalState::Preempting => GoalState::Active,
            other => other,
        }
    }

    pub fn goal_status_text(&self) -> Option<String> {
        let status_text = self.goal_handle.as_ref().map(|gh| gh.goal_status_text());
        if status_text.is_none() {
            rosrust::ros_err!("Called goal_status_text when no goal is running");
        }
        status_text
    }

    pub fn cancel_all_goals(&self) -> Result<()> {
        self.action_client.cancel_all_goals()
    }

    pub fn cancel_goals_at_and_before_time(&self, time: Time) -> Result<()> {
        self.action_client.cancel_goals_at_and_before_time(time)
    }

    pub fn cancel_goal(&self) {
        if let Some(ref gh) = self.goal_handle {
            gh.cancel();
        }
    }

    pub fn stop_tracking_goal(&mut self) {
        if let Some(ref cb_handle) = self.callback_handle {
            cb_handle.lock().expect(MUTEX_LOCK_FAIL).expired = true;
        }
        self.callback_handle = None;
        self.goal_handle = None;
    }
}

#[allow(dead_code)]
struct CallbackStatus<T: Action> {
    expired: bool,
    namespace: String,
    state: Arc<Mutex<SimpleGoalState>>,
    on_done: Option<Box<dyn Fn(GoalState, Option<ResultBody<T>>) + Send>>,
    on_active: Option<Box<dyn Fn() + Send>>,
    on_feedback: Option<Box<dyn Fn(FeedbackBody<T>) + Send>>,
    done_condition: Arc<DoneCondition>,
}

impl<T: Action> CallbackStatus<T> {
    fn handle_transition(&mut self, gh: ClientGoalHandle<T>) {
        let comm_state = gh.comm_state();

        match (comm_state, self.state()) {
            (State::Active, SimpleGoalState::Done)
            | (State::Recalling, SimpleGoalState::Active)
            | (State::Recalling, SimpleGoalState::Done)
            | (State::Preempting, SimpleGoalState::Done) => {
                rosrust::ros_err!("Received comm state {:?} when in simple state {:?} with SimpleActionClient in NS {}", comm_state, self.state, self.namespace);
            }
            (State::Done, SimpleGoalState::Done) => {
                rosrust::ros_err!("SimpleActionClient received {:?} twice", comm_state);
            }
            (State::Active, SimpleGoalState::Pending)
            | (State::Preempting, SimpleGoalState::Pending) => {
                self.set_state(SimpleGoalState::Active);
                if let Some(ref on_active) = self.on_active {
                    (*on_active)();
                }
            }
            (State::Done, SimpleGoalState::Pending) | (State::Done, SimpleGoalState::Active) => {
                self.set_state(SimpleGoalState::Done);
                if let Some(ref on_done) = self.on_done {
                    (*on_done)(gh.goal_state(), gh.result());
                }
                let (ref lock, ref condition) = *self.done_condition;
                let mut _condition_lock_guard = lock.lock().expect(MUTEX_LOCK_FAIL);
                condition.notify_all();
            }
            _ => {}
        }
    }

    fn state(&self) -> SimpleGoalState {
        *self.state.lock().expect(MUTEX_LOCK_FAIL)
    }

    fn set_state(&mut self, value: SimpleGoalState) {
        *self.state.lock().expect(MUTEX_LOCK_FAIL) = value;
    }

    fn handle_feedback(&mut self, _gh: ClientGoalHandle<T>, feedback: FeedbackBody<T>) {
        if self.expired {
            return;
        }
        if let Some(ref on_feedback) = self.on_feedback {
            (*on_feedback)(feedback);
        }
    }
}

pub struct SendGoalBuilder<'a, T: Action, Fd, Fa, Ff> {
    client: &'a mut SimpleActionClient<T>,
    goal: GoalBody<T>,
    on_done: Option<Fd>,
    on_active: Option<Fa>,
    on_feedback: Option<Ff>,
}

impl<'a, T: Action, Fd, Fa, Ff> SendGoalBuilder<'a, T, Fd, Fa, Ff>
where
    Fd: Fn(GoalState, Option<ResultBody<T>>) + Send + 'static,
    Fa: Fn() + Send + 'static,
    Ff: Fn(FeedbackBody<T>) + Send + 'static,
{
    fn new(client: &'a mut SimpleActionClient<T>, goal: GoalBody<T>, _: Fd, _: Fa, _: Ff) -> Self {
        Self {
            client,
            goal,
            on_done: None,
            on_active: None,
            on_feedback: None,
        }
    }

    #[inline]
    pub fn on_done<Fnew>(self, callback: Fnew) -> SendGoalBuilder<'a, T, Fnew, Fa, Ff>
    where
        Fnew: Fn(GoalState, Option<ResultBody<T>>) + Send + 'static,
    {
        SendGoalBuilder {
            client: self.client,
            goal: self.goal,
            on_done: Some(callback),
            on_active: self.on_active,
            on_feedback: self.on_feedback,
        }
    }

    #[inline]
    pub fn on_active<Fnew>(self, callback: Fnew) -> SendGoalBuilder<'a, T, Fd, Fnew, Ff>
    where
        Fnew: Fn() + Send + 'static,
    {
        SendGoalBuilder {
            client: self.client,
            goal: self.goal,
            on_done: self.on_done,
            on_active: Some(callback),
            on_feedback: self.on_feedback,
        }
    }

    #[inline]
    pub fn on_feedback<Fnew>(self, callback: Fnew) -> SendGoalBuilder<'a, T, Fd, Fa, Fnew>
    where
        Fnew: Fn(FeedbackBody<T>) + Send + 'static,
    {
        SendGoalBuilder {
            client: self.client,
            goal: self.goal,
            on_done: self.on_done,
            on_active: self.on_active,
            on_feedback: Some(callback),
        }
    }

    #[inline]
    pub fn send(self) {
        self.client
            .send_goal(self.goal, self.on_done, self.on_active, self.on_feedback)
    }
}
<|endoftext|>


<commit_before>use std::env;
use std::path::Path;

use lazy_static::lazy_static;
use raw_cpuid::CpuId;

lazy_static! {
    static ref CPUID: CpuId = CpuId::new();
}

fn has_aesni() -> bool {
    cfg!(feature = "native")
        && CPUID
            .get_feature_info()
            .map_or(false, |features| features.has_aesni())
}

fn has_sse41() -> bool {
    cfg!(feature = "native")
        && CPUID
            .get_feature_info()
            .map_or(false, |features| features.has_sse41())
}

fn has_sse42() -> bool {
    cfg!(feature = "native")
        && CPUID
            .get_feature_info()
            .map_or(false, |features| features.has_sse42())
}

fn has_avx() -> bool {
    cfg!(feature = "native")
        && CPUID
            .get_feature_info()
            .map_or(false, |features| features.has_avx())
}

fn has_avx2() -> bool {
    cfg!(feature = "native")
        && CPUID
            .get_extended_feature_info()
            .map_or(false, |features| features.has_avx2())
}

fn support_aesni() -> bool {
    cfg!(any(feature = "aes", target_feature = "aes")) || has_aesni()
}

#[allow(dead_code)]
fn support_sse41() -> bool {
    cfg!(any(feature = "sse41", target_feature = "sse41")) || has_sse41()
}

fn support_sse42() -> bool {
    cfg!(any(feature = "sse42", target_feature = "sse42")) || has_sse42()
}

fn support_avx() -> bool {
    cfg!(any(feature = "avx", target_feature = "avx")) || has_avx()
}

fn support_avx2() -> bool {
    cfg!(any(feature = "avx2", target_feature = "avx2")) || has_avx2()
}

#[cfg(all(not(feature = "gen"), any(target_os = "macos", target_os = "linux")))]
fn generate_binding(_out_file: &Path) {
    cargo_emit::warning!("pregenerated binding file.");
}

#[cfg(any(feature = "gen", not(any(target_os = "macos", target_os = "linux"))))]
fn generate_binding(out_file: &Path) {
    cargo_emit::warning!("generate binding file @ {:?}.", out_file);

    let _ = bindgen::builder()
        .clang_args(&["-x", "c++", "-std=c++11"])
        .clang_args(&[
            "-Dt1ha_EXPORTS",
            "-DXXH_STATIC_LINKING_ONLY",
            "-Isrc/highwayhash",
        ])
        .clang_args(if support_aesni() {
            &[
                "-maes",
                "-DT1HA0_RUNTIME_SELECT=1",
                "-DT1HA0_AESNI_AVAILABLE=1",
            ][..]
        } else {
            &[][..]
        })
        .clang_args(
            vec![
                if cfg!(feature = "native") {
                    Some("-march=native")
                } else {
                    None
                },
                if support_sse41() {
                    Some("-msse4.1")
                } else {
                    None
                },
                if support_sse42() {
                    Some("-msse4.2")
                } else {
                    None
                },
                if support_avx() { Some("-mavx") } else { None },
                if support_avx2() { Some("-mavx2") } else { None },
                if cfg!(feature = "city") {
                    Some("-DCITY_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "farm") {
                    Some("-DFARM_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "highway") {
                    Some("-DHIGHWAY_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "komi") {
                    Some("-DKOMI_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "lookup3") {
                    Some("-DLOOKUP3=1")
                } else {
                    None
                },
                if cfg!(all(
                    feature = "meow",
                    any(target_arch = "x86", target_arch = "x86_64")
                )) {
                    Some("-DMEOW_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "metro") {
                    Some("-DMETRO_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "mum") {
                    Some("-DMUM_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "murmur") {
                    Some("-DMURMUR_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "mx3") {
                    Some("-DMX3_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "nm") {
                    Some("-DNM_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "pengy") {
                    Some("-DPENGY_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "prv") {
                    Some("-DPRV_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "spooky") {
                    Some("-DSPOOKY_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "t1ha") {
                    Some("-DT1_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "wy") {
                    Some("-DWY_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "xx") {
                    Some("-DXX_HASH=1")
                } else {
                    None
                },
            ]
            .into_iter()
            .flatten(),
        )
        .header("src/fasthash.hpp")
        .size_t_is_usize(true)
        .generate_inline_functions(true)
        .disable_name_namespacing()
        .allowlist_function("^CityHash.*")
        .allowlist_function("^farmhash.*")
        .allowlist_function("^HighwayHash.*")
        .allowlist_function("^komi.*")
        .allowlist_function("^lookup3.*")
        .allowlist_function("^metrohash.*")
        .allowlist_function("^mum_hash.*")
        .allowlist_function("^MurmurHash.*")
        .allowlist_function("^mx3hash.*")
        .allowlist_function("^NMHASH.*")
        .allowlist_function("^pengy.*")
        .allowlist_function("^prvhash.*")
        .allowlist_function("^SpookyHasher.*")
        .allowlist_function("^t1ha.*")
        .allowlist_function("^wyhash.*")
        .allowlist_function("^XXH.*")
        .allowlist_function("^Meow.*")
        .blocklist_function("^t1ha_selfcheck__.*")
        .allowlist_var("^Meow.*")
        .allowlist_var("^PRH64S_.*")
        .generate()
        .unwrap()
        .write_to_file(out_file)
        .expect("fail to write bindings");

    cargo_emit::warning!("generate binding file @ {:?}.", out_file);
}

fn build_fasthash() {
    let mut build = cc::Build::new();

    build
        .cpp(true)
        .include("src/highwayhash")
        .flag("-std=c++11")
        .flag_if_supported("-Wno-implicit-fallthrough")
        .flag_if_supported("-Wno-unknown-attributes")
        .flag_if_supported("-Wno-sign-compare")
        .file("src/fasthash.cpp");

    if cfg!(feature = "city") {
        build.flag("-DCITY_HASH=1").file("src/smhasher/City.cpp");
    }

    if cfg!(feature = "farm") {
        build
            .flag("-DFARM_HASH=1")
            .file("src/smhasher/farmhash-c.c");
    }

    if cfg!(feature = "komi") {
        build.flag("-DKOMI_HASH=1");
    }

    if cfg!(feature = "highway") {
        build.flag("-DHIGHWAY_HASH=1");
    }

    if cfg!(feature = "lookup3") {
        build.flag("-DLOOKUP3=1").file("src/smhasher/lookup3.cpp");
    }

    if cfg!(all(
        feature = "meow",
        any(target_arch = "x86", target_arch = "x86_64")
    )) {
        build.flag("-DMEOW_HASH=1");
    }

    if cfg!(feature = "mx3") {
        build.flag("-DMX3_HASH=1");
    }

    if cfg!(feature = "nm") {
        build.flag("-DNM_HASH=1");
    }

    if cfg!(feature = "pengy") {
        build
            .flag("-DPENGY_HASH=1")
            .file("src/pengyhash/pengyhash.c");
    }

    if cfg!(feature = "prv") {
        build.flag("-DPRV_HASH=1");
    }

    if cfg!(feature = "t1ha") {
        build.flag("-DT1_HASH=1");
    }

    if cfg!(feature = "metro") {
        build
            .flag("-DMETRO_HASH=1")
            .file("src/smhasher/metrohash/metrohash64.cpp")
            .file("src/smhasher/metrohash/metrohash128.cpp");

        if support_sse42() {
            build
                .file("src/smhasher/metrohash/metrohash64crc.cpp")
                .file("src/smhasher/metrohash/metrohash128crc.cpp");
        }
    }

    if cfg!(feature = "mum") {
        build.flag("-DMUM_HASH=1").file("src/smhasher/mum.cc");
    }

    if cfg!(feature = "murmur") {
        build
            .flag("-DMURMUR_HASH=1")
            .file("src/smhasher/MurmurHash1.cpp")
            .file("src/smhasher/MurmurHash2.cpp")
            .file("src/smhasher/MurmurHash3.cpp");
    }

    if cfg!(feature = "spooky") {
        build
            .flag("-DSPOOKY_HASH=1")
            .file("src/smhasher/Spooky.cpp");
    }

    if cfg!(feature = "wy") {
        build.flag("-DWY_HASH=1");
    }

    if cfg!(feature = "xx") {
        build.flag("-DXX_HASH=1").file("src/xxHash/xxhash.c");
    }

    if cfg!(feature = "native") {
        build.flag("-march=native");
    } else {
        if cfg!(target_feature = "aes") {
            build.flag("-maes");
        }
        if cfg!(target_feature = "sse41") {
            build.flag("-msse41");
        }
        if cfg!(target_feature = "sse42") {
            build.flag("-msse4.2");
        }
        if cfg!(target_feature = "avx") {
            build.flag("-mavx");
        }
        if cfg!(target_feature = "avx2") {
            build.flag("-mavx2");
        }
    }

    build.static_flag(true).compile("fasthash");
}

fn build_t1() {
    let mut build = cc::Build::new();

    build
        .file("src/t1ha/src/t1ha0.c")
        .file("src/t1ha/src/t1ha1.c")
        .file("src/t1ha/src/t1ha2.c");

    // indirect functions are not supported on all targets (e.g. x86_64-unknown-linux-musl)
    #[cfg(target_env = "musl")]
    build.define("T1HA_USE_INDIRECT_FUNCTIONS", Some("0"));

    if support_aesni() {
        build
            .define("T1HA0_RUNTIME_SELECT", Some("1"))
            .define("T1HA0_AESNI_AVAILABLE", Some("1"))
            .flag("-maes")
            .file("src/t1ha/src/t1ha0_ia32aes_noavx.c")
            .file("src/t1ha/src/t1ha0_ia32aes_avx.c")
            .file("src/t1ha/src/t1ha0_ia32aes_avx2.c");

        if support_avx() {
            build.flag("-mavx");
        }

        if support_avx2() {
            build.flag("-mavx2");
        }
    }

    build.static_flag(true).compile("t1ha");
}

fn build_highway() {
    let mut build = cc::Build::new();

    build
        .cpp(true)
        .flag("-std=c++11")
        .flag_if_supported("-Wno-sign-compare")
        .include("src/highwayhash")
        .file("src/highwayhash/highwayhash/arch_specific.cc")
        .file("src/highwayhash/highwayhash/instruction_sets.cc")
        .file("src/highwayhash/highwayhash/os_specific.cc")
        .file("src/highwayhash/highwayhash/hh_portable.cc")
        .file("src/highwayhash/highwayhash/c_bindings.cc");

    if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
        build
            .flag("-msse4.1")
            .flag("-mavx2")
            .file("src/highwayhash/highwayhash/hh_sse41.cc")
            .file("src/highwayhash/highwayhash/hh_avx2.cc");
    } else if cfg!(target_arch = "aarch64") {
        build.file("src/highwayhash/highwayhash/hh_neon.cc");
    } else if cfg!(target_arch = "powerpc64") {
        build.file("src/highwayhash/highwayhash/hh_vsx.cc");
    }

    build.static_flag(true).compile("highwayhash");
}

fn main() {
    if has_aesni() {
        cargo_emit::rustc_cfg!(r#"feature="aes""#);
    }
    if has_sse41() {
        cargo_emit::rustc_cfg!(r#"feature="sse41""#);
    }
    if has_sse42() {
        cargo_emit::rustc_cfg!(r#"feature="sse42""#);
    }
    if has_avx() {
        cargo_emit::rustc_cfg!(r#"feature="avx""#);
    }
    if has_avx2() {
        cargo_emit::rustc_cfg!(r#"feature="avx2""#);
    }

    build_fasthash();
    if cfg!(feature = "t1ha") {
        build_t1();
    }
    if cfg!(feature = "highway") {
        build_highway();
    }

    let out_dir = env::var("OUT_DIR").unwrap();
    let out_file = Path::new(&out_dir).join("fasthash.rs");

    cargo_emit::rerun_if_changed!("src/fasthash.hpp");
    cargo_emit::rerun_if_changed!("src/fasthash.cpp");

    generate_binding(&out_file);
}
<commit_msg>add -mvsx for powerpc<commit_after>use std::env;
use std::path::Path;

use lazy_static::lazy_static;
use raw_cpuid::CpuId;

lazy_static! {
    static ref CPUID: CpuId = CpuId::new();
}

fn has_aesni() -> bool {
    cfg!(feature = "native")
        && CPUID
            .get_feature_info()
            .map_or(false, |features| features.has_aesni())
}

fn has_sse41() -> bool {
    cfg!(feature = "native")
        && CPUID
            .get_feature_info()
            .map_or(false, |features| features.has_sse41())
}

fn has_sse42() -> bool {
    cfg!(feature = "native")
        && CPUID
            .get_feature_info()
            .map_or(false, |features| features.has_sse42())
}

fn has_avx() -> bool {
    cfg!(feature = "native")
        && CPUID
            .get_feature_info()
            .map_or(false, |features| features.has_avx())
}

fn has_avx2() -> bool {
    cfg!(feature = "native")
        && CPUID
            .get_extended_feature_info()
            .map_or(false, |features| features.has_avx2())
}

fn support_aesni() -> bool {
    cfg!(any(feature = "aes", target_feature = "aes")) || has_aesni()
}

#[allow(dead_code)]
fn support_sse41() -> bool {
    cfg!(any(feature = "sse41", target_feature = "sse41")) || has_sse41()
}

fn support_sse42() -> bool {
    cfg!(any(feature = "sse42", target_feature = "sse42")) || has_sse42()
}

fn support_avx() -> bool {
    cfg!(any(feature = "avx", target_feature = "avx")) || has_avx()
}

fn support_avx2() -> bool {
    cfg!(any(feature = "avx2", target_feature = "avx2")) || has_avx2()
}

#[cfg(all(not(feature = "gen"), any(target_os = "macos", target_os = "linux")))]
fn generate_binding(_out_file: &Path) {
    cargo_emit::warning!("pregenerated binding file.");
}

#[cfg(any(feature = "gen", not(any(target_os = "macos", target_os = "linux"))))]
fn generate_binding(out_file: &Path) {
    cargo_emit::warning!("generate binding file @ {:?}.", out_file);

    let _ = bindgen::builder()
        .clang_args(&["-x", "c++", "-std=c++11"])
        .clang_args(&[
            "-Dt1ha_EXPORTS",
            "-DXXH_STATIC_LINKING_ONLY",
            "-Isrc/highwayhash",
        ])
        .clang_args(if support_aesni() {
            &[
                "-maes",
                "-DT1HA0_RUNTIME_SELECT=1",
                "-DT1HA0_AESNI_AVAILABLE=1",
            ][..]
        } else {
            &[][..]
        })
        .clang_args(
            vec![
                if cfg!(feature = "native") {
                    Some("-march=native")
                } else {
                    None
                },
                if support_sse41() {
                    Some("-msse4.1")
                } else {
                    None
                },
                if support_sse42() {
                    Some("-msse4.2")
                } else {
                    None
                },
                if support_avx() { Some("-mavx") } else { None },
                if support_avx2() { Some("-mavx2") } else { None },
                if cfg!(feature = "city") {
                    Some("-DCITY_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "farm") {
                    Some("-DFARM_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "highway") {
                    Some("-DHIGHWAY_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "komi") {
                    Some("-DKOMI_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "lookup3") {
                    Some("-DLOOKUP3=1")
                } else {
                    None
                },
                if cfg!(all(
                    feature = "meow",
                    any(target_arch = "x86", target_arch = "x86_64")
                )) {
                    Some("-DMEOW_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "metro") {
                    Some("-DMETRO_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "mum") {
                    Some("-DMUM_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "murmur") {
                    Some("-DMURMUR_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "mx3") {
                    Some("-DMX3_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "nm") {
                    Some("-DNM_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "pengy") {
                    Some("-DPENGY_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "prv") {
                    Some("-DPRV_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "spooky") {
                    Some("-DSPOOKY_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "t1ha") {
                    Some("-DT1_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "wy") {
                    Some("-DWY_HASH=1")
                } else {
                    None
                },
                if cfg!(feature = "xx") {
                    Some("-DXX_HASH=1")
                } else {
                    None
                },
            ]
            .into_iter()
            .flatten(),
        )
        .header("src/fasthash.hpp")
        .size_t_is_usize(true)
        .generate_inline_functions(true)
        .disable_name_namespacing()
        .allowlist_function("^CityHash.*")
        .allowlist_function("^farmhash.*")
        .allowlist_function("^HighwayHash.*")
        .allowlist_function("^komi.*")
        .allowlist_function("^lookup3.*")
        .allowlist_function("^metrohash.*")
        .allowlist_function("^mum_hash.*")
        .allowlist_function("^MurmurHash.*")
        .allowlist_function("^mx3hash.*")
        .allowlist_function("^NMHASH.*")
        .allowlist_function("^pengy.*")
        .allowlist_function("^prvhash.*")
        .allowlist_function("^SpookyHasher.*")
        .allowlist_function("^t1ha.*")
        .allowlist_function("^wyhash.*")
        .allowlist_function("^XXH.*")
        .allowlist_function("^Meow.*")
        .blocklist_function("^t1ha_selfcheck__.*")
        .allowlist_var("^Meow.*")
        .allowlist_var("^PRH64S_.*")
        .generate()
        .unwrap()
        .write_to_file(out_file)
        .expect("fail to write bindings");

    cargo_emit::warning!("generate binding file @ {:?}.", out_file);
}

fn build_fasthash() {
    let mut build = cc::Build::new();

    build
        .cpp(true)
        .include("src/highwayhash")
        .flag("-std=c++11")
        .flag_if_supported("-Wno-implicit-fallthrough")
        .flag_if_supported("-Wno-unknown-attributes")
        .flag_if_supported("-Wno-sign-compare")
        .file("src/fasthash.cpp");

    if cfg!(feature = "city") {
        build.flag("-DCITY_HASH=1").file("src/smhasher/City.cpp");
    }

    if cfg!(feature = "farm") {
        build
            .flag("-DFARM_HASH=1")
            .file("src/smhasher/farmhash-c.c");
    }

    if cfg!(feature = "komi") {
        build.flag("-DKOMI_HASH=1");
    }

    if cfg!(feature = "highway") {
        build.flag("-DHIGHWAY_HASH=1");
    }

    if cfg!(feature = "lookup3") {
        build.flag("-DLOOKUP3=1").file("src/smhasher/lookup3.cpp");
    }

    if cfg!(all(
        feature = "meow",
        any(target_arch = "x86", target_arch = "x86_64")
    )) {
        build.flag("-DMEOW_HASH=1");
    }

    if cfg!(feature = "mx3") {
        build.flag("-DMX3_HASH=1");
    }

    if cfg!(feature = "nm") {
        build.flag("-DNM_HASH=1");
    }

    if cfg!(feature = "pengy") {
        build
            .flag("-DPENGY_HASH=1")
            .file("src/pengyhash/pengyhash.c");
    }

    if cfg!(feature = "prv") {
        build.flag("-DPRV_HASH=1");
    }

    if cfg!(feature = "t1ha") {
        build.flag("-DT1_HASH=1");
    }

    if cfg!(feature = "metro") {
        build
            .flag("-DMETRO_HASH=1")
            .file("src/smhasher/metrohash/metrohash64.cpp")
            .file("src/smhasher/metrohash/metrohash128.cpp");

        if support_sse42() {
            build
                .file("src/smhasher/metrohash/metrohash64crc.cpp")
                .file("src/smhasher/metrohash/metrohash128crc.cpp");
        }
    }

    if cfg!(feature = "mum") {
        build.flag("-DMUM_HASH=1").file("src/smhasher/mum.cc");
    }

    if cfg!(feature = "murmur") {
        build
            .flag("-DMURMUR_HASH=1")
            .file("src/smhasher/MurmurHash1.cpp")
            .file("src/smhasher/MurmurHash2.cpp")
            .file("src/smhasher/MurmurHash3.cpp");
    }

    if cfg!(feature = "spooky") {
        build
            .flag("-DSPOOKY_HASH=1")
            .file("src/smhasher/Spooky.cpp");
    }

    if cfg!(feature = "wy") {
        build.flag("-DWY_HASH=1");
    }

    if cfg!(feature = "xx") {
        build.flag("-DXX_HASH=1").file("src/xxHash/xxhash.c");
    }

    if cfg!(feature = "native") {
        build.flag("-march=native");
    } else {
        if cfg!(target_feature = "aes") {
            build.flag("-maes");
        }
        if cfg!(target_feature = "sse41") {
            build.flag("-msse41");
        }
        if cfg!(target_feature = "sse42") {
            build.flag("-msse4.2");
        }
        if cfg!(target_feature = "avx") {
            build.flag("-mavx");
        }
        if cfg!(target_feature = "avx2") {
            build.flag("-mavx2");
        }
    }

    build.static_flag(true).compile("fasthash");
}

fn build_t1() {
    let mut build = cc::Build::new();

    build
        .file("src/t1ha/src/t1ha0.c")
        .file("src/t1ha/src/t1ha1.c")
        .file("src/t1ha/src/t1ha2.c");

    // indirect functions are not supported on all targets (e.g. x86_64-unknown-linux-musl)
    #[cfg(target_env = "musl")]
    build.define("T1HA_USE_INDIRECT_FUNCTIONS", Some("0"));

    if support_aesni() {
        build
            .define("T1HA0_RUNTIME_SELECT", Some("1"))
            .define("T1HA0_AESNI_AVAILABLE", Some("1"))
            .flag("-maes")
            .file("src/t1ha/src/t1ha0_ia32aes_noavx.c")
            .file("src/t1ha/src/t1ha0_ia32aes_avx.c")
            .file("src/t1ha/src/t1ha0_ia32aes_avx2.c");

        if support_avx() {
            build.flag("-mavx");
        }

        if support_avx2() {
            build.flag("-mavx2");
        }
    }

    build.static_flag(true).compile("t1ha");
}

fn build_highway() {
    let mut build = cc::Build::new();

    build
        .cpp(true)
        .flag("-std=c++11")
        .flag_if_supported("-Wno-sign-compare")
        .include("src/highwayhash")
        .file("src/highwayhash/highwayhash/arch_specific.cc")
        .file("src/highwayhash/highwayhash/instruction_sets.cc")
        .file("src/highwayhash/highwayhash/os_specific.cc")
        .file("src/highwayhash/highwayhash/hh_portable.cc")
        .file("src/highwayhash/highwayhash/c_bindings.cc");

    if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
        build
            .flag("-msse4.1")
            .flag("-mavx2")
            .file("src/highwayhash/highwayhash/hh_sse41.cc")
            .file("src/highwayhash/highwayhash/hh_avx2.cc");
    } else if cfg!(target_arch = "aarch64") {
        build.file("src/highwayhash/highwayhash/hh_neon.cc");
    } else if cfg!(target_arch = "powerpc64") {
        build
            .flag("-mvsx")
            .file("src/highwayhash/highwayhash/hh_vsx.cc");
    }

    build.static_flag(true).compile("highwayhash");
}

fn main() {
    if has_aesni() {
        cargo_emit::rustc_cfg!(r#"feature="aes""#);
    }
    if has_sse41() {
        cargo_emit::rustc_cfg!(r#"feature="sse41""#);
    }
    if has_sse42() {
        cargo_emit::rustc_cfg!(r#"feature="sse42""#);
    }
    if has_avx() {
        cargo_emit::rustc_cfg!(r#"feature="avx""#);
    }
    if has_avx2() {
        cargo_emit::rustc_cfg!(r#"feature="avx2""#);
    }

    build_fasthash();
    if cfg!(feature = "t1ha") {
        build_t1();
    }
    if cfg!(feature = "highway") {
        build_highway();
    }

    let out_dir = env::var("OUT_DIR").unwrap();
    let out_file = Path::new(&out_dir).join("fasthash.rs");

    cargo_emit::rerun_if_changed!("src/fasthash.hpp");
    cargo_emit::rerun_if_changed!("src/fasthash.cpp");

    generate_binding(&out_file);
}
<|endoftext|>


<commit_before>use unicode_width::{UnicodeWidthStr,UnicodeWidthChar};
use super::Renderer;
use std::mem;
use std::fmt::Debug;

/// A wrapper around a String with extra metadata.
#[derive(Debug)]
struct TaggedString<T:Debug> {
    s: String,
    tag: T,
}

/// A type to build up wrapped text, allowing extra metadata for
/// spans.
#[derive(Debug)]
struct WrappedBlock<T:Clone+Eq+Debug> {
    width: usize,
    text: Vec<Vec<TaggedString<T>>>,
    textlen: usize,
    line: Vec<TaggedString<T>>,
    linelen: usize,
    spacetag: Option<T>,         // Tag for the whitespace before the current word
    word: Vec<TaggedString<T>>,  // The current word (with no whitespace).
    wordlen: usize,
}

impl<T:Clone+Eq+Debug> WrappedBlock<T> {
    pub fn new(width: usize) -> WrappedBlock<T> {
        WrappedBlock {
            width: width,
            text: Vec::new(),
            textlen: 0,
            line: Vec::new(),
            linelen: 0,
            spacetag: None,
            word: Vec::new(),
            wordlen: 0,
        }
    }

    fn flush_word(&mut self) {
        /* Finish the word. */
        if self.word.len() > 0 {
            let space_in_line = self.width - self.linelen;
            let space_needed = self.wordlen +
                        if self.linelen > 0 { 1 } else { 0 }; // space
            if space_needed <= space_in_line {
                if self.linelen > 0 {
                    self.line.push(TaggedString{s: " ".into(), tag: self.spacetag.take().unwrap()});
                    self.linelen += 1;
                }
                self.line.extend(self.word.drain(..));
                self.linelen += self.wordlen;
                self.wordlen = 0;
            } else {
                /* Start a new line */
                self.flush_line();
                if self.wordlen <= self.width {
                    let mut new_word = Vec::new();
                    mem::swap(&mut new_word, &mut self.word);
                    mem::swap(&mut self.line, &mut new_word);
                    self.linelen = self.wordlen;
                    self.wordlen = 0;
                } else {
                    /* We need to split the word. */
                    let mut wordbits = self.word.drain(..);
                    /* Note: there's always at least one piece */
                    let mut opt_piece = wordbits.next();
                    let mut lineleft = self.width;
                    while let Some(piece) = opt_piece.take() {
                        let w = UnicodeWidthStr::width(piece.s.as_str());
                        if w <= lineleft {
                            self.line.push(piece);
                            lineleft -= w;
                            self.linelen += w;
                            opt_piece = wordbits.next();
                        } else {
                            /* Split into two */
                            let mut split_idx = 0;
                            for (idx,c) in piece.s.char_indices() {
                                let c_w = UnicodeWidthChar::width(c).unwrap();
                                if c_w <= lineleft {
                                    lineleft -= c_w;
                                } else {
                                    split_idx = idx;
                                    break;
                                }
                            }
                            self.line.push(TaggedString{
                                s: piece.s[..split_idx].into(),
                                tag: piece.tag.clone(),
                            });
                            {
                                let mut tmp_line = Vec::new();
                                mem::swap(&mut tmp_line, &mut self.line);
                                self.text.push(tmp_line);
                            }
                            lineleft = self.width;
                            self.linelen = 0;
                            opt_piece = Some(TaggedString{
                                s: piece.s[split_idx..].into(),
                                tag: piece.tag,
                            });
                        }
                    }
                }
            }
        }
    }

    fn flush_line(&mut self) {
        if self.line.len() > 0 {
            let mut tmp_line = Vec::new();
            mem::swap(&mut tmp_line, &mut self.line);
            self.text.push(tmp_line);
            self.linelen = 0;
        }
    }

    fn flush(&mut self) {
        self.flush_word();
        self.flush_line();
    }

    /// Consume self and return a vector of lines.
    pub fn into_untagged_lines(mut self) -> Vec<String> {
        self.flush();

        let mut result = Vec::new();
        for line in self.text.into_iter() {
            let mut line_s = String::new();
            for TaggedString{ s, .. } in line.into_iter() {
                line_s.push_str(&s);
            }
            result.push(line_s);
        }
        result
    }

    pub fn add_text(&mut self, text: &str, tag: T) {
        for c in text.chars() {
            if c.is_whitespace() {
                /* Whitespace is mostly ignored, except to terminate words. */
                self.flush_word();
                self.spacetag = Some(tag.clone());
            } else {
                /* Not whitespace; add to the current word. */
                if self.word.len() > 0 && self.word[self.word.len()-1].tag == tag {
                    /* Just add a character to the last piece */
                    let last_idx = self.word.len() - 1;
                    self.word[last_idx].s.push(c);
                } else {
                    /* Starting a new word; easy. */
                    let mut s = String::new();
                    s.push(c);
                    self.word.push(TaggedString{ s: s, tag: tag.clone()});
                }
                self.wordlen += UnicodeWidthChar::width(c).unwrap();
            }
        }
    }

    pub fn text_len(&self) -> usize {
        self.textlen + self.linelen + self.wordlen
    }
}

/// Allow decorating/styling text.
pub trait TextDecorator {
    /// An annotation which can be added to text, and which will
    /// be attached to spans of text.
    type Annotation;

    /// Return an annotation and rendering prefix for a link.
    fn decorate_link_start(&mut self, url: &str) -> (String, Option<Self::Annotation>);

    /// Return a suffix for after a link.
    fn decorate_link_end(&mut self) -> String;

    /// Finish with a document, and return extra lines (eg footnotes)
    /// to add to the rendered text.
    fn finalise(self) -> Vec<String>;
}

/// A renderer which just outputs plain text.
pub struct TextRenderer<D:TextDecorator+Clone> {
    width: usize,
    lines: Vec<String>,
    /// True at the end of a block, meaning we should add
    /// a blank line if any other text is added.
    at_block_end: bool,
    wrapping: Option<WrappedBlock<()>>,
    decorator: Option<D>,
}

impl<D:TextDecorator+Clone> TextRenderer<D> {
    /// Construct a new empty TextRenderer.
    pub fn new(width: usize, decorator: D) -> TextRenderer<D> {
        html_trace!("new({})", width);
        TextRenderer {
            width: width,
            lines: Vec::new(),
            at_block_end: false,
            wrapping: None,
            decorator: Some(decorator),
        }
    }

    /// Get the current line wrapping context (and create if
    /// needed).
    fn current_text(&mut self) -> &mut WrappedBlock<()> {
        if self.wrapping.is_none() {
            self.wrapping = Some(WrappedBlock::new(self.width));
        }
        self.wrapping.as_mut().unwrap()
    }

    pub fn add_subblock(&mut self, s: &str) {
        html_trace!("add_subblock({}, {})", self.width, s);
        self.lines.extend(s.lines().map(|l| l.into()));
    }

    fn flush_wrapping(&mut self) {
        if let Some(w) = self.wrapping.take() {
            self.lines.extend(w.into_untagged_lines())
        }
    }

    pub fn into_string(self) -> String {
        let mut result = String::new();
        for s in self.into_lines() {
            result.push_str(&s);
            result.push('\n');
        }
        html_trace!("into_string({}, {:?})", self.width, result);
        result
    }

    fn into_lines(mut self) -> Vec<String> {
        self.flush_wrapping();
        // And add the links
        let mut trailer = self.decorator.take().unwrap().finalise();
        if trailer.len() > 0 {
            self.start_block();
            for line in trailer.drain((0..)) {
                /* Hard wrap */
                let mut output = String::new();
                let mut pos = 0;
                for c in line.chars() {
                    // FIXME: should we percent-escape?  This is probably
                    // an invalid URL to start with.
                    let c = match c {
                        '\n' => ' ',
                        x => x,
                    };
                    let c_width = UnicodeWidthChar::width(c).unwrap();
                    if pos + c_width > self.width {
                        let mut tmp_s = String::new();
                        mem::swap(&mut output, &mut tmp_s);
                        self.lines.push(tmp_s);
                        output.push(c);
                        pos = c_width;
                    } else {
                        output.push(c);
                        pos += c_width;
                    }
                }
                self.lines.push(output);
            }
        }
        self.lines
    }
}

impl<D:TextDecorator+Clone> Renderer for TextRenderer<D> {
    type Sub = Self;
    fn add_empty_line(&mut self) {
        html_trace!("add_empty_line()");
        self.flush_wrapping();
        self.lines.push("".into());
        html_trace_quiet!("add_empty_line: at_block_end <- false");
        self.at_block_end = false;
        html_trace_quiet!("add_empty_line: new lines: {:?}", self.lines);
    }

    fn new_sub_renderer(&self, width: usize) -> Self {
        TextRenderer::new(width, self.decorator.as_ref().unwrap().clone())
    }

    fn start_block(&mut self) {
        html_trace!("start_block({})", self.width);
        self.flush_wrapping();
        if self.lines.len() > 0 {
            self.add_empty_line();
        }
        html_trace_quiet!("start_block; at_block_end <- false");
        self.at_block_end = false;
    }

    fn add_preformatted_block(&mut self, text: &str) {
        html_trace!("add_block({}, {})", self.width, text);
        self.start_block();
        self.add_subblock(text);
        html_trace_quiet!("add_block: at_block_end <- true");
        self.at_block_end = true;
    }

    fn end_block(&mut self) {
        self.at_block_end = true;
    }

    fn add_inline_text(&mut self, text: &str) {
        html_trace!("add_inline_text({}, {})", self.width, text);
        if self.at_block_end && text.chars().all(char::is_whitespace) {
            // Ignore whitespace between blocks.
            return;
        }
        if self.at_block_end {
            self.start_block();
        }
        let mut partial = self.current_text();
        partial.add_text(text, ());
    }

    fn width(&self) -> usize {
        self.width
    }

    fn add_block_line(&mut self, line: &str)
    {
        self.add_subblock(line);
    }

    fn append_subrender<'a, I>(&mut self, other: Self,
                               prefixes: I)
                           where I:Iterator<Item=&'a str>
    {
        self.flush_wrapping();
        self.lines.extend(other.into_lines()
                               .into_iter()
                               .zip(prefixes)
                               .map(|(line, prefix)| prefix.to_string() + &line));
    }

    fn append_columns<I>(&mut self, cols: I, separator: char)
                           where I:IntoIterator<Item=Self> {
        self.flush_wrapping();

        let line_sets = cols.into_iter()
                            .map(|sub_r| {
                                let width = sub_r.width;
                                (width, sub_r.into_lines()
                                             .into_iter()
                                             .map(|line| format!("{: <width$}", line, width=width))
                                             .collect())
                                 })
                            .collect::<Vec<(usize, Vec<String>)>>();

        let cell_height = line_sets.iter()
                                   .map(|&(_, ref v)| v.len())
                                   .max().unwrap_or(0);
        let spaces:String = (0..self.width).map(|_| ' ').collect();
        for i in 0..cell_height {
            let mut line = String::new();
            for (cellno, &(width, ref ls)) in line_sets.iter().enumerate() {
                let piece = ls.get(i).map(|s| s.as_str()).unwrap_or(&spaces[0..width]);
                line.push_str(piece);
                if cellno != line_sets.len()-1 {
                    line.push(separator)
                }
            }
            self.add_block_line(&line);
        }
    }

    fn empty(&self) -> bool {
        self.lines.len() == 0 && self.wrapping.is_none()
    }

    fn text_len(&self) -> usize {
        let mut result = 0;
        for line in &self.lines {
            result += UnicodeWidthStr::width(line.as_str());
        }
        if let Some(ref w) = self.wrapping {
            result += w.text_len();
        }
        result
    }

    fn start_link(&mut self, target: &str)
    {
        if let Some((s, _annotation)) = self.decorator.as_mut().map(|d| d.decorate_link_start(target)) {
            self.add_inline_text(&s);
        }
    }
    fn end_link(&mut self)
    {
        if let Some(s) = self.decorator.as_mut().map(|d| d.decorate_link_end()) {
            self.add_inline_text(&s);
        }
    }
}

#[derive(Clone)]
pub struct PlainDecorator {
    links: Vec<String>,
}

impl PlainDecorator {
    pub fn new() -> PlainDecorator {
        PlainDecorator {
            links: Vec::new(),
        }
    }
}

impl TextDecorator for PlainDecorator {
    type Annotation = ();

    fn decorate_link_start(&mut self, url: &str) -> (String, Option<Self::Annotation>)
    {
        self.links.push(url.to_string());
        ("[".to_string(), None)
    }

    fn decorate_link_end(&mut self) -> String
    {
        format!("][{}]", self.links.len())
    }

    fn finalise(self) -> Vec<String> {
        self.links.into_iter().enumerate().map(|(idx,s)| format!("[{}] {}", idx+1, s)).collect()
    }
}<commit_msg>Join up annotations a bit nore.<commit_after>use unicode_width::{UnicodeWidthStr,UnicodeWidthChar};
use super::Renderer;
use std::mem;
use std::fmt::Debug;

/// A wrapper around a String with extra metadata.
#[derive(Debug)]
struct TaggedString<T:Debug> {
    s: String,
    tag: T,
}

/// A type to build up wrapped text, allowing extra metadata for
/// spans.
#[derive(Debug)]
struct WrappedBlock<T:Clone+Eq+Debug> {
    width: usize,
    text: Vec<Vec<TaggedString<T>>>,
    textlen: usize,
    line: Vec<TaggedString<T>>,
    linelen: usize,
    spacetag: Option<T>,         // Tag for the whitespace before the current word
    word: Vec<TaggedString<T>>,  // The current word (with no whitespace).
    wordlen: usize,
}

impl<T:Clone+Eq+Debug> WrappedBlock<T> {
    pub fn new(width: usize) -> WrappedBlock<T> {
        WrappedBlock {
            width: width,
            text: Vec::new(),
            textlen: 0,
            line: Vec::new(),
            linelen: 0,
            spacetag: None,
            word: Vec::new(),
            wordlen: 0,
        }
    }

    fn flush_word(&mut self) {
        /* Finish the word. */
        if self.word.len() > 0 {
            let space_in_line = self.width - self.linelen;
            let space_needed = self.wordlen +
                        if self.linelen > 0 { 1 } else { 0 }; // space
            if space_needed <= space_in_line {
                if self.linelen > 0 {
                    self.line.push(TaggedString{s: " ".into(), tag: self.spacetag.take().unwrap()});
                    self.linelen += 1;
                }
                self.line.extend(self.word.drain(..));
                self.linelen += self.wordlen;
                self.wordlen = 0;
            } else {
                /* Start a new line */
                self.flush_line();
                if self.wordlen <= self.width {
                    let mut new_word = Vec::new();
                    mem::swap(&mut new_word, &mut self.word);
                    mem::swap(&mut self.line, &mut new_word);
                    self.linelen = self.wordlen;
                    self.wordlen = 0;
                } else {
                    /* We need to split the word. */
                    let mut wordbits = self.word.drain(..);
                    /* Note: there's always at least one piece */
                    let mut opt_piece = wordbits.next();
                    let mut lineleft = self.width;
                    while let Some(piece) = opt_piece.take() {
                        let w = UnicodeWidthStr::width(piece.s.as_str());
                        if w <= lineleft {
                            self.line.push(piece);
                            lineleft -= w;
                            self.linelen += w;
                            opt_piece = wordbits.next();
                        } else {
                            /* Split into two */
                            let mut split_idx = 0;
                            for (idx,c) in piece.s.char_indices() {
                                let c_w = UnicodeWidthChar::width(c).unwrap();
                                if c_w <= lineleft {
                                    lineleft -= c_w;
                                } else {
                                    split_idx = idx;
                                    break;
                                }
                            }
                            self.line.push(TaggedString{
                                s: piece.s[..split_idx].into(),
                                tag: piece.tag.clone(),
                            });
                            {
                                let mut tmp_line = Vec::new();
                                mem::swap(&mut tmp_line, &mut self.line);
                                self.text.push(tmp_line);
                            }
                            lineleft = self.width;
                            self.linelen = 0;
                            opt_piece = Some(TaggedString{
                                s: piece.s[split_idx..].into(),
                                tag: piece.tag,
                            });
                        }
                    }
                }
            }
        }
    }

    fn flush_line(&mut self) {
        if self.line.len() > 0 {
            let mut tmp_line = Vec::new();
            mem::swap(&mut tmp_line, &mut self.line);
            self.text.push(tmp_line);
            self.linelen = 0;
        }
    }

    fn flush(&mut self) {
        self.flush_word();
        self.flush_line();
    }

    /// Consume self and return a vector of lines.
    pub fn into_untagged_lines(mut self) -> Vec<String> {
        self.flush();

        let mut result = Vec::new();
        for line in self.text.into_iter() {
            let mut line_s = String::new();
            for TaggedString{ s, .. } in line.into_iter() {
                line_s.push_str(&s);
            }
            result.push(line_s);
        }
        result
    }

    pub fn add_text(&mut self, text: &str, tag: &T) {
        for c in text.chars() {
            if c.is_whitespace() {
                /* Whitespace is mostly ignored, except to terminate words. */
                self.flush_word();
                self.spacetag = Some(tag.clone());
            } else {
                /* Not whitespace; add to the current word. */
                if self.word.len() > 0 && self.word[self.word.len()-1].tag == *tag {
                    /* Just add a character to the last piece */
                    let last_idx = self.word.len() - 1;
                    self.word[last_idx].s.push(c);
                } else {
                    /* Starting a new word; easy. */
                    let mut s = String::new();
                    s.push(c);
                    self.word.push(TaggedString{ s: s, tag: tag.clone()});
                }
                self.wordlen += UnicodeWidthChar::width(c).unwrap();
            }
        }
    }

    pub fn text_len(&self) -> usize {
        self.textlen + self.linelen + self.wordlen
    }
}

/// Allow decorating/styling text.
pub trait TextDecorator {
    /// An annotation which can be added to text, and which will
    /// be attached to spans of text.
    type Annotation: Eq+PartialEq+Debug+Clone;

    /// Return an annotation and rendering prefix for a link.
    fn decorate_link_start(&mut self, url: &str) -> (String, Option<Self::Annotation>);

    /// Return a suffix for after a link.
    fn decorate_link_end(&mut self) -> String;

    /// Finish with a document, and return extra lines (eg footnotes)
    /// to add to the rendered text.
    fn finalise(self) -> Vec<String>;
}

/// A renderer which just outputs plain text.
pub struct TextRenderer<D:TextDecorator+Clone> {
    width: usize,
    lines: Vec<String>,
    /// True at the end of a block, meaning we should add
    /// a blank line if any other text is added.
    at_block_end: bool,
    wrapping: Option<WrappedBlock<Vec<D::Annotation>>>,
    decorator: Option<D>,
    ann_stack: Vec<D::Annotation>,
}

impl<D:TextDecorator+Clone> TextRenderer<D> {
    /// Construct a new empty TextRenderer.
    pub fn new(width: usize, decorator: D) -> TextRenderer<D> {
        html_trace!("new({})", width);
        TextRenderer {
            width: width,
            lines: Vec::new(),
            at_block_end: false,
            wrapping: None,
            decorator: Some(decorator),
            ann_stack: Vec::new(),
        }
    }

    /// Get the current line wrapping context (and create if
    /// needed).
    fn current_text(&mut self) -> &mut WrappedBlock<Vec<D::Annotation>> {
        if self.wrapping.is_none() {
            self.wrapping = Some(WrappedBlock::new(self.width));
        }
        self.wrapping.as_mut().unwrap()
    }

    pub fn add_subblock(&mut self, s: &str) {
        html_trace!("add_subblock({}, {})", self.width, s);
        self.lines.extend(s.lines().map(|l| l.into()));
    }

    fn flush_wrapping(&mut self) {
        if let Some(w) = self.wrapping.take() {
            self.lines.extend(w.into_untagged_lines())
        }
    }

    pub fn into_string(self) -> String {
        let mut result = String::new();
        for s in self.into_lines() {
            result.push_str(&s);
            result.push('\n');
        }
        html_trace!("into_string({}, {:?})", self.width, result);
        result
    }

    fn into_lines(mut self) -> Vec<String> {
        self.flush_wrapping();
        // And add the links
        let mut trailer = self.decorator.take().unwrap().finalise();
        if trailer.len() > 0 {
            self.start_block();
            for line in trailer.drain((0..)) {
                /* Hard wrap */
                let mut output = String::new();
                let mut pos = 0;
                for c in line.chars() {
                    // FIXME: should we percent-escape?  This is probably
                    // an invalid URL to start with.
                    let c = match c {
                        '\n' => ' ',
                        x => x,
                    };
                    let c_width = UnicodeWidthChar::width(c).unwrap();
                    if pos + c_width > self.width {
                        let mut tmp_s = String::new();
                        mem::swap(&mut output, &mut tmp_s);
                        self.lines.push(tmp_s);
                        output.push(c);
                        pos = c_width;
                    } else {
                        output.push(c);
                        pos += c_width;
                    }
                }
                self.lines.push(output);
            }
        }
        self.lines
    }
}

impl<D:TextDecorator+Clone> Renderer for TextRenderer<D> {
    type Sub = Self;
    fn add_empty_line(&mut self) {
        html_trace!("add_empty_line()");
        self.flush_wrapping();
        self.lines.push("".into());
        html_trace_quiet!("add_empty_line: at_block_end <- false");
        self.at_block_end = false;
        html_trace_quiet!("add_empty_line: new lines: {:?}", self.lines);
    }

    fn new_sub_renderer(&self, width: usize) -> Self {
        TextRenderer::new(width, self.decorator.as_ref().unwrap().clone())
    }

    fn start_block(&mut self) {
        html_trace!("start_block({})", self.width);
        self.flush_wrapping();
        if self.lines.len() > 0 {
            self.add_empty_line();
        }
        html_trace_quiet!("start_block; at_block_end <- false");
        self.at_block_end = false;
    }

    fn add_preformatted_block(&mut self, text: &str) {
        html_trace!("add_block({}, {})", self.width, text);
        self.start_block();
        self.add_subblock(text);
        html_trace_quiet!("add_block: at_block_end <- true");
        self.at_block_end = true;
    }

    fn end_block(&mut self) {
        self.at_block_end = true;
    }

    fn add_inline_text(&mut self, text: &str) {
        html_trace!("add_inline_text({}, {})", self.width, text);
        if self.at_block_end && text.chars().all(char::is_whitespace) {
            // Ignore whitespace between blocks.
            return;
        }
        if self.at_block_end {
            self.start_block();
        }
        // ensure wrapping is set
        let _ = self.current_text();
        self.wrapping.as_mut().unwrap().add_text(text, &self.ann_stack);
    }

    fn width(&self) -> usize {
        self.width
    }

    fn add_block_line(&mut self, line: &str)
    {
        self.add_subblock(line);
    }

    fn append_subrender<'a, I>(&mut self, other: Self,
                               prefixes: I)
                           where I:Iterator<Item=&'a str>
    {
        self.flush_wrapping();
        self.lines.extend(other.into_lines()
                               .into_iter()
                               .zip(prefixes)
                               .map(|(line, prefix)| prefix.to_string() + &line));
    }

    fn append_columns<I>(&mut self, cols: I, separator: char)
                           where I:IntoIterator<Item=Self> {
        self.flush_wrapping();

        let line_sets = cols.into_iter()
                            .map(|sub_r| {
                                let width = sub_r.width;
                                (width, sub_r.into_lines()
                                             .into_iter()
                                             .map(|line| format!("{: <width$}", line, width=width))
                                             .collect())
                                 })
                            .collect::<Vec<(usize, Vec<String>)>>();

        let cell_height = line_sets.iter()
                                   .map(|&(_, ref v)| v.len())
                                   .max().unwrap_or(0);
        let spaces:String = (0..self.width).map(|_| ' ').collect();
        for i in 0..cell_height {
            let mut line = String::new();
            for (cellno, &(width, ref ls)) in line_sets.iter().enumerate() {
                let piece = ls.get(i).map(|s| s.as_str()).unwrap_or(&spaces[0..width]);
                line.push_str(piece);
                if cellno != line_sets.len()-1 {
                    line.push(separator)
                }
            }
            self.add_block_line(&line);
        }
    }

    fn empty(&self) -> bool {
        self.lines.len() == 0 && self.wrapping.is_none()
    }

    fn text_len(&self) -> usize {
        let mut result = 0;
        for line in &self.lines {
            result += UnicodeWidthStr::width(line.as_str());
        }
        if let Some(ref w) = self.wrapping {
            result += w.text_len();
        }
        result
    }

    fn start_link(&mut self, target: &str)
    {
        if let Some((s, _annotation)) = self.decorator.as_mut().map(|d| d.decorate_link_start(target)) {
            self.add_inline_text(&s);
        }
    }
    fn end_link(&mut self)
    {
        if let Some(s) = self.decorator.as_mut().map(|d| d.decorate_link_end()) {
            self.add_inline_text(&s);
        }
    }
}

#[derive(Clone)]
pub struct PlainDecorator {
    links: Vec<String>,
}

impl PlainDecorator {
    pub fn new() -> PlainDecorator {
        PlainDecorator {
            links: Vec::new(),
        }
    }
}

impl TextDecorator for PlainDecorator {
    type Annotation = ();

    fn decorate_link_start(&mut self, url: &str) -> (String, Option<Self::Annotation>)
    {
        self.links.push(url.to_string());
        ("[".to_string(), None)
    }

    fn decorate_link_end(&mut self) -> String
    {
        format!("][{}]", self.links.len())
    }

    fn finalise(self) -> Vec<String> {
        self.links.into_iter().enumerate().map(|(idx,s)| format!("[{}] {}", idx+1, s)).collect()
    }
}<|endoftext|>


<commit_before>//! The Ref object is a helper over the link functionality, so one is able to create references to
//! files outside of the imag store.

use std::path::PathBuf;
use std::ops::Deref;
use std::ops::DerefMut;
use std::collections::BTreeMap;

use libimagstore::store::FileLockEntry;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
use libimagstore::store::Store;
use libimagerror::into::IntoError;

use toml::Value;

use error::RefErrorKind as REK;
use flags::RefFlags;
use result::Result;
use module_path::ModuleEntryPath;

pub struct Ref<'a>(FileLockEntry<'a>);

impl<'a> Ref<'a> {

    /// Try to get `si` as Ref object from the store
    pub fn get(store: &'a Store, si: StoreId) -> Result<Ref<'a>> {
        match store.get(si) {
            Err(e) => return Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
            Ok(None) => return Err(REK::RefNotInStore.into_error()),
            Ok(Some(fle)) => Ref::read_reference(&fle).map(|_| Ref(fle)),
        }
    }

    fn read_reference(fle: &FileLockEntry<'a>) -> Result<PathBuf> {
        match fle.get_header().read("ref.reference") {
            Ok(Some(Value::String(s))) => Ok(PathBuf::from(s)),
            Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),
            Ok(None)    => Err(REK::HeaderFieldMissingError.into_error()),
            Err(e)      => Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
        }
    }

    /// Create a Ref object which refers to `pb`
    pub fn create(store: &'a Store, pb: PathBuf, flags: RefFlags) -> Result<Ref<'a>> {
        use std::fs::File;
        use std::io::Read;
        use crypto::sha1::Sha1;
        use crypto::digest::Digest;

        if !pb.exists() {
            return Err(REK::RefTargetDoesNotExist.into_error());
        }
        if flags.get_content_hashing() && pb.is_dir() {
            return Err(REK::RefTargetCannotBeHashed.into_error());
        }

        let (mut fle, content_hash, permissions, canonical_path) = { // scope to be able to fold
            try!(File::open(pb.clone())
                .map_err(Box::new)
                .map_err(|e| REK::RefTargetFileCannotBeOpened.into_error_with_cause(e))

                // If we were able to open this file,
                // we hash the contents of the file and return (file, hash)
                .and_then(|mut file| {
                    let opt_contenthash = if flags.get_content_hashing() {
                        let mut hasher = Sha1::new();
                        let mut s = String::new();
                        file.read_to_string(&mut s);
                        hasher.input_str(&s[..]);
                        Some(hasher.result_str())
                    } else {
                        None
                    };

                    Ok((file, opt_contenthash))
                })

                // and then we get the permissions if we have to
                // and return (file, content hash, permissions)
                .and_then(|(file, opt_contenthash)| {
                    let opt_permissions = if flags.get_permission_tracking() {
                        Some(try!(file
                                  .metadata()
                                  .map(|md| md.permissions())
                                  .map_err(Box::new)
                                  .map_err(|e| REK::RefTargetCannotReadPermissions.into_error_with_cause(e))
                        ))
                    } else {
                        None
                    };

                    Ok((file, opt_contenthash, opt_permissions))
                })

                // and then we try to canonicalize the PathBuf, because we want to store a
                // canonicalized path
                // and return (file, content hash, permissions, canonicalized path)
                .and_then(|(file, opt_contenthash, opt_permissions)| {
                    pb.canonicalize()
                        .map(|can| (file, opt_contenthash, opt_permissions, can))
                        // if PathBuf::canonicalize() failed, build an error from the return value
                        .map_err(|e| REK::PathCanonicalizationError.into_error_with_cause(Box::new(e)))
                })

                // and then we hash the canonicalized path
                // and return (file, content hash, permissions, canonicalized path, path hash)
                .and_then(|(file, opt_contenthash, opt_permissions, can)| {
                    let path_hash = try!(Ref::hash_path(&can)
                        .map_err(Box::new)
                        .map_err(|e| REK::PathHashingError.into_error_with_cause(e))
                    );

                    Ok((file, opt_contenthash, opt_permissions, can, path_hash))
                })

                // and then we convert the PathBuf of the canonicalized path to a String to be able
                // to save it in the Ref FileLockEntry obj
                // and return
                // (file, content hash, permissions, canonicalized path as String, path hash)
                .and_then(|(file, opt_conhash, opt_perm, can, path_hash)| {
                    match can.to_str().map(String::from) {
                        // UTF convert error in PathBuf::to_str(),
                        None      => Err(REK::PathUTF8Error.into_error()),
                        Some(can) => Ok((file, opt_conhash, opt_perm, can, path_hash))
                    }
                })

                // and then we create the FileLockEntry in the Store
                // and return (filelockentry, content hash, permissions, canonicalized path)
                .and_then(|(file, opt_conhash, opt_perm, can, path_hash)| {
                    let fle = try!(store
                                   .create(ModuleEntryPath::new(path_hash))
                                   .map_err(Box::new)
                                   .map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
                    );

                    Ok((fle, opt_conhash, opt_perm, can))
                })
            )
        };

        for tpl in [
                Some(("ref",             Value::Table(BTreeMap::new()))),
                Some(("ref.permissions", Value::Table(BTreeMap::new()))),

                Some(("ref.path", Value::String(canonical_path))),

                content_hash.map(|h| ("ref.content_hash", Value::String(h))),
                permissions.map(|p| ("ref.permissions.ro", Value::Boolean(p.readonly()))),
            ].into_iter()
        {
            match tpl {
                &Some((ref s, ref v)) => {
                    match fle.get_header_mut().insert(s, v.clone()) {
                        Ok(false) => {
                            let e = REK::HeaderFieldAlreadyExistsError.into_error();
                            let e = Box::new(e);
                            let e = REK::HeaderFieldWriteError.into_error_with_cause(e);
                            return Err(e);
                        },
                        Err(e) => {
                            let e = Box::new(e);
                            let e = REK::HeaderFieldWriteError.into_error_with_cause(e);
                            return Err(e);
                        },
                        _ => (),
                    }
                }
                &None => {
                    debug!("Not going to insert.");
                }
            }
        }

        Ok(Ref(fle))
    }

    /// Creates a Hash from a PathBuf by making the PathBuf absolute and then running a hash
    /// algorithm on it
    fn hash_path(pb: &PathBuf) -> Result<String> {
        use std::io::Read;
        use crypto::sha1::Sha1;
        use crypto::digest::Digest;

        match pb.to_str() {
            Some(s) => {
                let mut hasher = Sha1::new();
                hasher.input_str(s);
                Ok(hasher.result_str())
            },
            None => return Err(REK::PathUTF8Error.into_error()),
        }
    }

    /// check whether the pointer the Ref represents still points to a file which exists
    pub fn fs_link_exists(&self) -> bool {
        unimplemented!()
    }

    /// Alias for `r.fs_link_exists() && r.deref().is_file()`
    pub fn is_ref_to_file(&self) -> bool {
        unimplemented!()
    }

    /// Alias for `r.fs_link_exists() && r.deref().is_dir()`
    pub fn is_ref_to_dir(&self) -> bool {
        unimplemented!()
    }

    /// Alias for `!Ref::fs_link_exists()`
    pub fn is_dangling(&self) -> bool {
        !self.fs_link_exists()
    }

    /// check whether the pointer the Ref represents is valid
    /// This includes:
    ///     - Hashsum of the file is still the same as stored in the Ref
    ///     - file permissions are still valid
    pub fn fs_link_valid(&self) -> bool {
        unimplemented!()
    }

    /// Check whether the file permissions of the referenced file are equal to the stored
    /// permissions
    pub fn fs_link_valid_permissions(&self) -> bool {
        unimplemented!()
    }

    /// Check whether the Hashsum of the referenced file is equal to the stored hashsum
    pub fn fs_link_valid_hash(&self) -> bool {
        unimplemented!()
    }

    /// Update the Ref by re-checking the file from FS
    /// This errors if the file is not present or cannot be read()
    pub fn update_ref(&mut self) -> Result<()> {
        unimplemented!()
    }

    /// Get the path of the file which is reffered to by this Ref
    pub fn fs_file(&self) -> &PathBuf {
        unimplemented!()
    }

    /// Check whether there is a reference to the file at `pb`
    pub fn exists(store: &Store, pb: PathBuf) -> Result<bool> {
        unimplemented!()
    }

    /// Re-find a referenced file
    ///
    /// This function tries to re-find a ref by searching all directories in `search_roots` recursively
    /// for a file which matches the hash of the Ref `ref`.
    ///
    /// If `search_roots` is `None`, it starts at the filesystem root `/`.
    ///
    /// # Warning
    ///
    /// This option causes heavy I/O as it recursively searches the Filesystem.
    pub fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Option<PathBuf> {
        unimplemented!()
    }

}

impl<'a> Deref for Ref<'a> {
    type Target = FileLockEntry<'a>;

    fn deref(&self) -> &FileLockEntry<'a> {
        &self.0
    }

}

impl<'a> DerefMut for Ref<'a> {

    fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
        &mut self.0
    }

}
<commit_msg>Impl Ref::fs_link_exists()<commit_after>//! The Ref object is a helper over the link functionality, so one is able to create references to
//! files outside of the imag store.

use std::path::PathBuf;
use std::ops::Deref;
use std::ops::DerefMut;
use std::collections::BTreeMap;

use libimagstore::store::FileLockEntry;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
use libimagstore::store::Store;
use libimagerror::into::IntoError;

use toml::Value;

use error::RefErrorKind as REK;
use flags::RefFlags;
use result::Result;
use module_path::ModuleEntryPath;

pub struct Ref<'a>(FileLockEntry<'a>);

impl<'a> Ref<'a> {

    /// Try to get `si` as Ref object from the store
    pub fn get(store: &'a Store, si: StoreId) -> Result<Ref<'a>> {
        match store.get(si) {
            Err(e) => return Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
            Ok(None) => return Err(REK::RefNotInStore.into_error()),
            Ok(Some(fle)) => Ref::read_reference(&fle).map(|_| Ref(fle)),
        }
    }

    fn read_reference(fle: &FileLockEntry<'a>) -> Result<PathBuf> {
        match fle.get_header().read("ref.reference") {
            Ok(Some(Value::String(s))) => Ok(PathBuf::from(s)),
            Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),
            Ok(None)    => Err(REK::HeaderFieldMissingError.into_error()),
            Err(e)      => Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
        }
    }

    /// Create a Ref object which refers to `pb`
    pub fn create(store: &'a Store, pb: PathBuf, flags: RefFlags) -> Result<Ref<'a>> {
        use std::fs::File;
        use std::io::Read;
        use crypto::sha1::Sha1;
        use crypto::digest::Digest;

        if !pb.exists() {
            return Err(REK::RefTargetDoesNotExist.into_error());
        }
        if flags.get_content_hashing() && pb.is_dir() {
            return Err(REK::RefTargetCannotBeHashed.into_error());
        }

        let (mut fle, content_hash, permissions, canonical_path) = { // scope to be able to fold
            try!(File::open(pb.clone())
                .map_err(Box::new)
                .map_err(|e| REK::RefTargetFileCannotBeOpened.into_error_with_cause(e))

                // If we were able to open this file,
                // we hash the contents of the file and return (file, hash)
                .and_then(|mut file| {
                    let opt_contenthash = if flags.get_content_hashing() {
                        let mut hasher = Sha1::new();
                        let mut s = String::new();
                        file.read_to_string(&mut s);
                        hasher.input_str(&s[..]);
                        Some(hasher.result_str())
                    } else {
                        None
                    };

                    Ok((file, opt_contenthash))
                })

                // and then we get the permissions if we have to
                // and return (file, content hash, permissions)
                .and_then(|(file, opt_contenthash)| {
                    let opt_permissions = if flags.get_permission_tracking() {
                        Some(try!(file
                                  .metadata()
                                  .map(|md| md.permissions())
                                  .map_err(Box::new)
                                  .map_err(|e| REK::RefTargetCannotReadPermissions.into_error_with_cause(e))
                        ))
                    } else {
                        None
                    };

                    Ok((file, opt_contenthash, opt_permissions))
                })

                // and then we try to canonicalize the PathBuf, because we want to store a
                // canonicalized path
                // and return (file, content hash, permissions, canonicalized path)
                .and_then(|(file, opt_contenthash, opt_permissions)| {
                    pb.canonicalize()
                        .map(|can| (file, opt_contenthash, opt_permissions, can))
                        // if PathBuf::canonicalize() failed, build an error from the return value
                        .map_err(|e| REK::PathCanonicalizationError.into_error_with_cause(Box::new(e)))
                })

                // and then we hash the canonicalized path
                // and return (file, content hash, permissions, canonicalized path, path hash)
                .and_then(|(file, opt_contenthash, opt_permissions, can)| {
                    let path_hash = try!(Ref::hash_path(&can)
                        .map_err(Box::new)
                        .map_err(|e| REK::PathHashingError.into_error_with_cause(e))
                    );

                    Ok((file, opt_contenthash, opt_permissions, can, path_hash))
                })

                // and then we convert the PathBuf of the canonicalized path to a String to be able
                // to save it in the Ref FileLockEntry obj
                // and return
                // (file, content hash, permissions, canonicalized path as String, path hash)
                .and_then(|(file, opt_conhash, opt_perm, can, path_hash)| {
                    match can.to_str().map(String::from) {
                        // UTF convert error in PathBuf::to_str(),
                        None      => Err(REK::PathUTF8Error.into_error()),
                        Some(can) => Ok((file, opt_conhash, opt_perm, can, path_hash))
                    }
                })

                // and then we create the FileLockEntry in the Store
                // and return (filelockentry, content hash, permissions, canonicalized path)
                .and_then(|(file, opt_conhash, opt_perm, can, path_hash)| {
                    let fle = try!(store
                                   .create(ModuleEntryPath::new(path_hash))
                                   .map_err(Box::new)
                                   .map_err(|e| REK::StoreWriteError.into_error_with_cause(e))
                    );

                    Ok((fle, opt_conhash, opt_perm, can))
                })
            )
        };

        for tpl in [
                Some(("ref",             Value::Table(BTreeMap::new()))),
                Some(("ref.permissions", Value::Table(BTreeMap::new()))),

                Some(("ref.path", Value::String(canonical_path))),

                content_hash.map(|h| ("ref.content_hash", Value::String(h))),
                permissions.map(|p| ("ref.permissions.ro", Value::Boolean(p.readonly()))),
            ].into_iter()
        {
            match tpl {
                &Some((ref s, ref v)) => {
                    match fle.get_header_mut().insert(s, v.clone()) {
                        Ok(false) => {
                            let e = REK::HeaderFieldAlreadyExistsError.into_error();
                            let e = Box::new(e);
                            let e = REK::HeaderFieldWriteError.into_error_with_cause(e);
                            return Err(e);
                        },
                        Err(e) => {
                            let e = Box::new(e);
                            let e = REK::HeaderFieldWriteError.into_error_with_cause(e);
                            return Err(e);
                        },
                        _ => (),
                    }
                }
                &None => {
                    debug!("Not going to insert.");
                }
            }
        }

        Ok(Ref(fle))
    }

    /// Creates a Hash from a PathBuf by making the PathBuf absolute and then running a hash
    /// algorithm on it
    fn hash_path(pb: &PathBuf) -> Result<String> {
        use std::io::Read;
        use crypto::sha1::Sha1;
        use crypto::digest::Digest;

        match pb.to_str() {
            Some(s) => {
                let mut hasher = Sha1::new();
                hasher.input_str(s);
                Ok(hasher.result_str())
            },
            None => return Err(REK::PathUTF8Error.into_error()),
        }
    }

    /// check whether the pointer the Ref represents still points to a file which exists
    pub fn fs_link_exists(&self) -> Result<bool> {
        self.fs_file().map(|pathbuf| pathbuf.exists())
    }

    /// Alias for `r.fs_link_exists() && r.deref().is_file()`
    pub fn is_ref_to_file(&self) -> bool {
        unimplemented!()
    }

    /// Alias for `r.fs_link_exists() && r.deref().is_dir()`
    pub fn is_ref_to_dir(&self) -> bool {
        unimplemented!()
    }

    /// Alias for `!Ref::fs_link_exists()`
    pub fn is_dangling(&self) -> Result<bool> {
        self.fs_link_exists().map(|b| !b)
    }

    /// check whether the pointer the Ref represents is valid
    /// This includes:
    ///     - Hashsum of the file is still the same as stored in the Ref
    ///     - file permissions are still valid
    pub fn fs_link_valid(&self) -> bool {
        unimplemented!()
    }

    /// Check whether the file permissions of the referenced file are equal to the stored
    /// permissions
    pub fn fs_link_valid_permissions(&self) -> bool {
        unimplemented!()
    }

    /// Check whether the Hashsum of the referenced file is equal to the stored hashsum
    pub fn fs_link_valid_hash(&self) -> bool {
        unimplemented!()
    }

    /// Update the Ref by re-checking the file from FS
    /// This errors if the file is not present or cannot be read()
    pub fn update_ref(&mut self) -> Result<()> {
        unimplemented!()
    }

    /// Get the path of the file which is reffered to by this Ref
    pub fn fs_file(&self) -> &PathBuf {
        unimplemented!()
    }

    /// Check whether there is a reference to the file at `pb`
    pub fn exists(store: &Store, pb: PathBuf) -> Result<bool> {
        unimplemented!()
    }

    /// Re-find a referenced file
    ///
    /// This function tries to re-find a ref by searching all directories in `search_roots` recursively
    /// for a file which matches the hash of the Ref `ref`.
    ///
    /// If `search_roots` is `None`, it starts at the filesystem root `/`.
    ///
    /// # Warning
    ///
    /// This option causes heavy I/O as it recursively searches the Filesystem.
    pub fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Option<PathBuf> {
        unimplemented!()
    }

}

impl<'a> Deref for Ref<'a> {
    type Target = FileLockEntry<'a>;

    fn deref(&self) -> &FileLockEntry<'a> {
        &self.0
    }

}

impl<'a> DerefMut for Ref<'a> {

    fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
        &mut self.0
    }

}
<|endoftext|>