<commit_before>use crate::{api::prelude::*, proc_macros::*};

/// Use this enum to trigger navigation actions on a pager.
#[derive(Debug, Clone, PartialEq)]
pub enum PagerAction {
    /// Navigates to the next child. If the current child is the last in the list nothing will happen.
    Next,

    /// Navigates to the previous child. If the current child is the first in the list nothing will happen.
    Previous,

    /// Navigates to the given index. Is the index out of bounds nothing will happen.
    Navigate(usize),

    /// Navigates to the current given index.
    NavigateToCurrent,

    /// Removes the child on the given index. If the index is out of bounds nothing will happen.
    Remove(usize),

    /// Pushes the given entity on the end of the pagers children.
    Push(Entity),
}

/// Structure of elements used to handle the pager status.
#[derive(Default, Clone, Debug, AsAny)]
pub struct PagerState {
    current_index: usize,
}

impl State for PagerState {
    fn init(&mut self, _registry: &mut Registry, ctx: &mut Context) {
        let current_index = Pager::correct_current_index(ctx);

        if let Some(count) = ctx.widget().children_count() {
            for i in 0..count {
                // make child on the current index always visible (todo: use current_index)
                if i == current_index {
                    if let Some(child) = &mut ctx.try_child_from_index(i) {
                        child.set("visibility", Visibility::Visible);
                    }

                    continue;
                }

                // collapse all other children
                if let Some(child) = &mut ctx.try_child_from_index(i) {
                    child.set("visibility", Visibility::Collapsed);
                }
            }
        }

        Pager::update_next_previous_enabled(ctx, current_index);
    }

    fn messages(
        &mut self,
        mut messages: MessageReader,
        _registry: &mut Registry,
        ctx: &mut Context,
    ) {
        for action in messages.read::<PagerAction>() {
            match action {
                PagerAction::Next => Pager::next(ctx, ctx.entity()),
                PagerAction::Previous => Pager::previous(ctx, ctx.entity()),
                PagerAction::Navigate(index) => Pager::navigate(ctx, ctx.entity(), index),
                PagerAction::Remove(index) => Pager::remove(ctx, ctx.entity(), index),
                PagerAction::Push(entity) => Pager::push(ctx, ctx.entity(), entity),
                PagerAction::NavigateToCurrent => {
                    let current_index = Pager::correct_current_index(ctx);
                    Pager::navigate(ctx, ctx.entity(), current_index);
                }
            }
        }
    }
}

widget!(
    /// `Pager` is a navigation widget that provides a stack based navigation.
    ///
    /// There are two way to interact with the `Pager`. By using the
    /// `PagerState` and by using the associated functions of `Pager`.
    /// It is suggested to use the state methods on callbacks and the
    /// associated functions inside of states of other widgets.
    ///
    /// # Example
    ///
    /// ```rust
    /// let pager = Pager::new()
    ///     .child(TextBlock::new().text("Page 1").build(ctx))
    ///     .child(TextBlock::new().text("Page 2").build(ctx))
    ///     .build(ctx);
    ///
    /// let next_button = Button::new()
    ///     .enabled(("next_enabled", pager))
    ///     .text("next")
    ///     .on_click(move |states, _| {
    ///         states.send_message(PagerAction::Next, pager);
    ///         true
    ///     })
    ///     .build(ctx);
    ///
    /// let previous_button = Button::new()
    ///     .enabled(("previous_enabled", pager))
    ///     .text("previous")
    ///     .on_click(move |states, _| {
    ///         states.send_message(PagerAction::Previous, pager);
    ///         true
    ///     })
    ///     .build(ctx);
    /// ```
    Pager<PagerState> {
        /// Defines the index of the current shown child.
        current_index: usize,

        /// Used to check if  next can be executed. If the current visible child is the last child next can not be executed.
        next_enabled: bool,

        /// Used to check if a previous can be executed. If the current visible child is the first child previous can not be executed.
        previous_enabled: bool
    }
);

// associated functions
impl Pager {
    // checks and updates if next and previous can be executed.
    fn update_next_previous_enabled(ctx: &mut Context, current_index: usize) {
        if current_index == 0 {
            Pager::previous_enabled_set(&mut ctx.widget(), false);
        } else {
            Pager::previous_enabled_set(&mut ctx.widget(), true);
        }

        if let Some(count) = ctx.widget().children_count() {
            if current_index == count - 1 {
                Pager::next_enabled_set(&mut ctx.widget(), false);
            } else {
                Pager::next_enabled_set(&mut ctx.widget(), true);
            }
        }
    }

    // corrects the current_index to bounds and returns the corrected value.
    fn correct_current_index(ctx: &mut Context) -> usize {
        let mut current_index = *Pager::current_index_ref(&ctx.widget());
        if let Some(count) = ctx.widget().children_count() {
            if current_index >= count {
                current_index = count - 1;
            }
        }

        Pager::current_index_set(&mut ctx.widget(), current_index);

        current_index
    }

    /// Navigates to the next child. If the current child is the last
    /// in the list nothing will happen.
    pub fn next(ctx: &mut Context, entity: Entity) {
        let current_index = *Pager::current_index_ref(&ctx.get_widget(entity));
        Pager::navigate(ctx, entity, current_index + 1);
    }

    /// Navigates to the previous child. If the current child is the
    /// first in the list nothing will happen.
    pub fn previous(ctx: &mut Context, entity: Entity) {
        let current_index = *Pager::current_index_ref(&ctx.get_widget(entity));
        if current_index == 0 {
            return;
        }
        Pager::navigate(ctx, entity, current_index - 1);
    }

    /// Navigates to the given index. Is the index out of bounds nothing will happen.
    pub fn navigate(ctx: &mut Context, entity: Entity, index: usize) {
        // update enabled next / previous
        Pager::panics_on_wrong_type(&ctx.get_widget(entity));

        // if the given index equals to the current index, the visible item it returned
        if let Some(child) = ctx.try_child_from_index(index) {
            if *child.get::<Visibility>("visibility") == Visibility::Visible {
                return;
            }
        }

        if let Some(count) = ctx.get_widget(entity).children_count() {
            if index >= count {
                return;
            }

            // hide the last visible child
            if let Some(count) = ctx.widget().children_count() {
                for i in 0..count {
                    if let Some(child) = &mut ctx.try_child_from_index(i) {
                        if *child.get::<Visibility>("visibility") != Visibility::Visible {
                            continue;
                        }

                        child.set("visibility", Visibility::Collapsed);
                    }
                }
            }

            // mark corresponding child for given index as visible
            if let Some(child) = &mut ctx.try_child_from_index(index) {
                child.set("visibility", Visibility::Visible);
            }

            Pager::current_index_set(&mut ctx.get_widget(entity), index);

            Pager::update_next_previous_enabled(ctx, index);
        }
    }

    /// Removes the child on the given index. If the index is out of
    /// bounds nothing will happen.
    pub fn remove(ctx: &mut Context, entity: Entity, index: usize) {
        Pager::panics_on_wrong_type(&ctx.get_widget(entity));

        let current_index = *Pager::current_index_ref(&ctx.get_widget(entity));

        let child_entity = ctx.try_child_from_index(index).map(|child| child.entity());

        if let Some(child) = child_entity {
            ctx.remove_child_from(child, entity);
        }

        if let Some(count) = ctx.widget().children_count() {
            if (index == current_index && index == count - 1) || index < current_index {
                Pager::navigate(ctx, entity, current_index - 1);
                return;
            }
        }

        Pager::navigate(ctx, entity, current_index);
    }

    /// Pushes the given entity on the end of the pagers children.
    pub fn push(ctx: &mut Context, entity: Entity, child: Entity) {
        Pager::panics_on_wrong_type(&ctx.get_widget(entity));

        ctx.append_child_entity_to(child, entity);
        let current_index = *Pager::current_index_ref(&ctx.get_widget(entity));
        Pager::update_next_previous_enabled(ctx, current_index);
    }
}

impl Template for Pager {
    fn template(self, _id: Entity, _context: &mut BuildContext) -> Self {
        self.name("Pager").on_changed("current_index", |ctx, id| {
            ctx.send_message(PagerAction::NavigateToCurrent, id);
        })
    }
}
<commit_msg>orbtk_core: implement PagerState methods<commit_after>use crate::{api::prelude::*, proc_macros::*};

/// Use this enum to trigger navigation actions on a pager.
#[derive(Debug, Clone, PartialEq)]
pub enum PagerAction {
    /// Navigates to the next child. If the current child is the last in the list nothing will happen.
    Next,

    /// Navigates to the previous child. If the current child is the first in the list nothing will happen.
    Previous,

    /// Navigates to the given index. Is the index out of bounds nothing will happen.
    Navigate(usize),

    /// Navigates to the current given index.
    NavigateToCurrent,

    /// Pushes the given entity on the end of the pagers children.
    Push(Entity),

    /// Removes the child on the given index. If the index is out of bounds nothing will happen.
    Remove(usize),
}

/// Structure of elements used to handle the pager status.
#[derive(AsAny, Clone, Debug, Default)]
pub struct PagerState {
    current_index: usize,
}

impl PagerState {
    /// Returns the current index.
    pub fn get_current_index(&self) -> usize {
        self.current_index
    }

    /// Sets the current index.
    pub fn set_current_index(&mut self, new_index: usize) -> usize {
        self.current_index = new_index;
        self.current_index
    }
}

impl State for PagerState {
    fn init(&mut self, _registry: &mut Registry, ctx: &mut Context) {
        PagerState::set_current_index(self, Pager::correct_current_index(ctx));

        if let Some(count) = ctx.widget().children_count() {
            for i in 0..count {
                // make child on the current index always visible
                if i == PagerState::get_current_index(self) {
                    if let Some(child) = &mut ctx.try_child_from_index(i) {
                        child.set("visibility", Visibility::Visible);
                    }

                    continue;
                }

                // collapse all other children
                if let Some(child) = &mut ctx.try_child_from_index(i) {
                    child.set("visibility", Visibility::Collapsed);
                }
            }
        }

        Pager::update_next_previous_enabled(ctx, self.current_index);
    }

    fn messages(
        &mut self,
        mut messages: MessageReader,
        _registry: &mut Registry,
        ctx: &mut Context,
    ) {
        for action in messages.read::<PagerAction>() {
            match action {
                PagerAction::Next => Pager::next(ctx, ctx.entity()),
                PagerAction::Previous => Pager::previous(ctx, ctx.entity()),
                PagerAction::Navigate(index) => Pager::navigate(ctx, ctx.entity(), index),
                PagerAction::Remove(index) => Pager::remove(ctx, ctx.entity(), index),
                PagerAction::Push(entity) => Pager::push(ctx, ctx.entity(), entity),
                PagerAction::NavigateToCurrent => {
                    PagerState::set_current_index(self, Pager::correct_current_index(ctx));
                    Pager::navigate(ctx, ctx.entity(), PagerState::get_current_index(self));
                }
            }
        }
    }
}

