doist 0.4.0

doist is an unofficial command line app for interacting with the Todoist API
Documentation
use std::collections::HashMap;

use crate::{
    api::{
        rest::{Gateway, Project, ProjectID, Section, SectionID},
        tree::{Tree, TreeFlattenExt},
    },
    interactive,
};
use color_eyre::{
    Result,
    eyre::{WrapErr, eyre},
};

pub struct State {
    projects: Vec<Tree<Project>>,
    sections: HashMap<SectionID, Section>,
}

impl State {
    pub async fn fetch_tree(gw: &Gateway) -> Result<State> {
        let (projects, sections) = tokio::try_join!(gw.projects(), gw.sections())?;
        let projects = Tree::from_items(projects).wrap_err("projects do not form a clean tree")?;
        let sections = sections.into_iter().map(|s| (s.id.clone(), s)).collect();
        Ok(State { projects, sections })
    }

    pub fn _select_project(&self) -> Result<Option<&Tree<Project>>> {
        if self.projects.is_empty() {
            return Err(eyre!("no projects were found"));
        }
        let items = self.projects.flat_tree();
        let result = interactive::select(
            "Select project",
            &items.iter().map(|i| &i.item).collect::<Vec<_>>(),
        )?;
        Ok(result.map(|index| items[index]))
    }

    pub fn project(&self, id: &ProjectID) -> Option<&Tree<Project>> {
        self.projects.find(id)
    }

    pub fn sections(&self, id: &ProjectID) -> Vec<&Section> {
        self.sections
            .iter()
            .filter_map(|s| {
                if s.1.project_id == *id {
                    Some(s.1)
                } else {
                    None
                }
            })
            .collect()
    }
}