use std::{ collections::HashMap, fmt::{Display, Formatter}, }; use serde::Deserialize; use terrors::OneOf; use crate::filesystem::FileSystem; #[derive(Debug, PartialEq, Eq, Deserialize)] pub struct Config { forge: HashMap, } impl Config { pub(crate) fn load(fs: &FileSystem) -> Result> { let str = fs.read_file("git-next-server.toml").map_err(OneOf::new)?; toml::from_str(&str).map_err(OneOf::new) } pub(crate) fn forges(&self) -> impl Iterator { self.forge .iter() .map(|(name, forge)| (ForgeName(name.clone()), forge)) } } #[derive(Debug, PartialEq, Eq, Deserialize)] pub struct Forge { forge_type: ForgeType, hostname: String, user: String, // API Token // Private SSH Key Path repos: HashMap, } impl Forge { pub const fn forge_type(&self) -> &ForgeType { &self.forge_type } pub fn hostname(&self) -> Hostname { Hostname(self.hostname.clone()) } pub fn user(&self) -> User { User(self.user.clone()) } pub fn repos(&self) -> impl Iterator { self.repos .iter() .map(|(name, repo)| (RepoName(name.clone()), repo)) } } #[derive(Debug, PartialEq, Eq, Deserialize)] pub struct Repo { repo: String, branch: String, } impl Repo { #[cfg(test)] pub fn new(repo: &str, branch: &str) -> Self { Self { repo: repo.to_string(), branch: branch.to_string(), } } #[allow(dead_code)] pub fn repo(&self) -> RepoPath { RepoPath(self.repo.clone()) } #[allow(dead_code)] pub fn branch(&self) -> BranchName { BranchName(self.branch.clone()) } } #[cfg(test)] impl AsRef for Repo { fn as_ref(&self) -> &Repo { self } } impl Display for Repo { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{} - {}", self.repo, self.branch) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct ForgeName(pub String); impl Display for ForgeName { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Hostname(pub String); impl Display for Hostname { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct User(pub String); impl Display for User { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct ForgeDetails { pub name: ForgeName, pub forge_type: ForgeType, pub hostname: Hostname, pub user: User, // API Token // Private SSH Key Path } impl From<(&ForgeName, &Forge)> for ForgeDetails { fn from(forge: (&ForgeName, &Forge)) -> Self { Self { name: forge.0.clone(), forge_type: forge.1.forge_type.clone(), hostname: Hostname(forge.1.hostname.clone()), user: User(forge.1.user.clone()), } } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RepoName(pub String); impl Display for RepoName { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RepoPath(pub String); impl Display for RepoPath { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct BranchName(pub String); impl Display for BranchName { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RepoDetails { pub name: RepoName, pub repo: RepoPath, pub branch: BranchName, pub forge: ForgeDetails, } impl RepoDetails { pub fn new(name: &RepoName, repo: &Repo, forge_name: &ForgeName, forge: &Forge) -> Self { Self { name: name.clone(), repo: RepoPath(repo.repo.clone()), branch: BranchName(repo.branch.clone()), forge: ForgeDetails { name: forge_name.clone(), forge_type: forge.forge_type.clone(), hostname: Hostname(forge.hostname.clone()), user: User(forge.user.clone()), }, } } } impl Display for RepoDetails { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "{}/{} ({}): {}:{}/{} @ {}", self.forge.name, self.name, self.forge.forge_type, self.forge.hostname, self.forge.user, self.repo, self.branch, ) } } #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] pub enum ForgeType { ForgeJo, // Gitea, // GitHub, // GitLab, // BitBucket, } impl Display for ForgeType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } #[cfg(test)] mod tests { use assert2::let_assert; use crate::filesystem::FileSystem; use super::*; #[test] fn test_config_load() -> Result<(), OneOf<(std::io::Error, toml::de::Error)>> { let fs = FileSystem::new_temp().map_err(OneOf::new)?; fs.write_file( "git-next-server.toml", r#" [forge.default] forge_type = "ForgeJo" hostname = "git.example.net" user = "Bob" [forge.default.repos] hello = { repo = "user/hello", branch = "main" } world = { repo = "user/world", branch = "master" } "#, ) .map_err(OneOf::new)?; let config = Config::load(&fs)?; let_assert!(Some(default) = config.forge.get("default")); assert_eq!(default.forge_type, ForgeType::ForgeJo); assert_eq!(default.hostname, "git.example.net".to_string()); assert_eq!(default.user, "Bob".to_string()); assert_eq!( default.repos.get("hello"), Some(Repo::new("user/hello", "main").as_ref()) ); assert_eq!( default.repos.get("world"), Some(Repo::new("user/world", "master").as_ref()) ); Ok(()) } }