widget!(
    /// `Pager` is a navigation widget that provides a stack based navigation.
    ///
    /// There are two way to interact with the `Pager`. By using the
    /// `PagerState` and by using the associated functions of `Pager`.
    /// It is suggested to use the state methods on callbacks and the
    /// associated functions inside of states of other widgets.
    ///
    /// # Example
    ///
    /// ```rust
    /// let pager = Pager::new()
    ///     .child(TextBlock::new().text("Page 1").build(ctx))
    ///     .child(TextBlock::new().text("Page 2").build(ctx))
    ///     .build(ctx);
    ///
    /// let next_button = Button::new()
    ///     .enabled(("next_enabled", pager))
    ///     .text("next")
    ///     .on_click(move |states, _| {
    ///         states.send_message(PagerAction::Next, pager);
    ///         true
    ///     })
    ///     .build(ctx);
    ///
    /// let previous_button = Button::new()
    ///     .enabled(("previous_enabled", pager))
    ///     .text("previous")
    ///     .on_click(move |states, _| {
    ///         states.send_message(PagerAction::Previous, pager);
    ///         true
    ///     })
    ///     .build(ctx);
    /// ```
    Pager<PagerState> {
        /// Defines the index of the current shown child.
        current_index: usize,

        /// Used to check if  next can be executed. If the current visible child is the last child next can not be executed.
        next_enabled: bool,

        /// Used to check if a previous can be executed. If the current visible child is the first child previous can not be executed.
        previous_enabled: bool
    }
);

// associated functions
impl Pager {
    // corrects the current_index to bounds and returns the corrected value.
    fn correct_current_index(ctx: &mut Context) -> usize {
        let mut current_index = *Pager::current_index_ref(&ctx.widget());
        if let Some(count) = ctx.widget().children_count() {
            if current_index >= count {
                current_index = count - 1;
            }
        }

        Pager::current_index_set(&mut ctx.widget(), current_index);

        current_index
    }

    /// Navigates to the given index. Is the index out of bounds nothing will happen.
    pub fn navigate(ctx: &mut Context, entity: Entity, index: usize) {
        // update enabled next / previous
        Pager::panics_on_wrong_type(&ctx.get_widget(entity));

        // if the given index equals to the current index, the visible item it returned
        if let Some(child) = ctx.try_child_from_index(index) {
            if *child.get::<Visibility>("visibility") == Visibility::Visible {
                return;
            }
        }

        if let Some(count) = ctx.get_widget(entity).children_count() {
            if index >= count {
                return;
            }

            // hide the last visible child
            if let Some(count) = ctx.widget().children_count() {
                for i in 0..count {
                    if let Some(child) = &mut ctx.try_child_from_index(i) {
                        if *child.get::<Visibility>("visibility") != Visibility::Visible {
                            continue;
                        }

                        child.set("visibility", Visibility::Collapsed);
                    }
                }
            }

            // mark corresponding child for given index as visible
            if let Some(child) = &mut ctx.try_child_from_index(index) {
                child.set("visibility", Visibility::Visible);
            }

            Pager::current_index_set(&mut ctx.get_widget(entity), index);

            Pager::update_next_previous_enabled(ctx, index);
        }
    }

    /// Navigates to the next child. If the current child is the last
    /// in the list nothing will happen.
    pub fn next(ctx: &mut Context, entity: Entity) {
        let current_index = *Pager::current_index_ref(&ctx.get_widget(entity));
        Pager::navigate(ctx, entity, current_index + 1);
    }

    /// Navigates to the previous child. If the current child is the
    /// first in the list nothing will happen.
    pub fn previous(ctx: &mut Context, entity: Entity) {
        let current_index = *Pager::current_index_ref(&ctx.get_widget(entity));
        if current_index == 0 {
            return;
        }
        Pager::navigate(ctx, entity, current_index - 1);
    }

    /// Pushes the given entity on the end of the pagers children.
    pub fn push(ctx: &mut Context, entity: Entity, child: Entity) {
        Pager::panics_on_wrong_type(&ctx.get_widget(entity));

        ctx.append_child_entity_to(child, entity);
        let current_index = *Pager::current_index_ref(&ctx.get_widget(entity));
        Pager::update_next_previous_enabled(ctx, current_index);
    }

    /// Removes the child on the given index. If the index is out of
    /// bounds nothing will happen.
    pub fn remove(ctx: &mut Context, entity: Entity, index: usize) {
        Pager::panics_on_wrong_type(&ctx.get_widget(entity));

        let current_index = *Pager::current_index_ref(&ctx.get_widget(entity));

        let child_entity = ctx.try_child_from_index(index).map(|child| child.entity());

        if let Some(child) = child_entity {
            ctx.remove_child_from(child, entity);
        }

        if let Some(count) = ctx.widget().children_count() {
            if (index == current_index && index == count - 1) || index < current_index {
                Pager::navigate(ctx, entity, current_index - 1);
                return;
            }
        }

        Pager::navigate(ctx, entity, current_index);
    }

    // checks and updates if next and previous can be executed.
    fn update_next_previous_enabled(ctx: &mut Context, current_index: usize) {
        if current_index == 0 {
            Pager::previous_enabled_set(&mut ctx.widget(), false);
        } else {
            Pager::previous_enabled_set(&mut ctx.widget(), true);
        }

        if let Some(count) = ctx.widget().children_count() {
            if current_index == count - 1 {
                Pager::next_enabled_set(&mut ctx.widget(), false);
            } else {
                Pager::next_enabled_set(&mut ctx.widget(), true);
            }
        }
    }
}

impl Template for Pager {
    fn template(self, _id: Entity, _context: &mut BuildContext) -> Self {
        self.name("Pager").on_changed("current_index", |ctx, id| {
            ctx.send_message(PagerAction::NavigateToCurrent, id);
        })
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(test, feature(test))]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::cognitive_complexity)]
#![feature(track_caller)]

mod config;
mod exit_dialogue;
mod input_area;
mod messaging;
#[doc(hidden)]
// FIXME: This is "pub" to be able to use in an example
pub mod msg_area;
mod notifier;
mod statusline;
mod tab;
mod termbox;
mod trie;
mod tui;
mod utils;
mod widget;

#[cfg(test)]
mod tests;

pub use crate::tab::TabStyle;
pub use libtiny_ui::*;

use futures::select;
use futures::stream::StreamExt;
use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::{Rc, Weak};
use term_input::Input;
use time::Tm;
use tokio::signal::unix::{signal, SignalKind};
use tokio::sync::mpsc;
use tokio::task::spawn_local;

#[macro_use]
extern crate log;

#[derive(Clone)]
pub struct TUI {
    inner: Weak<RefCell<tui::TUI>>,
}

impl TUI {
    pub fn run(config_path: PathBuf) -> (TUI, mpsc::Receiver<Event>) {
        let tui = Rc::new(RefCell::new(tui::TUI::new(config_path)));
        let inner = Rc::downgrade(&tui);

        let (snd_ev, rcv_ev) = mpsc::channel(10);

        // For SIGWINCH handler
        let (snd_abort, rcv_abort) = mpsc::channel::<()>(1);

        // Spawn SIGWINCH handler
        spawn_local(sigwinch_handler(inner.clone(), rcv_abort));

        // Spawn input handler task
        spawn_local(input_handler(tui, snd_ev, snd_abort));

        (TUI { inner }, rcv_ev)
    }
}

async fn sigwinch_handler(tui: Weak<RefCell<tui::TUI>>, rcv_abort: mpsc::Receiver<()>) {
    let stream = match signal(SignalKind::window_change()) {
        Err(err) => {
            debug!("Can't install SIGWINCH handler: {:?}", err);
            return;
        }
        Ok(stream) => stream,
    };

    let mut stream_fused = stream.fuse();
    let mut rcv_abort_fused = rcv_abort.fuse();

    loop {
        select! {
            _ = stream_fused.next() => {
                match tui.upgrade() {
                    None => {
                        return;
                    }
                    Some(tui) => {
                        tui.borrow_mut().resize();
                    }
                }
            },
            _ = rcv_abort_fused.next() => {
                return;
            }
        }
    }
}

async fn input_handler(
    tui: Rc<RefCell<tui::TUI>>,
    mut snd_ev: mpsc::Sender<Event>,
    mut snd_abort: mpsc::Sender<()>,
) {
    let mut input = Input::new();
    while let Some(mb_ev) = input.next().await {
        use tui::TUIRet::*;
        match mb_ev {
            Err(io_err) => {
                debug!("term_input error: {:?}", io_err);
                return;
            }
            Ok(ev) => {
                let tui_ret = tui.borrow_mut().handle_input_event(ev);
                match tui_ret {
                    Abort => {
                        snd_ev.try_send(Event::Abort).unwrap();
                        let _ = snd_abort.try_send(());
                        return;
                    }
                    KeyHandled | KeyIgnored(_) | EventIgnored(_) => {}
                    Input { msg, from } => {
                        if msg[0] == '/' {
                            // Handle TUI commands, send others to downstream
                            let cmd: String = (&msg[1..]).iter().collect();
                            let handled = tui.borrow_mut().try_handle_cmd(&cmd, &from);
                            if !handled {
                                snd_ev.try_send(Event::Cmd { cmd, source: from }).unwrap();
                            }
                        } else {
                            snd_ev
                                .try_send(Event::Msg {
                                    msg: msg.into_iter().collect(),
                                    source: from,
                                })
                                .unwrap();
                        }
                    }
                    Lines { lines, from } => {
                        snd_ev
                            .try_send(Event::Lines {
                                lines,
                                source: from,
                            })
                            .unwrap();
                    }
                }
            }
        }
        tui.borrow_mut().draw();
    }
}

macro_rules! delegate {
    ( $name:ident ( $( $x:ident: $t:ty, )* ) ) => {
        fn $name(&self, $($x: $t,)*) {
            if let Some(inner) = self.inner.upgrade() {
                inner.borrow_mut().$name( $( $x, )* );
            }
        }
    }
}

impl UI for TUI {
    delegate!(draw());
    delegate!(new_server_tab(serv_name: &str, alias: Option<String>,));
    delegate!(close_server_tab(serv_name: &str,));
    delegate!(new_chan_tab(serv_name: &str, chan: &str,));
    delegate!(close_chan_tab(serv_name: &str, chan: &str,));
    delegate!(close_user_tab(serv_name: &str, nick: &str,));
    delegate!(add_client_msg(msg: &str, target: &MsgTarget,));
    delegate!(add_msg(msg: &str, ts: Tm, target: &MsgTarget,));
    delegate!(add_err_msg(msg: &str, ts: Tm, target: &MsgTarget,));
    delegate!(add_client_err_msg(msg: &str, target: &MsgTarget,));
    delegate!(clear_nicks(serv_name: &str,));
    delegate!(set_nick(serv_name: &str, new_nick: &str,));
    delegate!(add_privmsg(
        sender: &str,
        msg: &str,
        ts: Tm,
        target: &MsgTarget,
        highlight: bool,
        is_action: bool,
    ));
    delegate!(add_nick(nick: &str, ts: Option<Tm>, target: &MsgTarget,));
    delegate!(remove_nick(nick: &str, ts: Option<Tm>, target: &MsgTarget,));
    delegate!(rename_nick(
        old_nick: &str,
        new_nick: &str,
        ts: Tm,
        target: &MsgTarget,
    ));
    delegate!(set_topic(
        topic: &str,
        ts: Tm,
        serv_name: &str,
        chan_name: &str,
    ));
    delegate!(set_tab_style(style: TabStyle, target: &MsgTarget,));

    fn user_tab_exists(&self, serv_name: &str, nick: &str) -> bool {
        match self.inner.upgrade() {
            Some(tui) => tui.borrow().user_tab_exists(serv_name, nick),
            None => false,
        }
    }
}
<commit_msg>Work around a rust-analyzer bug<commit_after>#![cfg_attr(test, feature(test))]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::cognitive_complexity)]
#![feature(track_caller)]

mod config;
mod exit_dialogue;
mod input_area;
mod messaging;
#[doc(hidden)]
// FIXME: This is "pub" to be able to use in an example
pub mod msg_area;
mod notifier;
mod statusline;
mod tab;
mod termbox;
mod trie;
mod tui;
mod utils;
mod widget;

#[cfg(test)]
mod tests;

