use serde::Serialize;
use crate::self_depend::manager::Manager;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, clap::ValueEnum)]
#[serde(rename_all = "kebab-case")]
pub enum Venue {
Crates,
Flake,
GithubRelease,
}
impl Venue {
pub const ALL: [Self; 3] = [Self::Crates, Self::Flake, Self::GithubRelease];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Crates => "crates",
Self::Flake => "flake",
Self::GithubRelease => "github-release",
}
}
}
impl std::fmt::Display for Venue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Reason {
AttributeUnknown,
HashNeeded,
NoBackend,
PluginUnknown,
}
impl Reason {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::AttributeUnknown => "no package-set attribute is known for this manager",
Self::HashNeeded => "a source hash this tool cannot compute offline is required",
Self::NoBackend => "the manager has no backend for this venue",
Self::PluginUnknown => "no published plugin installs this tool",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "kebab-case", tag = "verdict")]
pub enum Verdict {
Renders,
Manual {
reason: Reason,
},
}
#[must_use]
pub const fn verdict(manager: Manager, venue: Venue) -> Verdict {
match (manager, venue) {
(Manager::Flake | Manager::Devbox, Venue::Flake)
| (Manager::Mise, Venue::Crates | Venue::GithubRelease) => Verdict::Renders,
(Manager::Flake | Manager::Devbox, Venue::Crates) => Verdict::Manual {
reason: Reason::AttributeUnknown,
},
(Manager::Flake | Manager::Devbox, Venue::GithubRelease) => Verdict::Manual {
reason: Reason::HashNeeded,
},
(Manager::Mise, Venue::Flake) => Verdict::Manual {
reason: Reason::NoBackend,
},
(Manager::Asdf, _) => Verdict::Manual {
reason: Reason::PluginUnknown,
},
}
}
#[must_use]
pub fn default_venue(manager: Manager) -> Option<Venue> {
Venue::ALL
.into_iter()
.find(|venue| matches!(verdict(manager, *venue), Verdict::Renders))
}
#[derive(Debug, Clone, Serialize)]
pub struct Pair {
pub manager: Manager,
pub venue: Venue,
#[serde(flatten)]
pub verdict: Verdict,
}
#[must_use]
pub fn matrix() -> Vec<Pair> {
let mut rows = Vec::new();
for manager in Manager::ALL {
for venue in Venue::ALL {
rows.push(Pair {
manager,
venue,
verdict: verdict(manager, venue),
});
}
}
rows
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_pair_carries_a_verdict_and_every_manual_pair_a_reason() {
let rows = matrix();
assert_eq!(rows.len(), Manager::ALL.len() * Venue::ALL.len());
for row in &rows {
match row.verdict {
Verdict::Renders => {}
Verdict::Manual { reason } => assert!(!reason.as_str().is_empty()),
}
}
}
#[test]
fn the_rendering_pairs_are_the_four_this_release_proves() {
let renders: Vec<(Manager, Venue)> = matrix()
.into_iter()
.filter(|row| row.verdict == Verdict::Renders)
.map(|row| (row.manager, row.venue))
.collect();
assert_eq!(
renders,
[
(Manager::Flake, Venue::Flake),
(Manager::Mise, Venue::Crates),
(Manager::Mise, Venue::GithubRelease),
(Manager::Devbox, Venue::Flake),
]
);
}
#[test]
fn a_manager_defaults_to_its_first_rendering_venue() {
assert_eq!(default_venue(Manager::Flake), Some(Venue::Flake));
assert_eq!(default_venue(Manager::Mise), Some(Venue::Crates));
assert_eq!(default_venue(Manager::Devbox), Some(Venue::Flake));
assert_eq!(default_venue(Manager::Asdf), None);
}
}