rotz 1.2.1

Fully cross platform dotfile manager written in rust.
use std::{
  collections::{HashMap, HashSet},
  path::PathBuf,
};

#[cfg(test)]
use fake::Dummy;
use itertools::{Either, Itertools};
use serde::Deserialize;
use tap::Pipe;
#[cfg(feature = "profiling")]
use tracing::instrument;
use velcro::hash_set;

use crate::{
  helpers::{self, MultipleErrors},
  templating::{Engine, Parameters},
};

use super::{CapabilitiesComplex, DotCanonical, InstallsCanonical, LinksComplex, Merge};

#[derive(Deserialize, Clone, Default, Debug)]
#[cfg_attr(test, derive(Dummy))]
#[serde(deny_unknown_fields)]
pub struct CapabilitiesCanonical {
  pub links: Option<HashMap<PathBuf, HashSet<PathBuf>>>,
  pub installs: Option<InstallsCanonical>,
  pub depends: Option<HashSet<String>>,
}

impl From<CapabilitiesComplex> for CapabilitiesCanonical {
  #[cfg_attr(feature = "profiling", instrument)]
  fn from(value: CapabilitiesComplex) -> Self {
    Self {
      links: value.links.map(|links| {
        links
          .into_iter()
          .map(|l| {
            (
              l.0,
              match l.1 {
                LinksComplex::One(o) => hash_set!(o),
                LinksComplex::Many(m) => m,
              },
            )
          })
          .collect::<HashMap<_, _>>()
      }),
      installs: value.installs.map(Into::into),
      depends: value.depends,
    }
  }
}

impl CapabilitiesCanonical {
  #[cfg_attr(feature = "profiling", instrument(skip(engine)))]
  pub fn from(DotCanonical { selectors }: DotCanonical, engine: &Engine<'_>, parameters: &Parameters<'_>) -> Result<Self, helpers::ParseError> {
    let selectors = selectors
      .into_iter()
      .map(|(selector, capabilities)| (selector.applies(engine, parameters), selector, capabilities))
      .collect_vec();
    if selectors.iter().any(|(a, _, _)| a.is_err()) {
      return selectors
        .into_iter()
        .filter_map(|(applies, _, _)| applies.err())
        .flatten()
        .collect::<Vec<_>>()
        .pipe(|e| Err(helpers::ParseError::Selector(MultipleErrors::from(e))));
    }
    let selectors = selectors
      .into_iter()
      .filter_map(|(applies, selector, capabilities)| if applies.unwrap() { Some((selector, capabilities)) } else { None });
    let (globals, selectors): (Vec<_>, Vec<_>) = selectors.partition_map(|(selector, capability)| if selector.is_global() { Either::Left } else { Either::Right }(capability));
    let mut capabilities = None::<CapabilitiesCanonical>;

    for capability in globals {
      capabilities = capabilities.merge(capability.into());
    }

    for capability in selectors {
      capabilities = capabilities.merge(capability.into());
    }

    capabilities.unwrap_or_default().pipe(Ok)
  }
}

impl Merge<Option<CapabilitiesCanonical>> for Option<CapabilitiesCanonical> {
  #[cfg_attr(feature = "profiling", instrument)]
  fn merge(self, merge: Option<CapabilitiesCanonical>) -> Self {
    if let Some(s) = self {
      if let Some(merge) = merge { s.merge(merge) } else { s }.into()
    } else {
      merge
    }
  }
}

impl Merge<Self> for CapabilitiesCanonical {
  #[cfg_attr(feature = "profiling", instrument)]
  fn merge(mut self, Self { mut links, installs, depends }: Self) -> Self {
    if let Some(self_links) = &mut self.links {
      if let Some(merge_links) = &mut links {
        for l in &mut *merge_links {
          if self_links.contains_key(l.0) {
            let self_links_value = self_links.get_mut(l.0).unwrap();
            self_links_value.extend(l.1.clone());
          } else {
            self_links.insert(l.0.clone(), l.1.clone());
          }
        }
      }
    } else {
      self.links = links;
    }

    if let Some(i) = &mut self.installs {
      if let Some(installs) = installs {
        if installs.is_none() {
          self.installs = None;
        } else {
          let cmd_outer: String;
          let mut depends_outer;

          match installs {
            InstallsCanonical::Full { cmd, depends } => {
              cmd_outer = cmd;
              depends_outer = depends;
            }
            InstallsCanonical::None(_) => unreachable!(),
          }

          *i = match i {
            InstallsCanonical::None(_) => InstallsCanonical::Full {
              cmd: cmd_outer,
              depends: depends_outer,
            },
            InstallsCanonical::Full { depends, .. } => {
              depends_outer.extend(depends.clone());
              InstallsCanonical::Full {
                cmd: cmd_outer,
                depends: depends_outer,
              }
            }
          };
        }
      }
    } else {
      self.installs = installs;
    }

    if let Some(d) = &mut self.depends {
      if let Some(depends) = depends {
        d.extend(depends);
      }
    } else {
      self.depends = depends;
    }

    self
  }
}