pub use crate::tab::TabStyle;
use crate::tui::TUIRet;
pub use libtiny_ui::*;

use futures::select;
use futures::stream::StreamExt;
use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::{Rc, Weak};
use term_input::Input;
use time::Tm;
use tokio::signal::unix::{signal, SignalKind};
use tokio::sync::mpsc;
use tokio::task::spawn_local;

#[macro_use]
extern crate log;

#[derive(Clone)]
pub struct TUI {
    inner: Weak<RefCell<tui::TUI>>,
}

impl TUI {
    pub fn run(config_path: PathBuf) -> (TUI, mpsc::Receiver<Event>) {
        let tui = Rc::new(RefCell::new(tui::TUI::new(config_path)));
        let inner = Rc::downgrade(&tui);

        let (snd_ev, rcv_ev) = mpsc::channel(10);

        // For SIGWINCH handler
        let (snd_abort, rcv_abort) = mpsc::channel::<()>(1);

        // Spawn SIGWINCH handler
        spawn_local(sigwinch_handler(inner.clone(), rcv_abort));

        // Spawn input handler task
        spawn_local(input_handler(tui, snd_ev, snd_abort));

        (TUI { inner }, rcv_ev)
    }
}

async fn sigwinch_handler(tui: Weak<RefCell<tui::TUI>>, rcv_abort: mpsc::Receiver<()>) {
    let stream = match signal(SignalKind::window_change()) {
        Err(err) => {
            debug!("Can't install SIGWINCH handler: {:?}", err);
            return;
        }
        Ok(stream) => stream,
    };

    let mut stream_fused = stream.fuse();
    let mut rcv_abort_fused = rcv_abort.fuse();

    loop {
        select! {
            _ = stream_fused.next() => {
                match tui.upgrade() {
                    None => {
                        return;
                    }
                    Some(tui) => {
                        tui.borrow_mut().resize();
                    }
                }
            },
            _ = rcv_abort_fused.next() => {
                return;
            }
        }
    }
}

async fn input_handler(
    tui: Rc<RefCell<tui::TUI>>,
    mut snd_ev: mpsc::Sender<Event>,
    mut snd_abort: mpsc::Sender<()>,
) {
    let mut input = Input::new();
    while let Some(mb_ev) = input.next().await {
        match mb_ev {
            Err(io_err) => {
                debug!("term_input error: {:?}", io_err);
                return;
            }
            Ok(ev) => {
                let tui_ret = tui.borrow_mut().handle_input_event(ev);
                match tui_ret {
                    TUIRet::Abort => {
                        snd_ev.try_send(Event::Abort).unwrap();
                        let _ = snd_abort.try_send(());
                        return;
                    }
                    TUIRet::KeyHandled | TUIRet::KeyIgnored(_) | TUIRet::EventIgnored(_) => {}
                    TUIRet::Input { msg, from } => {
                        if msg[0] == '/' {
                            // Handle TUI commands, send others to downstream
                            let cmd: String = (&msg[1..]).iter().collect();
                            let handled = tui.borrow_mut().try_handle_cmd(&cmd, &from);
                            if !handled {
                                snd_ev.try_send(Event::Cmd { cmd, source: from }).unwrap();
                            }
                        } else {
                            snd_ev
                                .try_send(Event::Msg {
                                    msg: msg.into_iter().collect(),
                                    source: from,
                                })
                                .unwrap();
                        }
                    }
                    TUIRet::Lines { lines, from } => {
                        snd_ev
                            .try_send(Event::Lines {
                                lines,
                                source: from,
                            })
                            .unwrap();
                    }
                }
            }
        }
        tui.borrow_mut().draw();
    }
}

macro_rules! delegate {
    ( $name:ident ( $( $x:ident: $t:ty, )* ) ) => {
        fn $name(&self, $($x: $t,)*) {
            if let Some(inner) = self.inner.upgrade() {
                inner.borrow_mut().$name( $( $x, )* );
            }
        }
    }
}

impl UI for TUI {
    delegate!(draw());
    delegate!(new_server_tab(serv_name: &str, alias: Option<String>,));
    delegate!(close_server_tab(serv_name: &str,));
    delegate!(new_chan_tab(serv_name: &str, chan: &str,));
    delegate!(close_chan_tab(serv_name: &str, chan: &str,));
    delegate!(close_user_tab(serv_name: &str, nick: &str,));
    delegate!(add_client_msg(msg: &str, target: &MsgTarget,));
    delegate!(add_msg(msg: &str, ts: Tm, target: &MsgTarget,));
    delegate!(add_err_msg(msg: &str, ts: Tm, target: &MsgTarget,));
    delegate!(add_client_err_msg(msg: &str, target: &MsgTarget,));
    delegate!(clear_nicks(serv_name: &str,));
    delegate!(set_nick(serv_name: &str, new_nick: &str,));
    delegate!(add_privmsg(
        sender: &str,
        msg: &str,
        ts: Tm,
        target: &MsgTarget,
        highlight: bool,
        is_action: bool,
    ));
    delegate!(add_nick(nick: &str, ts: Option<Tm>, target: &MsgTarget,));
    delegate!(remove_nick(nick: &str, ts: Option<Tm>, target: &MsgTarget,));
    delegate!(rename_nick(
        old_nick: &str,
        new_nick: &str,
        ts: Tm,
        target: &MsgTarget,
    ));
    delegate!(set_topic(
        topic: &str,
        ts: Tm,
        serv_name: &str,
        chan_name: &str,
    ));
    delegate!(set_tab_style(style: TabStyle, target: &MsgTarget,));

    fn user_tab_exists(&self, serv_name: &str, nick: &str) -> bool {
        match self.inner.upgrade() {
            Some(tui) => tui.borrow().user_tab_exists(serv_name, nick),
            None => false,
        }
    }
}
<|endoftext|>


<commit_before>use std::error::Error;
use std::collections::HashMap;
use emailaddress::EmailAddress;
use iron::Url;
use serde_json::de::from_reader;
use serde_json::value::Value;
use super::error::{BrokerError, BrokerResult};
use super::hyper::client::Client as HttpClient;
use super::hyper::header::ContentType as HyContentType;
use super::hyper::header::Headers;
use super::{Config, create_jwt};
use super::crypto::{session_id, verify_jws};
use super::store_cache::CacheKey;
use time::now_utc;
use url::percent_encoding::{utf8_percent_encode, QUERY_ENCODE_SET};

/// Macro used to extract a typed field from a JSON Value.
///
/// Will return from the caller with a BrokerError if the field is missing or its value is an
/// incompatible type. `descr` is used to format the error message.
///
/// ```
/// let foo = try_get_json_field!(value, "foo", as_str, "example document");
/// ```
macro_rules! try_get_json_field {
    ( $input:expr, $key:tt, $conv:ident, $descr:expr ) => {
        try!($input.find($key).and_then(|v| v.$conv()).ok_or_else(|| {
            BrokerError::Provider(format!("{} missing from {}", $key, $descr))
        }))
    }
}

/// Helper method to issue an OAuth authorization request.
///
/// When an authentication request comes in and matches one of the "famous"
/// identity providers configured in the `Config`, we redirect the client
/// to an Authentication Request URL, which we discover by reading the
/// provider's configured Discovery URL. We pass in the client ID we received
/// when pre-registering for the provider, as well as a callback URL which the
/// user will be redirected back to after confirming (or denying) the
/// Authentication Request. Included in the request is a nonce which we can
/// later use to definitively match the callback to this request.
pub fn request(app: &Config, email_addr: EmailAddress, client_id: &str, nonce: &str, redirect_uri: &Url)
               -> BrokerResult<Url> {

    let session = session_id(&email_addr, client_id);

    // Store the nonce and the RP's `redirect_uri` in Redis for use in the
    // callback handler.
    try!(app.store.store_session(&session, &[
        ("type", "oidc"),
        ("email", &email_addr.to_string()),
        ("client_id", client_id),
        ("nonce", nonce),
        ("redirect", &redirect_uri.to_string()),
    ]));

    let client = HttpClient::new();

    // Retrieve the provider's Discovery document and extract the
    // `authorization_endpoint` from it.
    let domain = &email_addr.domain;
    let provider = &app.providers[domain];
    let config_obj: Value = try!(
        app.store.cache.fetch_json_url(
            &app.store,
            CacheKey::Discovery { domain: &email_addr.domain },
            &client,
            &provider.discovery_url
        ).map_err(|e| {
            BrokerError::Provider(format!("could not fetch {}'s discovery document: {}",
                                          domain, e.description()))
        })
    );
    let descr = format!("{}'s discovery document", domain);
    let authz_base = try_get_json_field!(config_obj, "authorization_endpoint", as_str, descr);

    // Create the URL to redirect to, properly escaping all parameters.
    Url::parse(&vec![
        authz_base,
        "?",
        "client_id=",
        &utf8_percent_encode(&provider.client_id, QUERY_ENCODE_SET).to_string(),
        "&response_type=code",
        "&scope=",
        &utf8_percent_encode("openid email", QUERY_ENCODE_SET).to_string(),
        "&redirect_uri=",
        &utf8_percent_encode(&format!("{}/callback", &app.public_url),
                             QUERY_ENCODE_SET).to_string(),
        "&state=",
        &utf8_percent_encode(&session, QUERY_ENCODE_SET).to_string(),
        "&login_hint=",
        &utf8_percent_encode(&email_addr.to_string(), QUERY_ENCODE_SET).to_string(),
    ].join("")).map_err(|_| {
        BrokerError::Provider(format!("failed to build valid authorization URL from {}'s 'authorization_endpoint'", domain))
    })

}

pub fn canonicalize_google(email: String) -> String {
    let at = email.find('@').unwrap();
    let (user, domain) = email.split_at(at);
    let domain = &domain[1..];
    let user = &user.replace(".", ""); // Ignore dots

    // Trim plus addresses
    let user = match user.find('+') {
        Some(pos) => user.split_at(pos).0,
        None => user,
    };

    // Normalize googlemail.com to gmail.com
    let domain = if domain == "googlemail.com" {
        "gmail.com"
    } else {
        domain
    };
    user.to_string() + "@" + domain
}

pub fn canonicalized(email: &str) -> String {
    let normalized = email.to_lowercase();
    if normalized.ends_with("@gmail.com") || normalized.ends_with("@googlemail.com") {
        canonicalize_google(normalized)
    } else {
        normalized
    }
}

