From 2f2248c9ff851022b70094ef5c109b5565eea5ae Mon Sep 17 00:00:00 2001 From: figsoda Date: Fri, 30 Dec 2022 12:33:44 -0500 Subject: [PATCH] enum dispatch --- Cargo.lock | 13 +++++ Cargo.toml | 1 + src/cli.rs | 4 +- src/fetcher/git.rs | 3 +- src/fetcher/github.rs | 11 +++-- src/fetcher/gitlab.rs | 11 +++-- src/fetcher/hg.rs | 3 +- src/fetcher/mod.rs | 100 +++++++++++++++++++++++++++------------ src/fetcher/sourcehut.rs | 11 +++-- src/main.rs | 56 ++++++++++------------ 10 files changed, 134 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7589734..8a9c5f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,18 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "enum_dispatch" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1693044dcf452888dd3a6a6a0dab67f0652094e3920dfe029a54d2f37d9b7394" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "errno" version = "0.2.8" @@ -167,6 +179,7 @@ version = "0.1.1" dependencies = [ "anyhow", "clap", + "enum_dispatch", "indoc", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index d5b5fae..b6fd046 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ categories = ["command-line-utilities"] [dependencies] anyhow = "1.0.68" +enum_dispatch = "0.3.9" indoc = "1.0.8" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.91" diff --git a/src/cli.rs b/src/cli.rs index 45e34af..5b003a2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -14,7 +14,7 @@ pub struct Opts { /// specify the fetcher function instead of inferring from the URL #[arg(short, long)] - pub fetcher: Option, + pub fetcher: Option, /// extra indentation (in number of spaces) #[arg(short, long, default_value_t = 0)] @@ -23,7 +23,7 @@ pub struct Opts { #[derive(Clone, Debug, ValueEnum)] #[clap(rename_all = "camelCase")] -pub enum Fetcher { +pub enum FetcherFunction { FetchFromGitHub, FetchFromGitLab, FetchFromSourcehut, diff --git a/src/fetcher/git.rs b/src/fetcher/git.rs index c15b3b7..3d632f7 100644 --- a/src/fetcher/git.rs +++ b/src/fetcher/git.rs @@ -1,6 +1,7 @@ -use crate::fetcher::UrlFlakeFetcher; +use crate::{fetcher::UrlFlakeFetcher, impl_fetcher}; pub struct Fetchgit; +impl_fetcher!(Fetchgit); impl UrlFlakeFetcher for Fetchgit { const FLAKE_TYPE: &'static str = "git"; diff --git a/src/fetcher/github.rs b/src/fetcher/github.rs index 28050bb..98c5178 100644 --- a/src/fetcher/github.rs +++ b/src/fetcher/github.rs @@ -1,12 +1,13 @@ -use crate::fetcher::SimpleFlakeFetcher; +use crate::{fetcher::SimpleFlakeFetcher, impl_fetcher}; -pub struct FetchFromGitHub<'a>(pub Option<&'a str>); +pub struct FetchFromGitHub(pub Option); +impl_fetcher!(FetchFromGitHub); -impl<'a> SimpleFlakeFetcher<'a> for FetchFromGitHub<'a> { +impl<'a> SimpleFlakeFetcher<'a> for FetchFromGitHub { const FLAKE_TYPE: &'static str = "github"; const NAME: &'static str = "fetchFromGitHub"; - fn host(&self) -> Option<&'a str> { - self.0 + fn host(&'a self) -> &'a Option { + &self.0 } } diff --git a/src/fetcher/gitlab.rs b/src/fetcher/gitlab.rs index 0257597..422c4db 100644 --- a/src/fetcher/gitlab.rs +++ b/src/fetcher/gitlab.rs @@ -1,12 +1,13 @@ -use crate::fetcher::SimpleFlakeFetcher; +use crate::{fetcher::SimpleFlakeFetcher, impl_fetcher}; -pub struct FetchFromGitLab<'a>(pub Option<&'a str>); +pub struct FetchFromGitLab(pub Option); +impl_fetcher!(FetchFromGitLab); -impl<'a> SimpleFlakeFetcher<'a> for FetchFromGitLab<'a> { +impl<'a> SimpleFlakeFetcher<'a> for FetchFromGitLab { const FLAKE_TYPE: &'static str = "gitlab"; const NAME: &'static str = "fetchFromGitLab"; - fn host(&self) -> Option<&'a str> { - self.0 + fn host(&'a self) -> &'a Option { + &self.0 } } diff --git a/src/fetcher/hg.rs b/src/fetcher/hg.rs index 35e422f..abff606 100644 --- a/src/fetcher/hg.rs +++ b/src/fetcher/hg.rs @@ -1,6 +1,7 @@ -use crate::fetcher::UrlFlakeFetcher; +use crate::{fetcher::UrlFlakeFetcher, impl_fetcher}; pub struct Fetchhg; +impl_fetcher!(Fetchhg); impl UrlFlakeFetcher for Fetchhg { const FLAKE_TYPE: &'static str = "hg"; diff --git a/src/fetcher/mod.rs b/src/fetcher/mod.rs index cd6e038..1dc9141 100644 --- a/src/fetcher/mod.rs +++ b/src/fetcher/mod.rs @@ -4,6 +4,7 @@ mod gitlab; mod hg; mod sourcehut; +use enum_dispatch::enum_dispatch; pub use git::Fetchgit; pub use github::FetchFromGitHub; pub use gitlab::FetchFromGitLab; @@ -20,47 +21,42 @@ use std::{ process::{Command, Output, Stdio}, }; +#[enum_dispatch] pub trait Fetcher { fn fetch_nix(&self, out: &mut impl Write, url: &Url, rev: String, indent: &str) -> Result<()>; } -trait GetStdout { - fn get_stdout(&mut self) -> Result>; +#[enum_dispatch(Fetcher)] +pub enum FetcherDispatch { + FetchFromGitHub(FetchFromGitHub), + FetchFromGitLab(FetchFromGitLab), + FetchFromSourcehut(FetchFromSourcehut), + Fetchgit(Fetchgit), + Fetchhg(Fetchhg), } -impl GetStdout for Command { - fn get_stdout(&mut self) -> Result> { - let Output { stdout, status, .. } = self.stderr(Stdio::inherit()).output()?; - if !status.success() { - bail!("command exited with exit code {}", status); +#[macro_export] +macro_rules! impl_fetcher { + ($t:ident $($tt:tt)*) => { + impl $($tt)* $crate::fetcher::Fetcher for $t $($tt)* { + fn fetch_nix( + &self, + out: &mut impl ::std::io::Write, + url: &::url::Url, + rev: String, + indent: &str, + ) -> ::anyhow::Result<()> { + self.fetch_nix_imp(out, url, rev, indent) + } } - Ok(stdout) - } -} - -pub fn flake_prefetch(flake_ref: String) -> Result { - #[derive(Deserialize)] - struct PrefetchOutput { - hash: String, - } - - eprintln!("$ nix flake prefetch --json {flake_ref}"); - Ok(serde_json::from_slice::( - &Command::new("nix") - .arg("flake") - .arg("prefetch") - .arg("--json") - .arg(flake_ref) - .get_stdout()?, - )? - .hash) + }; } pub trait SimpleFlakeFetcher<'a> { const FLAKE_TYPE: &'static str; const NAME: &'static str; - fn host(&self) -> Option<&'a str>; + fn host(&'a self) -> &'a Option; fn get_repo(&self, url: &Url) -> Option<(String, String)> { let mut xs = url.path_segments()?; @@ -72,7 +68,13 @@ pub trait SimpleFlakeFetcher<'a> { )) } - fn fetch_nix(&self, out: &mut impl Write, url: &Url, rev: String, indent: &str) -> Result<()> { + fn fetch_nix_imp( + &'a self, + out: &mut impl Write, + url: &Url, + rev: String, + indent: &str, + ) -> Result<()> { let (owner, repo) = self .get_repo(url) .with_context(|| format!("failed to parse {url} as a {} url", Self::FLAKE_TYPE))?; @@ -107,7 +109,13 @@ pub trait UrlFlakeFetcher { const FLAKE_TYPE: &'static str; const NAME: &'static str; - fn fetch_nix(&self, out: &mut impl Write, url: &Url, rev: String, indent: &str) -> Result<()> { + fn fetch_nix_imp( + &self, + out: &mut impl Write, + url: &Url, + rev: String, + indent: &str, + ) -> Result<()> { let hash = flake_prefetch(format!( "{}+{url}?{}={rev}", Self::FLAKE_TYPE, @@ -128,3 +136,35 @@ pub trait UrlFlakeFetcher { Ok(()) } } + +trait GetStdout { + fn get_stdout(&mut self) -> Result>; +} + +impl GetStdout for Command { + fn get_stdout(&mut self) -> Result> { + let Output { stdout, status, .. } = self.stderr(Stdio::inherit()).output()?; + if !status.success() { + bail!("command exited with exit code {}", status); + } + Ok(stdout) + } +} + +pub fn flake_prefetch(flake_ref: String) -> Result { + #[derive(Deserialize)] + struct PrefetchOutput { + hash: String, + } + + eprintln!("$ nix flake prefetch --json {flake_ref}"); + Ok(serde_json::from_slice::( + &Command::new("nix") + .arg("flake") + .arg("prefetch") + .arg("--json") + .arg(flake_ref) + .get_stdout()?, + )? + .hash) +} diff --git a/src/fetcher/sourcehut.rs b/src/fetcher/sourcehut.rs index fc909a4..d1dd10d 100644 --- a/src/fetcher/sourcehut.rs +++ b/src/fetcher/sourcehut.rs @@ -1,12 +1,13 @@ -use crate::fetcher::SimpleFlakeFetcher; +use crate::{fetcher::SimpleFlakeFetcher, impl_fetcher}; -pub struct FetchFromSourcehut<'a>(pub Option<&'a str>); +pub struct FetchFromSourcehut(pub Option); +impl_fetcher!(FetchFromSourcehut); -impl<'a> SimpleFlakeFetcher<'a> for FetchFromSourcehut<'a> { +impl<'a> SimpleFlakeFetcher<'a> for FetchFromSourcehut { const FLAKE_TYPE: &'static str = "sourcehut"; const NAME: &'static str = "fetchFromSourcehut"; - fn host(&self) -> Option<&'a str> { - self.0 + fn host(&'a self) -> &'a Option { + &self.0 } } diff --git a/src/main.rs b/src/main.rs index 1c368c2..6d69548 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,10 +6,10 @@ use clap::Parser; use url::Host; use crate::{ - cli::{Fetcher, Opts}, + cli::{FetcherFunction, Opts}, fetcher::{ - FetchFromGitHub, FetchFromGitLab, FetchFromSourcehut, Fetchgit, Fetchhg, - SimpleFlakeFetcher, UrlFlakeFetcher, + FetchFromGitHub, FetchFromGitLab, FetchFromSourcehut, Fetcher, FetcherDispatch, Fetchgit, + Fetchhg, }, }; @@ -18,54 +18,50 @@ use std::io::stdout; fn main() -> Result<()> { let opts = Opts::parse(); - let out = &mut stdout().lock(); let indent = &" ".repeat(opts.indent); - match (opts.fetcher, opts.url.host()) { - (Some(Fetcher::FetchFromGitHub), Some(host)) => { - FetchFromGitHub((host != Host::Domain("github.com")).then_some(&host.to_string())) - .fetch_nix(out, &opts.url, opts.rev, indent)?; + let fetcher: FetcherDispatch = match (opts.fetcher, opts.url.host()) { + (None | Some(FetcherFunction::FetchFromGitHub), Some(Host::Domain("github.com"))) => { + FetchFromGitHub(None).into() } - (None, Some(Host::Domain("github.com"))) => { - FetchFromGitHub(None).fetch_nix(out, &opts.url, opts.rev, indent)?; + (Some(FetcherFunction::FetchFromGitHub), Some(host)) => { + FetchFromGitHub(Some(host.to_string())).into() } - (Some(Fetcher::FetchFromGitLab), Some(host)) => { - FetchFromGitLab((host != Host::Domain("github.com")).then_some(&host.to_string())) - .fetch_nix(out, &opts.url, opts.rev, indent)?; + (None | Some(FetcherFunction::FetchFromGitLab), Some(Host::Domain("gitlab.com"))) => { + FetchFromGitLab(None).into() } (None, Some(Host::Domain(host))) if host.starts_with("gitlab.") => { - FetchFromGitLab((host != "gitlab.com").then_some(host)) - .fetch_nix(out, &opts.url, opts.rev, indent)?; + FetchFromGitLab(Some(host.into())).into() + } + (Some(FetcherFunction::FetchFromGitLab), Some(host)) => { + FetchFromGitLab(Some(host.to_string())).into() } - (Some(Fetcher::FetchFromSourcehut), Some(host)) => { - FetchFromSourcehut((host != Host::Domain("git.sr.ht")).then_some(&host.to_string())) - .fetch_nix(out, &opts.url, opts.rev, indent)?; + (None | Some(FetcherFunction::FetchFromSourcehut), Some(Host::Domain("git.sr.ht"))) => { + FetchFromSourcehut(None).into() } - (None, Some(Host::Domain("git.sr.ht"))) => { - FetchFromSourcehut(None).fetch_nix(out, &opts.url, opts.rev, indent)?; + (Some(FetcherFunction::FetchFromSourcehut), Some(host)) => { + FetchFromSourcehut(Some(host.to_string())).into() } ( Some( - fetcher @ (Fetcher::FetchFromGitHub - | Fetcher::FetchFromGitLab - | Fetcher::FetchFromSourcehut), + fetcher @ (FetcherFunction::FetchFromGitHub + | FetcherFunction::FetchFromGitLab + | FetcherFunction::FetchFromSourcehut), ), None, ) => { bail!("{fetcher:?} does not support URLs without a host"); } - (Some(Fetcher::Fetchgit), _) | (None, _) => { - Fetchgit.fetch_nix(out, &opts.url, opts.rev, indent)?; - } + (Some(FetcherFunction::Fetchgit), _) | (None, _) => Fetchgit.into(), - (Some(Fetcher::Fetchhg), _) => { - Fetchhg.fetch_nix(out, &opts.url, opts.rev, indent)?; - } - } + (Some(FetcherFunction::Fetchhg), _) => Fetchhg.into(), + }; + + fetcher.fetch_nix(&mut stdout().lock(), &opts.url, opts.rev, indent)?; Ok(()) }