igor 0.1.2

Generic text-based vendoring
Documentation
use std::mem::swap;
use anyhow::{anyhow, Result};
use ahash::{AHashMap, AHashSet};
use log::debug;
use serde::{Deserialize, Serialize};
use crate::config_model::use_thundercloud_config_data::UseThundercloudConfigData;
use crate::file_system::ConfigFormat;
use crate::path::AbsolutePath;
use super::psychotropic::{NicheTriggers, PsychotropicConfig};

#[derive(Deserialize,Serialize,Debug,Clone)]
#[serde(untagged)]
enum UseThundercloudSpec {
    ProjectPath(String),
    Inline(UseThundercloudConfigData),
}

#[derive(Deserialize,Serialize,Debug,Clone)]
#[serde(rename_all = "kebab-case")]
pub struct NicheCueData {
    name: String,
    use_thundercloud: Option<UseThundercloudSpec>,
    #[serde(default)]
    wait_for: Vec<String>,
}

impl NicheCueData {
    fn name(&self) -> String {
        self.name.clone()
    }

    fn wait_for(&self) -> &[String] {
        &self.wait_for
    }
}

#[derive(Deserialize,Serialize,Debug)]
pub struct PsychotropicConfigData {
    cues: Vec<NicheCueData>
}

#[derive(Debug, Clone)]
pub struct NicheTriggersData {
    niche_cue: NicheCueData,
    triggers: Vec<String>,
}

impl NicheTriggers for NicheTriggersData {
    type UseThundercloudConfigImpl = UseThundercloudConfigData;

    fn name(&self) -> String {
        self.niche_cue.name()
    }

    fn use_thundercloud(&self) -> Option<&Self::UseThundercloudConfigImpl> {
        match &self.niche_cue.use_thundercloud {
            Some(UseThundercloudSpec::Inline(use_thundercloud)) => Some(use_thundercloud),
            _ => None
        }
    }

    fn use_thundercloud_path(&self) -> Option<AbsolutePath> {
        match &self.niche_cue.use_thundercloud {
            Some(UseThundercloudSpec::ProjectPath(path)) => {
                let root = AbsolutePath::root();
                Some(AbsolutePath::new(path, &root))
            }
            _ => None
        }
    }

    fn wait_for(&self) -> &[String] {
        self.niche_cue.wait_for()
    }

    fn triggers(&self) -> &[String] {
        &self.triggers
    }
}

impl NicheTriggersData {
    fn new(niche_cue: NicheCueData) -> Self {
        NicheTriggersData {
            niche_cue,
            triggers: Vec::new(),
        }
    }
}

#[derive(Debug)]
pub struct PsychotropicConfigIndex(AHashMap<String, NicheTriggersData>);

impl PsychotropicConfigIndex {
    pub fn from_str(body: &str, config_format: ConfigFormat) -> Result<Self> {
        let data: PsychotropicConfigData = match config_format {
            ConfigFormat::TOML => toml::from_str(body)?,
            ConfigFormat::YAML => {
                let result = serde_yaml::from_str(body)?;
                #[cfg(test)]
                crate::test_utils::log_toml("Psychotropic Config", &result)?;
                result
            }
        };

        data_to_index(&data)
    }
}

impl PsychotropicConfig for PsychotropicConfigIndex {
    type NicheTriggersImpl = NicheTriggersData;

    fn independent(&self) -> AHashSet<String> {
        let mut candidates = AHashSet::new();
        let mut excludes = AHashSet::new();
        for triggers in self.0.values() {
            let wait_for = triggers.wait_for();
            let niche = triggers.name();
            debug!("Is independent? {:?}: {:?}", &niche, wait_for);
            if wait_for.is_empty() {
                if !excludes.contains(&niche) {
                    candidates.insert(niche);
                }
            } else {
                debug!("Exclude from independent: {:?}", &niche);
                excludes.insert(niche);
                for dep in wait_for {
                    if !excludes.contains(dep) {
                        candidates.insert(dep.to_string());
                    }
                }
            }
        }
        for exclude in excludes.iter() {
            candidates.remove(exclude);
        }
        candidates
    }

    fn get(&self, key: &str) -> Option<&Self::NicheTriggersImpl> {
        self.0.get(key)
    }

    fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    fn values(&self) -> Vec<Self::NicheTriggersImpl> {
        self.0.values().into_iter().map(Self::NicheTriggersImpl::clone).collect()
    }
}

pub fn data_to_index(data: &PsychotropicConfigData) -> Result<PsychotropicConfigIndex> {
    let mut barriers = AHashSet::new();
    let mut current_barrier = "#".to_string();
    let mut current_barrier_wait_for = Vec::new();
    let mut in_block = None;
    barriers.insert(current_barrier.clone());
    let mut index: AHashMap<String, NicheTriggersData> = AHashMap::new();
    for cue in &data.cues {
        let cue_name = cue.name();
        if let Some(_) = cue_name.strip_prefix("#") {
            if barriers.contains(&cue_name) {
                return Err(anyhow!("Barrier appears multiple times in psychotropic config: {:?}", &cue_name));
            }
            if in_block.is_some() {
                let mut name = cue_name.clone();
                swap(&mut name, &mut current_barrier);
                let previous_barrier_name = name.clone();
                let mut wait_for = Vec::new();
                swap(&mut wait_for, &mut current_barrier_wait_for);
                let barrier_cue = NicheCueData { name, wait_for, use_thundercloud: None };
                index.insert(previous_barrier_name, NicheTriggersData::new(barrier_cue));
            } else {
                current_barrier = cue_name.clone();
            }
            barriers.insert(current_barrier.clone());
            in_block = Some(current_barrier.clone());
            continue;
        }
        if index.contains_key(&cue_name) {
            return Err(anyhow!("Niche appears multiple times in psychotropic config: {:?}", &cue.name));
        }
        current_barrier_wait_for.push(cue_name.clone());
        let mut wait_for = cue.wait_for();
        let mut wait_for_extended;
        if let Some(barrier_name) = &in_block {
            wait_for_extended = wait_for.to_vec();
            wait_for_extended.push(barrier_name.clone());
            wait_for = wait_for_extended.as_slice();
        }
        for dep in wait_for {
            if let Some(niche_trigger) = index.get_mut(dep) {
                niche_trigger.triggers.push(cue.name())
            } else {
                let trivial = NicheCueData { name: dep.clone(), wait_for: Vec::new(), use_thundercloud: None };
                let mut niche_trigger = NicheTriggersData::new(trivial);
                niche_trigger.triggers.push(cue.name());
                index.insert(dep.clone(), niche_trigger);
            }
        }
        index.insert(cue.name().to_string(), NicheTriggersData::new(cue.clone()));
    }
    Ok(PsychotropicConfigIndex(index))
}

pub fn empty() -> PsychotropicConfigIndex {
    PsychotropicConfigIndex(AHashMap::new())
}