/// Helper method to verify OAuth authentication result.
///
/// Match the returned email address and nonce against our Redis data, then
/// extract the identity token returned by the provider and verify it. Return
/// an identity token for the RP if successful, or an error message otherwise.
pub fn verify(app: &Config, stored: &HashMap<String, String>, code: &str)
              -> BrokerResult<(String, String)> {

    let email_addr = EmailAddress::new(&stored["email"]).unwrap();
    let origin = &stored["client_id"];
    let nonce = &stored["nonce"];

    let client = HttpClient::new();

    // Request the provider's Discovery document to get the
    // `token_endpoint` and `jwks_uri` values from it. TODO: save these
    // when requesting the Discovery document in the `oauth_request()`
    // function, and/or cache them by provider host.
    let domain = &email_addr.domain;
    let provider = &app.providers[domain];
    let config_obj: Value = try!(
        app.store.cache.fetch_json_url(
            &app.store,
            CacheKey::Discovery { domain: domain },
            &client,
            &provider.discovery_url
        ).map_err(|e| {
            BrokerError::Provider(format!("could not fetch {}'s discovery document: {}",
                                          domain, e.description()))
        })
    );
    let descr = format!("{}'s discovery document", domain);
    let token_url = try_get_json_field!(config_obj, "token_endpoint", as_str, descr);
    let jwks_url = try_get_json_field!(config_obj, "jwks_uri", as_str, descr);

    // Create form data for the Token Request, where we exchange the code
    // received in this callback request for an identity token (while
    // proving our identity by passing the client secret value).
    let body: String = vec![
        "code=",
        &utf8_percent_encode(code, QUERY_ENCODE_SET).to_string(),
        "&client_id=",
        &utf8_percent_encode(&provider.client_id, QUERY_ENCODE_SET).to_string(),
        "&client_secret=",
        &utf8_percent_encode(&provider.secret, QUERY_ENCODE_SET).to_string(),
        "&redirect_uri=",
        &utf8_percent_encode(&format!("{}/callback", &app.public_url),
                             QUERY_ENCODE_SET).to_string(),
        "&grant_type=authorization_code",
    ].join("");

    // Send the Token Request and extract the `id_token` from the response.
    let token_obj: Value = {
        let mut headers = Headers::new();
        headers.set(HyContentType::form_url_encoded());
        try!(
            client.post(token_url).headers(headers).body(&body).send()
                .map_err(BrokerError::Http)
                .and_then(|rsp| from_reader(rsp).map_err(|_| {
                    BrokerError::Provider("failed to parse response as JSON".to_string())
                }))
                .map_err(|e| {
                    BrokerError::Provider(format!("{} token request failed: {}",
                                                  domain, e.description()))
                })
        )
    };
    let descr = format!("{}'s token response", domain);
    let id_token = try_get_json_field!(token_obj, "id_token", as_str, descr);

    // Grab the keys from the provider, then verify the signature.
    let jwt_payload = {
        let keys_obj: Value = try!(
            app.store.cache.fetch_json_url(
                &app.store,
                CacheKey::KeySet { domain: domain },
                &client,
                &jwks_url
            ).map_err(|e| {
                BrokerError::Provider(format!("could not fetch {}'s keys: {}",
                                              domain, e.description()))
            })
        );
        try!(
            verify_jws(id_token, &keys_obj).map_err(|_| {
                BrokerError::Provider(format!("could not verify the token received from {}", domain))
            })
        )
    };

    // Verify the claims contained in the token.
    let descr = format!("{}'s token payload", domain);
    let iss = try_get_json_field!(jwt_payload, "iss", as_str, descr);
    let aud = try_get_json_field!(jwt_payload, "aud", as_str, descr);
    let token_addr = try_get_json_field!(jwt_payload, "email", as_str, descr);
    let exp = try_get_json_field!(jwt_payload, "exp", as_i64, descr);
    let issuer_origin = vec!["https://", &provider.issuer_domain].join("");
    assert!(iss == provider.issuer_domain || iss == issuer_origin);
    assert!(aud == provider.client_id);
    assert!(canonicalized(token_addr) == canonicalized(&email_addr.to_string()));
    let now = now_utc().to_timespec().sec;
    assert!(now < exp);

    // If everything is okay, build a new identity token and send it
    // to the relying party.
    let id_token = create_jwt(app, &email_addr.to_string(), origin, nonce);
    let redirect = &stored["redirect"];
    Ok((id_token, redirect.to_string()))

}

#[cfg(test)]
mod tests {
    use super::canonicalized;
    #[test]
    fn test_canonicalized_google() {
        assert_eq!(canonicalized("example.foo+bar@example.com"),
                   "example.foo+bar@example.com");
        assert_eq!(canonicalized("example@gmail.com"),
                   "example@gmail.com");
        assert_eq!(canonicalized("example@googlemail.com"),
                   "example@gmail.com");
        assert_eq!(canonicalized("example.foo@gmail.com"),
                   "examplefoo@gmail.com");
        assert_eq!(canonicalized("example+bar@gmail.com"),
                   "example@gmail.com");
        assert_eq!(canonicalized("example.foo+bar@googlemail.com"),
                   "examplefoo@gmail.com");
    }
}
<commit_msg>Require consent for oidc flow.<commit_after>use std::error::Error;
use std::collections::HashMap;
use emailaddress::EmailAddress;
use iron::Url;
use serde_json::de::from_reader;
use serde_json::value::Value;
use super::error::{BrokerError, BrokerResult};
use super::hyper::client::Client as HttpClient;
use super::hyper::header::ContentType as HyContentType;
use super::hyper::header::Headers;
use super::{Config, create_jwt};
use super::crypto::{session_id, verify_jws};
use super::store_cache::CacheKey;
use time::now_utc;
use url::percent_encoding::{utf8_percent_encode, QUERY_ENCODE_SET};

/// Macro used to extract a typed field from a JSON Value.
///
/// Will return from the caller with a BrokerError if the field is missing or its value is an
/// incompatible type. `descr` is used to format the error message.
///
/// ```
/// let foo = try_get_json_field!(value, "foo", as_str, "example document");
/// ```
macro_rules! try_get_json_field {
    ( $input:expr, $key:tt, $conv:ident, $descr:expr ) => {
        try!($input.find($key).and_then(|v| v.$conv()).ok_or_else(|| {
            BrokerError::Provider(format!("{} missing from {}", $key, $descr))
        }))
    }
}

/// Helper method to issue an OAuth authorization request.
///
/// When an authentication request comes in and matches one of the "famous"
/// identity providers configured in the `Config`, we redirect the client
/// to an Authentication Request URL, which we discover by reading the
/// provider's configured Discovery URL. We pass in the client ID we received
/// when pre-registering for the provider, as well as a callback URL which the
/// user will be redirected back to after confirming (or denying) the
/// Authentication Request. Included in the request is a nonce which we can
/// later use to definitively match the callback to this request.
pub fn request(app: &Config, email_addr: EmailAddress, client_id: &str, nonce: &str, redirect_uri: &Url)
               -> BrokerResult<Url> {

    let session = session_id(&email_addr, client_id);

    // Store the nonce and the RP's `redirect_uri` in Redis for use in the
    // callback handler.
    try!(app.store.store_session(&session, &[
        ("type", "oidc"),
        ("email", &email_addr.to_string()),
        ("client_id", client_id),
        ("nonce", nonce),
        ("redirect", &redirect_uri.to_string()),
    ]));

    let client = HttpClient::new();

    // Retrieve the provider's Discovery document and extract the
    // `authorization_endpoint` from it.
    let domain = &email_addr.domain;
    let provider = &app.providers[domain];
    let config_obj: Value = try!(
        app.store.cache.fetch_json_url(
            &app.store,
            CacheKey::Discovery { domain: &email_addr.domain },
            &client,
            &provider.discovery_url
        ).map_err(|e| {
            BrokerError::Provider(format!("could not fetch {}'s discovery document: {}",
                                          domain, e.description()))
        })
    );
    let descr = format!("{}'s discovery document", domain);
    let authz_base = try_get_json_field!(config_obj, "authorization_endpoint", as_str, descr);

    // Create the URL to redirect to, properly escaping all parameters.
    Url::parse(&vec![
        authz_base,
        "?",
        "client_id=",
        &utf8_percent_encode(&provider.client_id, QUERY_ENCODE_SET).to_string(),
        "&response_type=code",
        "&scope=",
        &utf8_percent_encode("openid email", QUERY_ENCODE_SET).to_string(),
        "&redirect_uri=",
        &utf8_percent_encode(&format!("{}/callback", &app.public_url),
                             QUERY_ENCODE_SET).to_string(),
        "&state=",
        &utf8_percent_encode(&session, QUERY_ENCODE_SET).to_string(),
        "&login_hint=",
        &utf8_percent_encode(&email_addr.to_string(), QUERY_ENCODE_SET).to_string(),
        "&prompt=consent",
    ].join("")).map_err(|_| {
        BrokerError::Provider(format!("failed to build valid authorization URL from {}'s 'authorization_endpoint'", domain))
    })

}

pub fn canonicalize_google(email: String) -> String {
    let at = email.find('@').unwrap();
    let (user, domain) = email.split_at(at);
    let domain = &domain[1..];
    let user = &user.replace(".", ""); // Ignore dots

    // Trim plus addresses
    let user = match user.find('+') {
        Some(pos) => user.split_at(pos).0,
        None => user,
    };

    // Normalize googlemail.com to gmail.com
    let domain = if domain == "googlemail.com" {
        "gmail.com"
    } else {
        domain
    };
    user.to_string() + "@" + domain
}

pub fn canonicalized(email: &str) -> String {
    let normalized = email.to_lowercase();
    if normalized.ends_with("@gmail.com") || normalized.ends_with("@googlemail.com") {
        canonicalize_google(normalized)
    } else {
        normalized
    }
}

/// Helper method to verify OAuth authentication result.
///
/// Match the returned email address and nonce against our Redis data, then
/// extract the identity token returned by the provider and verify it. Return
/// an identity token for the RP if successful, or an error message otherwise.
pub fn verify(app: &Config, stored: &HashMap<String, String>, code: &str)
              -> BrokerResult<(String, String)> {

    let email_addr = EmailAddress::new(&stored["email"]).unwrap();
    let origin = &stored["client_id"];
    let nonce = &stored["nonce"];

    let client = HttpClient::new();

    // Request the provider's Discovery document to get the
    // `token_endpoint` and `jwks_uri` values from it. TODO: save these
    // when requesting the Discovery document in the `oauth_request()`
    // function, and/or cache them by provider host.
    let domain = &email_addr.domain;
    let provider = &app.providers[domain];
    let config_obj: Value = try!(
        app.store.cache.fetch_json_url(
            &app.store,
            CacheKey::Discovery { domain: domain },
            &client,
            &provider.discovery_url
        ).map_err(|e| {
            BrokerError::Provider(format!("could not fetch {}'s discovery document: {}",
                                          domain, e.description()))
        })
    );
    let descr = format!("{}'s discovery document", domain);
    let token_url = try_get_json_field!(config_obj, "token_endpoint", as_str, descr);
    let jwks_url = try_get_json_field!(config_obj, "jwks_uri", as_str, descr);

    // Create form data for the Token Request, where we exchange the code
    // received in this callback request for an identity token (while
    // proving our identity by passing the client secret value).
    let body: String = vec![
        "code=",
        &utf8_percent_encode(code, QUERY_ENCODE_SET).to_string(),
        "&client_id=",
        &utf8_percent_encode(&provider.client_id, QUERY_ENCODE_SET).to_string(),
        "&client_secret=",
        &utf8_percent_encode(&provider.secret, QUERY_ENCODE_SET).to_string(),
        "&redirect_uri=",
        &utf8_percent_encode(&format!("{}/callback", &app.public_url),
                             QUERY_ENCODE_SET).to_string(),
        "&grant_type=authorization_code",
    ].join("");

    // Send the Token Request and extract the `id_token` from the response.
    let token_obj: Value = {
        let mut headers = Headers::new();
        headers.set(HyContentType::form_url_encoded());
        try!(
            client.post(token_url).headers(headers).body(&body).send()
                .map_err(BrokerError::Http)
                .and_then(|rsp| from_reader(rsp).map_err(|_| {
                    BrokerError::Provider("failed to parse response as JSON".to_string())
                }))
                .map_err(|e| {
                    BrokerError::Provider(format!("{} token request failed: {}",
                                                  domain, e.description()))
                })
        )
    };
    let descr = format!("{}'s token response", domain);
    let id_token = try_get_json_field!(token_obj, "id_token", as_str, descr);

    // Grab the keys from the provider, then verify the signature.
    let jwt_payload = {
        let keys_obj: Value = try!(
            app.store.cache.fetch_json_url(
                &app.store,
                CacheKey::KeySet { domain: domain },
                &client,
                &jwks_url
            ).map_err(|e| {
                BrokerError::Provider(format!("could not fetch {}'s keys: {}",
                                              domain, e.description()))
            })
        );
        try!(
            verify_jws(id_token, &keys_obj).map_err(|_| {
                BrokerError::Provider(format!("could not verify the token received from {}", domain))
            })
        )
    };

    // Verify the claims contained in the token.
    let descr = format!("{}'s token payload", domain);
    let iss = try_get_json_field!(jwt_payload, "iss", as_str, descr);
    let aud = try_get_json_field!(jwt_payload, "aud", as_str, descr);
    let token_addr = try_get_json_field!(jwt_payload, "email", as_str, descr);
    let exp = try_get_json_field!(jwt_payload, "exp", as_i64, descr);
    let issuer_origin = vec!["https://", &provider.issuer_domain].join("");
    assert!(iss == provider.issuer_domain || iss == issuer_origin);
    assert!(aud == provider.client_id);
    assert!(canonicalized(token_addr) == canonicalized(&email_addr.to_string()));
    let now = now_utc().to_timespec().sec;
    assert!(now < exp);

    // If everything is okay, build a new identity token and send it
    // to the relying party.
    let id_token = create_jwt(app, &email_addr.to_string(), origin, nonce);
    let redirect = &stored["redirect"];
    Ok((id_token, redirect.to_string()))

}

#[cfg(test)]
mod tests {
    use super::canonicalized;
    #[test]
    fn test_canonicalized_google() {
        assert_eq!(canonicalized("example.foo+bar@example.com"),
                   "example.foo+bar@example.com");
        assert_eq!(canonicalized("example@gmail.com"),
                   "example@gmail.com");
        assert_eq!(canonicalized("example@googlemail.com"),
                   "example@gmail.com");
        assert_eq!(canonicalized("example.foo@gmail.com"),
                   "examplefoo@gmail.com");
        assert_eq!(canonicalized("example+bar@gmail.com"),
                   "example@gmail.com");
        assert_eq!(canonicalized("example.foo+bar@googlemail.com"),
                   "examplefoo@gmail.com");
    }
}
<|endoftext|>


<commit_before>
use std::ops::{Index, IndexMut};
use std::str::FromStr;

use value::Value;
use error::{NcclError, ErrorKind};

// top level key that contains everything is __top_level__
#[derive(Clone, Debug, PartialEq)]
pub struct Pair {
    pub key: String,
    pub value: Vec<Pair>
}

impl Pair {
    pub fn new(key: &str) -> Self {
        Pair {
            key: key.to_owned(),
            value: vec![]
        }
    }

    pub fn add(&mut self, value: &str) {
        self.value.push(Pair::new(value));
    }

    pub fn has_key(&self, key: &str) -> bool {
        for item in self.value.iter() {
            if &item.key == key {
                return true;
            }
        }

        return false;
    }

    pub fn get(&mut self, value: &str) -> Result<&mut Pair, NcclError> {
        let value_owned = value.to_owned();

        if self.value.is_empty() {
            return Ok(self);
        } else {
            for item in self.value.iter_mut() {
                if item.key == value_owned {
                    return Ok(item);
                }
            }
        }

        Err(NcclError::new(ErrorKind::KeyNotFound, "Could not find key", 0))
    }

    pub fn get_ref(&self, value: &str) -> Result<&Pair, NcclError> {
        let value_owned = value.to_owned();

        if self.value.is_empty() {
            return Ok(self);
        } else {
            for item in self.value.iter() {
                if item.key == value_owned {
                    return Ok(item);
                }
            }
        }

        Err(NcclError::new(ErrorKind::KeyNotFound, "Cound not find key", 0))
    }

    pub fn value(&self) -> Result<Value, NcclError>  {
        if self.value.len() == 1 {
            Ok(Value::String(self.value[0].key.clone()))
        } else if self.value.len() > 1 {
            Ok(Value::Vec(self.keys()))
        } else {
            Err(NcclError::new(ErrorKind::NoValue, "Key does not have value", 0))
        }
    }

    pub fn value_as<T>(&self) -> Result<T, NcclError> where T: FromStr {
        match self.value() {
            Ok(value) => match value {
                Value::String(s) => match s.parse::<T>() {
                    Ok(ok) => Ok(ok),
                    Err(_) => Err(NcclError::new(ErrorKind::ParseError, "Could not parse key", 0))
                },
                Value::Vec(_) => Err(NcclError::new(ErrorKind::ParseError, "Cannot parse non-terminal key", 0))
            },
            Err(err) => Err(err)
        }
    }

    pub fn keys(&self) -> Vec<String> {
        self.value.clone().into_iter().map(|x| x.key).collect()
    }

    pub fn keys_as<T: FromStr>(&self) -> Result<Vec<T>, NcclError> {
        // XXX this is gross
        match self.keys().iter().map(|s| s.parse::<T>()).collect::<Result<Vec<T>, _>>() {
            Ok(ok) => Ok(ok),
            Err(_) => Err(NcclError::new(ErrorKind::ParseError, "Could not parse keys", 0))
        }
    }
}

impl<'a> Index<&'a str> for Pair {
    type Output = Pair;
    fn index(&self, i: &str) -> &Pair {
        self.get_ref(i).unwrap()
    }
}

impl<'a> IndexMut<&'a str> for Pair {
    fn index_mut(&mut self, i: &str) -> &mut Pair {
        self.get(i).unwrap()
    }
}

<commit_msg>return err if pair has no value on get<commit_after>
use std::ops::{Index, IndexMut};
use std::str::FromStr;

use value::Value;
use error::{NcclError, ErrorKind};

// top level key that contains everything is __top_level__
#[derive(Clone, Debug, PartialEq)]
pub struct Pair {
    pub key: String,
    pub value: Vec<Pair>
}

impl Pair {
    pub fn new(key: &str) -> Self {
        Pair {
            key: key.to_owned(),
            value: vec![]
        }
    }

    pub fn add(&mut self, value: &str) {
        self.value.push(Pair::new(value));
    }

    pub fn has_key(&self, key: &str) -> bool {
        for item in self.value.iter() {
            if &item.key == key {
                return true;
            }
        }

        return false;
    }

    pub fn get(&mut self, value: &str) -> Result<&mut Pair, NcclError> {
        let value_owned = value.to_owned();

        if self.value.is_empty() {
            return Err(NcclError::new(ErrorKind::KeyNotFound, &format!("Pair does not have key: {}", value), 0));
        } else {
            for item in self.value.iter_mut() {
                if item.key == value_owned {
                    return Ok(item);
                }
            }
        }

        Err(NcclError::new(ErrorKind::KeyNotFound, &format!("Could not find key: {}", value), 0))
    }

    pub fn get_ref(&self, value: &str) -> Result<&Pair, NcclError> {
        let value_owned = value.to_owned();

        if self.value.is_empty() {
            return Ok(self);
        } else {
            for item in self.value.iter() {
                if item.key == value_owned {
                    return Ok(item);
                }
            }
        }

        Err(NcclError::new(ErrorKind::KeyNotFound, "Cound not find key", 0))
    }

    pub fn value(&self) -> Result<Value, NcclError>  {
        if self.value.len() == 1 {
            Ok(Value::String(self.value[0].key.clone()))
        } else if self.value.len() > 1 {
            Ok(Value::Vec(self.keys()))
        } else {
            Err(NcclError::new(ErrorKind::NoValue, "Key does not have value", 0))
        }
    }

    pub fn value_as<T>(&self) -> Result<T, NcclError> where T: FromStr {
        match self.value() {
            Ok(value) => match value {
                Value::String(s) => match s.parse::<T>() {
                    Ok(ok) => Ok(ok),
                    Err(_) => Err(NcclError::new(ErrorKind::ParseError, "Could not parse key", 0))
                },
                Value::Vec(_) => Err(NcclError::new(ErrorKind::ParseError, "Cannot parse non-terminal key", 0))
            },
            Err(err) => Err(err)
        }
    }

    pub fn keys(&self) -> Vec<String> {
        self.value.clone().into_iter().map(|x| x.key).collect()
    }

    pub fn keys_as<T: FromStr>(&self) -> Result<Vec<T>, NcclError> {
        // XXX this is gross
        match self.keys().iter().map(|s| s.parse::<T>()).collect::<Result<Vec<T>, _>>() {
            Ok(ok) => Ok(ok),
            Err(_) => Err(NcclError::new(ErrorKind::ParseError, "Could not parse keys", 0))
        }
    }
}

impl<'a> Index<&'a str> for Pair {
    type Output = Pair;
    fn index(&self, i: &str) -> &Pair {
        self.get_ref(i).unwrap()
    }
}

impl<'a> IndexMut<&'a str> for Pair {
    fn index_mut(&mut self, i: &str) -> &mut Pair {
        self.get(i).unwrap()
    }
}

<|endoftext|>


<commit_before>//! Contains the `Snes` struct, which wields the combined power of this project.

use apu::Apu;
use cpu::Cpu;
use dma::{do_dma, do_hdma, DmaChannel};
use frontend::Renderer;
use input::Input;
use log_util::LogOnPanic;
use ppu::Ppu;
use rom::Rom;

const WRAM_SIZE: usize = 128 * 1024;
byte_array!(Wram[WRAM_SIZE]);

/// Contains everything connected to the CPU via one of the two address buses. All memory accesses
/// will be directed through this (the CPU already takes access time into account).
pub struct Peripherals {
    apu: Apu,
    ppu: Ppu,
    rom: Rom,
    /// The 128 KB of working RAM of the SNES (separate from cartridge RAM)
    wram: Wram,
    input: Input,

    pub dma: [DmaChannel; 8],
    /// `$420c` - HDMAEN: HDMA enable flags
    /// (Note that general DMA doesn't have a register here, since all transactions are started
    /// immediately and the register can't be read)
    hdmaen: u8,
    /// `$4200` - NMITIMEN: Interrupt enable flags
    /// `n-xy---a`
    /// * `n`: Enable NMI on V-Blank
    /// * `x`: Enable IRQ on H-Counter match
    /// * `y`: Enable IRQ on V-Counter match
    /// * `a`: Enable auto-joypad read
    nmien: u8,
    /// `$4204`/`$4205` - WRDIVH/WRDIVL: Dividend C
    wrdiv: u16,
    /// `$4206` - WRDIVB: Divisor B
    wrdivb: u8,
    /// `$4207`/`$4208` - HTIMEL/HTIMEH: H Timer (9-bit value)
    htime: u16,
    /// `$4209`/`$420a` - VTIMEL/VTIMEH: V Timer (9-bit value)
    vtime: u16,
    /// `$4210` NMI flag and 5A22 Version
    /// `n---vvvv`
    /// * `n`: `self.nmi`
    /// * `v`: Version
    nmi: bool,
    /// `$4211` TIMEUP - IRQ flag
    /// `i-------`
    /// * `i`: IRQ flag (cleared on read)
    irq: bool,

    /// Additional cycles spent doing IO (in master clock cycles). This is reset before each CPU
    /// instruction and added to the cycle count returned by the CPU.
    cy: u32,
}

impl Peripherals {
    pub fn new(rom: Rom) -> Peripherals {
        Peripherals {
            rom: rom,
            apu: Apu::new(),
            ppu: Ppu::new(),
            wram: Wram::default(),
            input: Input::default(),
            dma: [DmaChannel::new(); 8],
            hdmaen: 0x00,
            nmien: 0x00,
            wrdiv: 0xffff,
            wrdivb: 0,
            htime: 0x1ff,
            vtime: 0x1ff,
            nmi: false,
            irq: false,
            cy: 0,
        }
    }

    pub fn load(&mut self, bank: u8, addr: u16) -> u8 {
        match bank {
            0x00 ... 0x3f | 0x80 ... 0xbf => match addr {
                // Mirror of first 8k of WRAM
                0x0000 ... 0x1fff => self.wram[addr as usize],
                // PPU
                0x2100 ... 0x2133 => panic!("read from write-only PPU register ${:04X}", addr),
                0x2138 ... 0x213f => self.ppu.load(addr),
                // APU IO registers
                0x2140 ... 0x217f => self.apu.read_port((addr & 0b11) as u8),
                0x4016 | 0x4017 => self.input.load(addr),
                0x4210 => {
                    const CPU_VERSION: u8 = 2;  // FIXME Is 2 okay in all cases? Does anyone care?
                    let nmi = if self.nmi { 0x80 } else { 0 };
                    self.nmi = false;   // Cleared on read
                    nmi | CPU_VERSION
                }
                0x4211 => {
                    let val = if self.irq { 0x80 } else { 0 };
                    self.irq = false;
                    val
                }
                0x4218 ... 0x421f => self.input.load(addr),
                // DMA channels (0x43xr, where x is the channel and r is the channel register)
                0x4300 ... 0x43ff => self.dma[(addr as usize & 0x00f0) >> 4].load(addr as u8 & 0xf),
                0x8000 ... 0xffff => self.rom.loadb(bank, addr),
                _ => panic!("invalid/unimplemented load from ${:02X}:{:04X}", bank, addr)
            },
            // WRAM banks. The first 8k are mapped into the start of all banks.
            0x7e | 0x7f => self.wram[(bank as usize - 0x7e) * 65536 + addr as usize],
            0x40 ... 0x7d | 0xc0 ... 0xff => self.rom.loadb(bank, addr),
            _ => unreachable!(),    // Rust should know this!
        }
    }

    pub fn store(&mut self, bank: u8, addr: u16, value: u8) {
        match bank {
            0x00 ... 0x3f | 0x80 ... 0xbf => match addr {
                0x0000 ... 0x1fff => self.wram[addr as usize] = value,
                // PPU registers. Let it deal with the access.
                0x2100 ... 0x2133 => self.ppu.store(addr, value),
                0x2138 ... 0x213f => panic!("store to read-only PPU register ${:04X}", addr),
                // APU IO registers.
                0x2140 ... 0x217f => self.apu.store_port((addr & 0b11) as u8, value),
                0x2180 ... 0x2183 => panic!("NYI: WRAM registers"),
                0x4016 => self.input.store(addr, value),
                0x4200 => {
                    // NMITIMEN - NMI/IRQ enable
                    // E-HV---J
                    // E: Enable NMI
                    // H: Enable IRQ on H-Counter
                    // V: Enable IRQ on V-Counter
                    // J: Enable Auto-Joypad-Read

                    // Check useless bits
                    if value & 0x4e != 0 { panic!("Invalid value for NMIEN: ${:02X}", value) }
                    self.nmien = value;
                }
                0x4204 => self.wrdiv = (self.wrdiv & 0xff00) | value as u16,
                0x4205 => self.wrdiv = ((value as u16) << 8) | (self.wrdiv & 0xff),
                0x4206 => self.wrdivb = value,
                0x4207 => self.htime = (self.htime & 0xff00) | value as u16,
                0x4208 => {
                    assert!(value & 0x01 == value, "invalid value for $4207: ${:02X}", value);
                    self.htime = ((value as u16) << 8) | (self.htime & 0xff);
                }
                0x4209 => self.vtime = (self.vtime & 0xff00) | value as u16,
                0x420a => {
                    assert!(value & 0x01 == value, "invalid value for $4209: ${:02X}", value);
                    self.vtime = ((value as u16) << 8) | (self.vtime & 0xff);
                }
                // MDMAEN - Party enable
                0x420b => self.cy += do_dma(self, value),
                // HDMAEN - HDMA enable
                0x420c => self.hdmaen = value,
                // DMA channels (0x43xr, where x is the channel and r is the channel register)
                0x4300 ... 0x43ff => {
                    self.dma[(addr as usize & 0x00f0) >> 4].store(addr as u8 & 0xf, value);
                }
                0x8000 ... 0xffff => self.rom.storeb(bank, addr, value),
                _ => panic!("invalid store: ${:02X} to ${:02X}:{:04X}", value, bank, addr)
            },
            // WRAM main banks
            0x7e | 0x7f => self.wram[(bank as usize - 0x7e) * 65536 + addr as usize] = value,
            0x40 ... 0x7d | 0xc0 ... 0xff => self.rom.storeb(bank, addr, value),
            _ => unreachable!(),    // Rust should know this!
        }
    }

    fn nmi_enabled(&self) -> bool { self.nmien & 0x80 != 0 }
    fn v_irq_enabled(&self) -> bool { self.nmien & 0x10 != 0 }
    fn h_irq_enabled(&self) -> bool { self.nmien & 0x20 != 0 }
}

pub struct Snes {
    cpu: Cpu,
    renderer: Box<Renderer>,
}

impl Snes {
    pub fn new(rom: Rom, renderer: Box<Renderer>) -> Snes {
        Snes {
            cpu: Cpu::new(Peripherals::new(rom)),
            renderer: renderer,
        }
    }

    pub fn run(&mut self) {
        /// Start tracing at this master cycle (0 to trace everything)
        const TRACE_START: u64 = !0;

        /// Approximated APU clock divider. It's actually somewhere around 20.9..., which is why we
        /// can't directly use `MASTER_CLOCK_FREQ / APU_CLOCK_FREQ` (it would round down, which
        /// might not be critical, but better safe than sorry).
        const APU_DIVIDER: i32 = 21;

        // Master cycle counter, used only for debugging atm
        let mut master_cy: u64 = 0;
        // Master clock cycles for the APU not yet accounted for (can be negative)
        let mut apu_master_cy_debt = 0;
        let mut ppu_master_cy_debt = 0;
        let working_cy = LogOnPanic::new(0);

        loop {
            if master_cy >= TRACE_START {
                self.cpu.trace = true;
                self.cpu.mem.apu.trace = true;
            }

            // Run a CPU instruction and calculate the master cycles elapsed
            self.cpu.mem.cy = 0;
            let cpu_master_cy = self.cpu.dispatch() as i32 + self.cpu.mem.cy as i32;
            master_cy += cpu_master_cy as u64;

            // Now we "owe" the other components a few cycles:
            apu_master_cy_debt += cpu_master_cy;
            ppu_master_cy_debt += cpu_master_cy;

            // Run all components until we no longer owe them:
            while apu_master_cy_debt > APU_DIVIDER {
                // (Since the APU uses lots of cycles to do stuff - lower clock rate and such - we
                // only run it if we owe it `APU_DIVIDER` master cycles - or one SPC700 cycle)
                let apu_master_cy = self.cpu.mem.apu.dispatch() as i32 * APU_DIVIDER;
                apu_master_cy_debt -= apu_master_cy;
            }
            while ppu_master_cy_debt > 0 {
                let (cy, result) = self.cpu.mem.ppu.update();
                ppu_master_cy_debt -= cy as i32;

                if result.hblank {
                    // FIXME Cycles
                    do_hdma(self.cpu.mem.hdmaen);
                }
                if result.last_pixel {
                    self.renderer.render(&*self.cpu.mem.ppu.framebuf);
                }
                if result.vblank {
                    self.cpu.mem.input.new_frame();
                    if self.cpu.mem.nmi_enabled() {
                        //trace!("V-Blank NMI triggered! Trace started!");
                        //self.cpu.trace = true;
                        self.cpu.mem.nmi = true;
                        self.cpu.trigger_nmi();
                        // XXX Break to handle the NMI immediately. Let's hope we don't owe the PPU
                        // too many cycles.
                        break;
                    }
                }
                if result.new_frame {
                    self.cpu.mem.nmi = false;   // Flag cleared on read or end of VBlank
                }

                let cpu = &mut self.cpu;
                if cpu.mem.ppu.v_counter() == cpu.mem.vtime && cpu.mem.v_irq_enabled() {
                    cpu.mem.irq = true;
                    cpu.trigger_irq();
                    break;
                }
                if cpu.mem.ppu.h_counter() == cpu.mem.htime && cpu.mem.h_irq_enabled() {
                    cpu.mem.irq = true;
                    cpu.trigger_irq();
                    break;
                }
            }

            working_cy.set(master_cy);
        }
    }
}
<commit_msg>Implement division and multiplication registers<commit_after>//! Contains the `Snes` struct, which wields the combined power of this project.

use apu::Apu;
use cpu::Cpu;
use dma::{do_dma, do_hdma, DmaChannel};
use frontend::Renderer;
use input::Input;
use log_util::LogOnPanic;
use ppu::Ppu;
use rom::Rom;

const WRAM_SIZE: usize = 128 * 1024;
byte_array!(Wram[WRAM_SIZE]);

/// Contains everything connected to the CPU via one of the two address buses. All memory accesses
/// will be directed through this (the CPU already takes access time into account).
pub struct Peripherals {
    apu: Apu,
    ppu: Ppu,
    rom: Rom,
    /// The 128 KB of working RAM of the SNES (separate from cartridge RAM)
    wram: Wram,
    input: Input,

    pub dma: [DmaChannel; 8],
    /// `$420c` - HDMAEN: HDMA enable flags
    /// (Note that general DMA doesn't have a register here, since all transactions are started
    /// immediately and the register can't be read)
    hdmaen: u8,
    /// `$4200` - NMITIMEN: Interrupt enable flags
    /// `n-xy---a`
    /// * `n`: Enable NMI on V-Blank
    /// * `x`: Enable IRQ on H-Counter match
    /// * `y`: Enable IRQ on V-Counter match
    /// * `a`: Enable auto-joypad read
    nmien: u8,
    /// `$4202` - WRMPYA: Multiplicand 1
    wrmpya: u8,
    /// `$4204`/`$4205` - WRDIVH/WRDIVL: Dividend
    wrdiv: u16,
    /// `$4216`/`$4217` - RDDIVH/RDDIVL: Unsigned Division Result (Quotient)
    rddiv: u16,
    /// `$4216`/`$4217` - RDMPYH/RDMPYL: Unsigned Division Remainder / Multiply Product
    rdmpy: u16,
    /// `$4207`/`$4208` - HTIMEL/HTIMEH: H Timer (9-bit value)
    htime: u16,
    /// `$4209`/`$420a` - VTIMEL/VTIMEH: V Timer (9-bit value)
    vtime: u16,
    /// `$4210` NMI flag and 5A22 Version
    /// `n---vvvv`
    /// * `n`: `self.nmi`
    /// * `v`: Version
    nmi: bool,
    /// `$4211` TIMEUP - IRQ flag
    /// `i-------`
    /// * `i`: IRQ flag (cleared on read)
    irq: bool,

    /// Additional cycles spent doing IO (in master clock cycles). This is reset before each CPU
    /// instruction and added to the cycle count returned by the CPU.
    cy: u32,
}

impl Peripherals {
    pub fn new(rom: Rom) -> Peripherals {
        Peripherals {
            rom: rom,
            apu: Apu::new(),
            ppu: Ppu::new(),
            wram: Wram::default(),
            input: Input::default(),
            dma: [DmaChannel::new(); 8],
            hdmaen: 0x00,
            nmien: 0x00,
            wrmpya: 0,
            wrdiv: 0xffff,
            rddiv: 0,
            rdmpy: 0,
            htime: 0x1ff,
            vtime: 0x1ff,
            nmi: false,
            irq: false,
            cy: 0,
        }
    }

    pub fn load(&mut self, bank: u8, addr: u16) -> u8 {
        match bank {
            0x00 ... 0x3f | 0x80 ... 0xbf => match addr {
                // Mirror of first 8k of WRAM
                0x0000 ... 0x1fff => self.wram[addr as usize],
                // PPU
                0x2100 ... 0x2133 => panic!("read from write-only PPU register ${:04X}", addr),
                0x2138 ... 0x213f => self.ppu.load(addr),
                // APU IO registers
                0x2140 ... 0x217f => self.apu.read_port((addr & 0b11) as u8),
                0x4016 | 0x4017 => self.input.load(addr),
                0x4210 => {
                    const CPU_VERSION: u8 = 2;  // FIXME Is 2 okay in all cases? Does anyone care?
                    let nmi = if self.nmi { 0x80 } else { 0 };
                    self.nmi = false;   // Cleared on read
                    nmi | CPU_VERSION
                }
                0x4211 => {
                    let val = if self.irq { 0x80 } else { 0 };
                    self.irq = false;
                    val
                }
                // RDDIVL - Unsigned Division Result (Quotient) (lower 8bit)
                0x4214 => self.rddiv as u8,
                // RDDIVH - Unsigned Division Result (Quotient) (upper 8bit)
                0x4215 => (self.rddiv >> 8) as u8,
                // RDMPYL
                0x4216 => self.rdmpy as u8,
                // RDMPYH
                0x4217 => (self.rdmpy >> 8) as u8,
                // Input ports
                0x4218 ... 0x421f => self.input.load(addr),
                // DMA channels (0x43xr, where x is the channel and r is the channel register)
                0x4300 ... 0x43ff => self.dma[(addr as usize & 0x00f0) >> 4].load(addr as u8 & 0xf),
                0x8000 ... 0xffff => self.rom.loadb(bank, addr),
                _ => panic!("invalid/unimplemented load from ${:02X}:{:04X}", bank, addr)
            },
            // WRAM banks. The first 8k are mapped into the start of all banks.
            0x7e | 0x7f => self.wram[(bank as usize - 0x7e) * 65536 + addr as usize],
            0x40 ... 0x7d | 0xc0 ... 0xff => self.rom.loadb(bank, addr),
            _ => unreachable!(),    // Rust should know this!
        }
    }

    pub fn store(&mut self, bank: u8, addr: u16, value: u8) {
        match bank {
            0x00 ... 0x3f | 0x80 ... 0xbf => match addr {
                0x0000 ... 0x1fff => self.wram[addr as usize] = value,
                // PPU registers. Let it deal with the access.
                0x2100 ... 0x2133 => self.ppu.store(addr, value),
                0x2138 ... 0x213f => panic!("store to read-only PPU register ${:04X}", addr),
                // APU IO registers.
                0x2140 ... 0x217f => self.apu.store_port((addr & 0b11) as u8, value),
                0x2180 ... 0x2183 => panic!("NYI: WRAM registers"),
                0x4016 => self.input.store(addr, value),
                0x4200 => {
                    // NMITIMEN - NMI/IRQ enable
                    // E-HV---J
                    // E: Enable NMI
                    // H: Enable IRQ on H-Counter
                    // V: Enable IRQ on V-Counter
                    // J: Enable Auto-Joypad-Read

                    // Check useless bits
                    if value & 0x4e != 0 { panic!("Invalid value for NMIEN: ${:02X}", value) }
                    self.nmien = value;
                }
                0x4202 => self.wrmpya = value,
                // WRMPYB: Performs multiplication on write
                0x4203 => self.rdmpy = self.wrmpya as u16 * value as u16,
                0x4204 => self.wrdiv = (self.wrdiv & 0xff00) | value as u16,
                0x4205 => self.wrdiv = ((value as u16) << 8) | (self.wrdiv & 0xff),
                // WRDIVB: Performs division on write
                0x4206 => {
                    self.rddiv = if value == 0 { 0xffff } else { self.wrdiv / value as u16 };
                    self.rdmpy = if value == 0 { value as u16 } else { self.wrdiv % value as u16 };
                }
                0x4207 => self.htime = (self.htime & 0xff00) | value as u16,
                0x4208 => {
                    assert!(value & 0x01 == value, "invalid value for $4207: ${:02X}", value);
                    self.htime = ((value as u16) << 8) | (self.htime & 0xff);
                }
                0x4209 => self.vtime = (self.vtime & 0xff00) | value as u16,
                0x420a => {
                    assert!(value & 0x01 == value, "invalid value for $4209: ${:02X}", value);
                    self.vtime = ((value as u16) << 8) | (self.vtime & 0xff);
                }
                // MDMAEN - Party enable
                0x420b => self.cy += do_dma(self, value),
                // HDMAEN - HDMA enable
                0x420c => self.hdmaen = value,
                // DMA channels (0x43xr, where x is the channel and r is the channel register)
                0x4300 ... 0x43ff => {
                    self.dma[(addr as usize & 0x00f0) >> 4].store(addr as u8 & 0xf, value);
                }
                0x8000 ... 0xffff => self.rom.storeb(bank, addr, value),
                _ => panic!("invalid store: ${:02X} to ${:02X}:{:04X}", value, bank, addr)
            },
            // WRAM main banks
            0x7e | 0x7f => self.wram[(bank as usize - 0x7e) * 65536 + addr as usize] = value,
            0x40 ... 0x7d | 0xc0 ... 0xff => self.rom.storeb(bank, addr, value),
            _ => unreachable!(),    // Rust should know this!
        }
    }

    fn nmi_enabled(&self) -> bool { self.nmien & 0x80 != 0 }
    fn v_irq_enabled(&self) -> bool { self.nmien & 0x10 != 0 }
    fn h_irq_enabled(&self) -> bool { self.nmien & 0x20 != 0 }
}

pub struct Snes {
    cpu: Cpu,
    renderer: Box<Renderer>,
}

impl Snes {
    pub fn new(rom: Rom, renderer: Box<Renderer>) -> Snes {
        Snes {
            cpu: Cpu::new(Peripherals::new(rom)),
            renderer: renderer,
        }
    }

    pub fn run(&mut self) {
        /// Start tracing at this master cycle (0 to trace everything)
        const TRACE_START: u64 = !0;

        /// Approximated APU clock divider. It's actually somewhere around 20.9..., which is why we
        /// can't directly use `MASTER_CLOCK_FREQ / APU_CLOCK_FREQ` (it would round down, which
        /// might not be critical, but better safe than sorry).
        const APU_DIVIDER: i32 = 21;

        // Master cycle counter, used only for debugging atm
        let mut master_cy: u64 = 0;
        // Master clock cycles for the APU not yet accounted for (can be negative)
        let mut apu_master_cy_debt = 0;
        let mut ppu_master_cy_debt = 0;
        let working_cy = LogOnPanic::new(0);

        loop {
            if master_cy >= TRACE_START {
                self.cpu.trace = true;
                self.cpu.mem.apu.trace = true;
            }

            // Run a CPU instruction and calculate the master cycles elapsed
            self.cpu.mem.cy = 0;
            let cpu_master_cy = self.cpu.dispatch() as i32 + self.cpu.mem.cy as i32;
            master_cy += cpu_master_cy as u64;

            // Now we "owe" the other components a few cycles:
            apu_master_cy_debt += cpu_master_cy;
            ppu_master_cy_debt += cpu_master_cy;

            // Run all components until we no longer owe them:
            while apu_master_cy_debt > APU_DIVIDER {
                // (Since the APU uses lots of cycles to do stuff - lower clock rate and such - we
                // only run it if we owe it `APU_DIVIDER` master cycles - or one SPC700 cycle)
                let apu_master_cy = self.cpu.mem.apu.dispatch() as i32 * APU_DIVIDER;
                apu_master_cy_debt -= apu_master_cy;
            }
            while ppu_master_cy_debt > 0 {
                let (cy, result) = self.cpu.mem.ppu.update();
                ppu_master_cy_debt -= cy as i32;

                if result.hblank {
                    // FIXME Cycles
                    do_hdma(self.cpu.mem.hdmaen);
                }
                if result.last_pixel {
                    self.renderer.render(&*self.cpu.mem.ppu.framebuf);
                }
                if result.vblank {
                    self.cpu.mem.input.new_frame();
                    if self.cpu.mem.nmi_enabled() {
                        //trace!("V-Blank NMI triggered! Trace started!");
                        //self.cpu.trace = true;
                        self.cpu.mem.nmi = true;
                        self.cpu.trigger_nmi();
                        // XXX Break to handle the NMI immediately. Let's hope we don't owe the PPU
                        // too many cycles.
                        break;
                    }
                }
                if result.new_frame {
                    self.cpu.mem.nmi = false;   // Flag cleared on read or end of VBlank
                }

                let cpu = &mut self.cpu;
                if cpu.mem.ppu.v_counter() == cpu.mem.vtime && cpu.mem.v_irq_enabled() {
                    cpu.mem.irq = true;
                    cpu.trigger_irq();
                    break;
                }
                if cpu.mem.ppu.h_counter() == cpu.mem.htime && cpu.mem.h_irq_enabled() {
                    cpu.mem.irq = true;
                    cpu.trigger_irq();
                    break;
                }
            }

            working_cy.set(master_cy);
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

use chrono::prelude::*;
use serde::{Deserialize, Deserializer};
use serde_derive::{Deserialize, Serialize};
use strum_macros::Display;

use crate::aliasing::insert_alias;

/// This enum represents the status of the internal task handling of Pueue.
/// They basically represent the internal task life-cycle.
#[derive(PartialEq, Clone, Debug, Display, Serialize, Deserialize)]
pub enum TaskStatus {
    /// The task is queued and waiting for a free slot
    Queued,
    /// The task has been manually stashed. It won't be executed until it's manually enqueued
    #[serde(deserialize_with = "enqueue_at_or_default")]
    Stashed { enqueue_at: Option<DateTime<Local>> },
    /// The task is started and running
    Running,
    /// A previously running task has been paused
    Paused,
    /// Task finished. The actual result of the task is handled by the [TaskResult] enum.
    Done(TaskResult),
    /// Used while the command of a task is edited (to prevent starting the task)
    Locked,
}

fn enqueue_at_or_default<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
    T: Deserialize<'de> + Default,
    D: Deserializer<'de>,
{
    let v: Value = Deserialize::deserialize(deserializer)?;
    Ok(T::deserialize(v).unwrap_or_default())
}

/// This enum represents the exit status of an actually spawned program.
/// It's only used, once a task finished or failed in some kind of way.
#[derive(PartialEq, Clone, Debug, Display, Serialize, Deserialize)]
pub enum TaskResult {
    /// Task exited with 0
    Success,
    /// The task failed in some other kind of way (error code != 0)
    Failed(i32),
    /// The task couldn't be spawned. Probably a typo in the command
    FailedToSpawn(String),
    /// Task has been actively killed by either the user or the daemon on shutdown
    Killed,
    /// Some kind of IO error. This should barely ever happen. Please check the daemon logs.
    Errored,
    /// A dependency of the task failed.
    DependencyFailed,
}

/// Representation of a task.
/// start will be set the second the task starts processing.
/// `result`, `output` and `end` won't be initialized, until the task has finished.
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Task {
    pub id: usize,
    pub original_command: String,
    pub command: String,
    pub path: String,
    pub envs: HashMap<String, String>,
    pub group: String,
    pub dependencies: Vec<usize>,
    pub label: Option<String>,
    pub status: TaskStatus,
    /// This field is only used when editing the path/command of a task.
    /// It's necessary, since we enter the `Locked` state during editing.
    /// However, we have to go back to the previous state after we finished editing.
    pub prev_status: TaskStatus,
    pub start: Option<DateTime<Local>>,
    pub end: Option<DateTime<Local>>,
}

impl Task {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        original_command: String,
        path: String,
        envs: HashMap<String, String>,
        group: String,
        starting_status: TaskStatus,
        dependencies: Vec<usize>,
        label: Option<String>,
    ) -> Task {
        let command = insert_alias(original_command.clone());

        Task {
            id: 0,
            original_command,
            command,
            path,
            envs,
            group,
            dependencies,
            label,
            status: starting_status.clone(),
            prev_status: starting_status,
            start: None,
            end: None,
        }
    }

    /// A convenience function used to duplicate a task.
    pub fn from_task(task: &Task) -> Task {
        Task {
            id: 0,
            original_command: task.original_command.clone(),
            command: task.command.clone(),
            path: task.path.clone(),
            envs: task.envs.clone(),
            group: "default".to_string(),
            dependencies: Vec::new(),
            label: task.label.clone(),
            status: TaskStatus::Queued,
            prev_status: TaskStatus::Queued,
            start: None,
            end: None,
        }
    }

    /// Whether the task is having a running process managed by the TaskHandler
    pub fn is_running(&self) -> bool {
        self.status == TaskStatus::Running || self.status == TaskStatus::Paused
    }

    /// Whether the task's process finished.
    pub fn is_done(&self) -> bool {
        matches!(self.status, TaskStatus::Done(_))
    }

    /// Check if the task errored. \
    /// It either:
    /// 1. Finished successfully
    /// 2. Didn't finish yet.
    pub fn failed(&self) -> bool {
        matches!(self.status, TaskStatus::Done(TaskResult::Success))
            || !matches!(self.status, TaskStatus::Done(_))
    }

    pub fn is_queued(&self) -> bool {
        matches!(self.status, TaskStatus::Queued | TaskStatus::Stashed { .. })
    }

    /// Small convenience function to set the task's group to the default group.
    pub fn set_default_group(&mut self) {
        self.group = String::from("default");
    }

    pub fn is_in_default_group(&self) -> bool {
        self.group.eq("default")
    }
}
<commit_msg>Revert "fix: task::failed() had wrong logic"<commit_after>use std::collections::HashMap;

use chrono::prelude::*;
use serde_derive::{Deserialize, Serialize};
use strum_macros::Display;

use crate::aliasing::insert_alias;

/// This enum represents the status of the internal task handling of Pueue.
/// They basically represent the internal task life-cycle.
#[derive(PartialEq, Clone, Debug, Display, Serialize, Deserialize)]
pub enum TaskStatus {
    /// The task is queued and waiting for a free slot
    Queued,
    /// The task has been manually stashed. It won't be executed until it's manually enqueued
    Stashed { enqueue_at: Option<DateTime<Local>> },
    /// The task is started and running
    Running,
    /// A previously running task has been paused
    Paused,
    /// Task finished. The actual result of the task is handled by the [TaskResult] enum.
    Done(TaskResult),
    /// Used while the command of a task is edited (to prevent starting the task)
    Locked,
}

/// This enum represents the exit status of an actually spawned program.
/// It's only used, once a task finished or failed in some kind of way.
#[derive(PartialEq, Clone, Debug, Display, Serialize, Deserialize)]
pub enum TaskResult {
    /// Task exited with 0
    Success,
    /// The task failed in some other kind of way (error code != 0)
    Failed(i32),
    /// The task couldn't be spawned. Probably a typo in the command
    FailedToSpawn(String),
    /// Task has been actively killed by either the user or the daemon on shutdown
    Killed,
    /// Some kind of IO error. This should barely ever happen. Please check the daemon logs.
    Errored,
    /// A dependency of the task failed.
    DependencyFailed,
}

/// Representation of a task.
/// start will be set the second the task starts processing.
/// `result`, `output` and `end` won't be initialized, until the task has finished.
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub struct Task {
    pub id: usize,
    pub original_command: String,
    pub command: String,
    pub path: String,
    pub envs: HashMap<String, String>,
    pub group: String,
    pub dependencies: Vec<usize>,
    pub label: Option<String>,
    pub status: TaskStatus,
    /// This field is only used when editing the path/command of a task.
    /// It's necessary, since we enter the `Locked` state during editing.
    /// However, we have to go back to the previous state after we finished editing.
    pub prev_status: TaskStatus,
    pub start: Option<DateTime<Local>>,
    pub end: Option<DateTime<Local>>,
}

impl Task {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        original_command: String,
        path: String,
        envs: HashMap<String, String>,
        group: String,
        starting_status: TaskStatus,
        dependencies: Vec<usize>,
        label: Option<String>,
    ) -> Task {
        let command = insert_alias(original_command.clone());

        Task {
            id: 0,
            original_command,
            command,
            path,
            envs,
            group,
            dependencies,
            label,
            status: starting_status.clone(),
            prev_status: starting_status,
            start: None,
            end: None,
        }
    }

    /// A convenience function used to duplicate a task.
    pub fn from_task(task: &Task) -> Task {
        Task {
            id: 0,
            original_command: task.original_command.clone(),
            command: task.command.clone(),
            path: task.path.clone(),
            envs: task.envs.clone(),
            group: "default".to_string(),
            dependencies: Vec::new(),
            label: task.label.clone(),
            status: TaskStatus::Queued,
            prev_status: TaskStatus::Queued,
            start: None,
            end: None,
        }
    }

    /// Whether the task is having a running process managed by the TaskHandler
    pub fn is_running(&self) -> bool {
        self.status == TaskStatus::Running || self.status == TaskStatus::Paused
    }

    /// Whether the task's process finished.
    pub fn is_done(&self) -> bool {
        matches!(self.status, TaskStatus::Done(_))
    }

    /// Check if the task errored. \
    /// It either:
    /// 1. Finished successfully
    /// 2. Didn't finish yet.
    pub fn failed(&self) -> bool {
        matches!(self.status, TaskStatus::Done(TaskResult::Success))
            || !matches!(self.status, TaskStatus::Done(_))
    }

    pub fn is_queued(&self) -> bool {
        matches!(self.status, TaskStatus::Queued | TaskStatus::Stashed { .. })
    }

    /// Small convenience function to set the task's group to the default group.
    pub fn set_default_group(&mut self) {
        self.group = String::from("default");
    }

    pub fn is_in_default_group(&self) -> bool {
        self.group.eq("default")
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::io::prelude::*;
use std::fs::File;

use glob::glob;

use template::Template;
use context::Context;
use errors::TeraResult;

#[derive(Debug)]
pub struct Tera {
    pub templates: HashMap<String, Template>,
}

// Wanted api:
// let mut tera = Tera::new("templates/");
// tera.register_filter(Capitalize);
// ^ the above can panic as it should be run in compile or first time
// ^ it will have run lexer + parser so we only need to render
// ...
// tera.render("dashboard/index.html", &someData) (-> Result<String>)

impl Tera {
    pub fn new(dir: &str) -> Tera {
        // TODO: add tests
        if dir.find('*').is_none() {
            panic!("Tera expects a glob as input, no * were found in {}", dir);
        }

        let mut templates = HashMap::new();

        // We are parsing all the templates on instantiation
        for entry in glob(dir).unwrap().filter_map(|e| e.ok()) {
            let path = entry.as_path();
            // We only care about actual files
            if path.is_file() {
                // We clean the filename by removing the dir given
                // to Tera so users don't have to prefix everytime
                let parent_dir = dir.split_at(dir.find('*').unwrap()).0;
                let filepath = path.to_string_lossy().replace(parent_dir, "");
                // we know the file exists so unwrap all the things
                let mut f = File::open(path).unwrap();
                let mut input = String::new();
                f.read_to_string(&mut input).unwrap();
                templates.insert(filepath.to_owned(), Template::new(&filepath, &input));
            }
        }

        Tera {
            templates: templates
        }
    }

    pub fn render(&self, template_name: &str, data: Context) -> TeraResult<String> {
        let template = self.templates.get(template_name).unwrap(); // TODO error handling

        // TODO: avoid cloning?
        template.render(data, self.templates.clone())
    }

    pub fn get_template(&self, template_name: &str) -> Option<&Template> {
        self.templates.get(template_name)
    }
}
<commit_msg>Fix window bug + error handling on render<commit_after>use std::collections::HashMap;
use std::io::prelude::*;
use std::fs::File;

use glob::glob;

use template::Template;
use context::Context;
use errors::{TeraResult, template_not_found};

#[derive(Debug)]
pub struct Tera {
    pub templates: HashMap<String, Template>,
}

// Wanted api:
// let mut tera = Tera::new("templates/");
// tera.register_filter(Capitalize);
// ^ the above can panic as it should be run in compile or first time
// ^ it will have run lexer + parser so we only need to render
// ...
// tera.render("dashboard/index.html", &someData) (-> Result<String>)

impl Tera {
    pub fn new(dir: &str) -> Tera {
        // TODO: add tests
        if dir.find('*').is_none() {
            panic!("Tera expects a glob as input, no * were found in {}", dir);
        }

        let mut templates = HashMap::new();

        // We are parsing all the templates on instantiation
        for entry in glob(dir).unwrap().filter_map(|e| e.ok()) {
            let path = entry.as_path();
            // We only care about actual files
            if path.is_file() {
                // We clean the filename by removing the dir given
                // to Tera so users don't have to prefix everytime
                let parent_dir = dir.split_at(dir.find('*').unwrap()).0;
                let filepath = path.to_string_lossy()
                    .replace("\\", "/") // change windows slash to forward slash
                    .replace(parent_dir, "");
                // we know the file exists so unwrap all the things
                let mut f = File::open(path).unwrap();
                let mut input = String::new();
                f.read_to_string(&mut input).unwrap();
                templates.insert(filepath.to_owned(), Template::new(&filepath, &input));
            }
        }

        Tera {
            templates: templates
        }
    }

    pub fn render(&self, template_name: &str, data: Context) -> TeraResult<String> {
        let template = match self.templates.get(template_name) {
            Some(tmpl) => tmpl,
            None => { return Err(template_not_found(template_name)); }
        };

        // TODO: avoid cloning?
        template.render(data, self.templates.clone())
    }

    pub fn get_template(&self, template_name: &str) -> Option<&Template> {
        self.templates.get(template_name)
    }
}
<|endoftext|>


<commit_before><commit_msg>added some tests<commit_after>use words::{Words, Word};
use formatting::Formatting;

use tens::Tens;

#[test]
fn test_words()
{
    let words = Words::new(vec![
        Word::Number("one".to_owned()),
        Word::Dash,
        Word::Comma,
        Word::Number("two".to_owned()),
        Word::And,
        Word::Number("three".to_owned())
    ]);

    assert_eq!(words.build(Formatting::all()), "One-, Two and Three");
    assert_eq!(words.build(Formatting::none()), "onetwothree");
}

#[test]
fn test_tens()
{
    let zero = Tens::new(0);
    let nineteen = Tens::new(19);

    let twenty = Tens::new(20);
    let ninetynine = Tens::new(99);

    let all = Formatting::all();
    let none = Formatting::none();

    assert_eq!(zero.build().build(all), "Zero");
    assert_eq!(zero.build().build(none), "zero");

    assert_eq!(nineteen.build().build(all), "Nineteen");
    assert_eq!(nineteen.build().build(none), "nineteen");

    assert_eq!(twenty.build().build(all), "Twenty");
    assert_eq!(twenty.build().build(none), "twenty");

    assert_eq!(ninetynine.build().build(all), "Ninety-Nine");
    assert_eq!(ninetynine.build().build(none), "ninetynine");
}<|endoftext|>