mirror of
https://github.com/nix-community/nurl.git
synced 2024-09-11 18:39:19 +03:00
enum dispatch
This commit is contained in:
parent
6fc0d29d5e
commit
2f2248c9ff
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -60,6 +60,18 @@ dependencies = [
|
|||||||
"os_str_bytes",
|
"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]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
@ -167,6 +179,7 @@ version = "0.1.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"enum_dispatch",
|
||||||
"indoc",
|
"indoc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -13,6 +13,7 @@ categories = ["command-line-utilities"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.68"
|
anyhow = "1.0.68"
|
||||||
|
enum_dispatch = "0.3.9"
|
||||||
indoc = "1.0.8"
|
indoc = "1.0.8"
|
||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.91"
|
||||||
|
@ -14,7 +14,7 @@ pub struct Opts {
|
|||||||
|
|
||||||
/// specify the fetcher function instead of inferring from the URL
|
/// specify the fetcher function instead of inferring from the URL
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub fetcher: Option<Fetcher>,
|
pub fetcher: Option<FetcherFunction>,
|
||||||
|
|
||||||
/// extra indentation (in number of spaces)
|
/// extra indentation (in number of spaces)
|
||||||
#[arg(short, long, default_value_t = 0)]
|
#[arg(short, long, default_value_t = 0)]
|
||||||
@ -23,7 +23,7 @@ pub struct Opts {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, ValueEnum)]
|
#[derive(Clone, Debug, ValueEnum)]
|
||||||
#[clap(rename_all = "camelCase")]
|
#[clap(rename_all = "camelCase")]
|
||||||
pub enum Fetcher {
|
pub enum FetcherFunction {
|
||||||
FetchFromGitHub,
|
FetchFromGitHub,
|
||||||
FetchFromGitLab,
|
FetchFromGitLab,
|
||||||
FetchFromSourcehut,
|
FetchFromSourcehut,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::fetcher::UrlFlakeFetcher;
|
use crate::{fetcher::UrlFlakeFetcher, impl_fetcher};
|
||||||
|
|
||||||
pub struct Fetchgit;
|
pub struct Fetchgit;
|
||||||
|
impl_fetcher!(Fetchgit);
|
||||||
|
|
||||||
impl UrlFlakeFetcher for Fetchgit {
|
impl UrlFlakeFetcher for Fetchgit {
|
||||||
const FLAKE_TYPE: &'static str = "git";
|
const FLAKE_TYPE: &'static str = "git";
|
||||||
|
@ -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<String>);
|
||||||
|
impl_fetcher!(FetchFromGitHub);
|
||||||
|
|
||||||
impl<'a> SimpleFlakeFetcher<'a> for FetchFromGitHub<'a> {
|
impl<'a> SimpleFlakeFetcher<'a> for FetchFromGitHub {
|
||||||
const FLAKE_TYPE: &'static str = "github";
|
const FLAKE_TYPE: &'static str = "github";
|
||||||
const NAME: &'static str = "fetchFromGitHub";
|
const NAME: &'static str = "fetchFromGitHub";
|
||||||
|
|
||||||
fn host(&self) -> Option<&'a str> {
|
fn host(&'a self) -> &'a Option<String> {
|
||||||
self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<String>);
|
||||||
|
impl_fetcher!(FetchFromGitLab);
|
||||||
|
|
||||||
impl<'a> SimpleFlakeFetcher<'a> for FetchFromGitLab<'a> {
|
impl<'a> SimpleFlakeFetcher<'a> for FetchFromGitLab {
|
||||||
const FLAKE_TYPE: &'static str = "gitlab";
|
const FLAKE_TYPE: &'static str = "gitlab";
|
||||||
const NAME: &'static str = "fetchFromGitLab";
|
const NAME: &'static str = "fetchFromGitLab";
|
||||||
|
|
||||||
fn host(&self) -> Option<&'a str> {
|
fn host(&'a self) -> &'a Option<String> {
|
||||||
self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::fetcher::UrlFlakeFetcher;
|
use crate::{fetcher::UrlFlakeFetcher, impl_fetcher};
|
||||||
|
|
||||||
pub struct Fetchhg;
|
pub struct Fetchhg;
|
||||||
|
impl_fetcher!(Fetchhg);
|
||||||
|
|
||||||
impl UrlFlakeFetcher for Fetchhg {
|
impl UrlFlakeFetcher for Fetchhg {
|
||||||
const FLAKE_TYPE: &'static str = "hg";
|
const FLAKE_TYPE: &'static str = "hg";
|
||||||
|
@ -4,6 +4,7 @@ mod gitlab;
|
|||||||
mod hg;
|
mod hg;
|
||||||
mod sourcehut;
|
mod sourcehut;
|
||||||
|
|
||||||
|
use enum_dispatch::enum_dispatch;
|
||||||
pub use git::Fetchgit;
|
pub use git::Fetchgit;
|
||||||
pub use github::FetchFromGitHub;
|
pub use github::FetchFromGitHub;
|
||||||
pub use gitlab::FetchFromGitLab;
|
pub use gitlab::FetchFromGitLab;
|
||||||
@ -20,47 +21,42 @@ use std::{
|
|||||||
process::{Command, Output, Stdio},
|
process::{Command, Output, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[enum_dispatch]
|
||||||
pub trait Fetcher {
|
pub trait Fetcher {
|
||||||
fn fetch_nix(&self, out: &mut impl Write, url: &Url, rev: String, indent: &str) -> Result<()>;
|
fn fetch_nix(&self, out: &mut impl Write, url: &Url, rev: String, indent: &str) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
trait GetStdout {
|
#[enum_dispatch(Fetcher)]
|
||||||
fn get_stdout(&mut self) -> Result<Vec<u8>>;
|
pub enum FetcherDispatch {
|
||||||
|
FetchFromGitHub(FetchFromGitHub),
|
||||||
|
FetchFromGitLab(FetchFromGitLab),
|
||||||
|
FetchFromSourcehut(FetchFromSourcehut),
|
||||||
|
Fetchgit(Fetchgit),
|
||||||
|
Fetchhg(Fetchhg),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GetStdout for Command {
|
#[macro_export]
|
||||||
fn get_stdout(&mut self) -> Result<Vec<u8>> {
|
macro_rules! impl_fetcher {
|
||||||
let Output { stdout, status, .. } = self.stderr(Stdio::inherit()).output()?;
|
($t:ident $($tt:tt)*) => {
|
||||||
if !status.success() {
|
impl $($tt)* $crate::fetcher::Fetcher for $t $($tt)* {
|
||||||
bail!("command exited with exit code {}", status);
|
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<String> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct PrefetchOutput {
|
|
||||||
hash: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
eprintln!("$ nix flake prefetch --json {flake_ref}");
|
|
||||||
Ok(serde_json::from_slice::<PrefetchOutput>(
|
|
||||||
&Command::new("nix")
|
|
||||||
.arg("flake")
|
|
||||||
.arg("prefetch")
|
|
||||||
.arg("--json")
|
|
||||||
.arg(flake_ref)
|
|
||||||
.get_stdout()?,
|
|
||||||
)?
|
|
||||||
.hash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SimpleFlakeFetcher<'a> {
|
pub trait SimpleFlakeFetcher<'a> {
|
||||||
const FLAKE_TYPE: &'static str;
|
const FLAKE_TYPE: &'static str;
|
||||||
const NAME: &'static str;
|
const NAME: &'static str;
|
||||||
|
|
||||||
fn host(&self) -> Option<&'a str>;
|
fn host(&'a self) -> &'a Option<String>;
|
||||||
|
|
||||||
fn get_repo(&self, url: &Url) -> Option<(String, String)> {
|
fn get_repo(&self, url: &Url) -> Option<(String, String)> {
|
||||||
let mut xs = url.path_segments()?;
|
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
|
let (owner, repo) = self
|
||||||
.get_repo(url)
|
.get_repo(url)
|
||||||
.with_context(|| format!("failed to parse {url} as a {} url", Self::FLAKE_TYPE))?;
|
.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 FLAKE_TYPE: &'static str;
|
||||||
const NAME: &'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!(
|
let hash = flake_prefetch(format!(
|
||||||
"{}+{url}?{}={rev}",
|
"{}+{url}?{}={rev}",
|
||||||
Self::FLAKE_TYPE,
|
Self::FLAKE_TYPE,
|
||||||
@ -128,3 +136,35 @@ pub trait UrlFlakeFetcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait GetStdout {
|
||||||
|
fn get_stdout(&mut self) -> Result<Vec<u8>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetStdout for Command {
|
||||||
|
fn get_stdout(&mut self) -> Result<Vec<u8>> {
|
||||||
|
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<String> {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PrefetchOutput {
|
||||||
|
hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("$ nix flake prefetch --json {flake_ref}");
|
||||||
|
Ok(serde_json::from_slice::<PrefetchOutput>(
|
||||||
|
&Command::new("nix")
|
||||||
|
.arg("flake")
|
||||||
|
.arg("prefetch")
|
||||||
|
.arg("--json")
|
||||||
|
.arg(flake_ref)
|
||||||
|
.get_stdout()?,
|
||||||
|
)?
|
||||||
|
.hash)
|
||||||
|
}
|
||||||
|
@ -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<String>);
|
||||||
|
impl_fetcher!(FetchFromSourcehut);
|
||||||
|
|
||||||
impl<'a> SimpleFlakeFetcher<'a> for FetchFromSourcehut<'a> {
|
impl<'a> SimpleFlakeFetcher<'a> for FetchFromSourcehut {
|
||||||
const FLAKE_TYPE: &'static str = "sourcehut";
|
const FLAKE_TYPE: &'static str = "sourcehut";
|
||||||
const NAME: &'static str = "fetchFromSourcehut";
|
const NAME: &'static str = "fetchFromSourcehut";
|
||||||
|
|
||||||
fn host(&self) -> Option<&'a str> {
|
fn host(&'a self) -> &'a Option<String> {
|
||||||
self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
56
src/main.rs
56
src/main.rs
@ -6,10 +6,10 @@ use clap::Parser;
|
|||||||
use url::Host;
|
use url::Host;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{Fetcher, Opts},
|
cli::{FetcherFunction, Opts},
|
||||||
fetcher::{
|
fetcher::{
|
||||||
FetchFromGitHub, FetchFromGitLab, FetchFromSourcehut, Fetchgit, Fetchhg,
|
FetchFromGitHub, FetchFromGitLab, FetchFromSourcehut, Fetcher, FetcherDispatch, Fetchgit,
|
||||||
SimpleFlakeFetcher, UrlFlakeFetcher,
|
Fetchhg,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -18,54 +18,50 @@ use std::io::stdout;
|
|||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let opts = Opts::parse();
|
let opts = Opts::parse();
|
||||||
|
|
||||||
let out = &mut stdout().lock();
|
|
||||||
let indent = &" ".repeat(opts.indent);
|
let indent = &" ".repeat(opts.indent);
|
||||||
|
|
||||||
match (opts.fetcher, opts.url.host()) {
|
let fetcher: FetcherDispatch = match (opts.fetcher, opts.url.host()) {
|
||||||
(Some(Fetcher::FetchFromGitHub), Some(host)) => {
|
(None | Some(FetcherFunction::FetchFromGitHub), Some(Host::Domain("github.com"))) => {
|
||||||
FetchFromGitHub((host != Host::Domain("github.com")).then_some(&host.to_string()))
|
FetchFromGitHub(None).into()
|
||||||
.fetch_nix(out, &opts.url, opts.rev, indent)?;
|
|
||||||
}
|
}
|
||||||
(None, Some(Host::Domain("github.com"))) => {
|
(Some(FetcherFunction::FetchFromGitHub), Some(host)) => {
|
||||||
FetchFromGitHub(None).fetch_nix(out, &opts.url, opts.rev, indent)?;
|
FetchFromGitHub(Some(host.to_string())).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
(Some(Fetcher::FetchFromGitLab), Some(host)) => {
|
(None | Some(FetcherFunction::FetchFromGitLab), Some(Host::Domain("gitlab.com"))) => {
|
||||||
FetchFromGitLab((host != Host::Domain("github.com")).then_some(&host.to_string()))
|
FetchFromGitLab(None).into()
|
||||||
.fetch_nix(out, &opts.url, opts.rev, indent)?;
|
|
||||||
}
|
}
|
||||||
(None, Some(Host::Domain(host))) if host.starts_with("gitlab.") => {
|
(None, Some(Host::Domain(host))) if host.starts_with("gitlab.") => {
|
||||||
FetchFromGitLab((host != "gitlab.com").then_some(host))
|
FetchFromGitLab(Some(host.into())).into()
|
||||||
.fetch_nix(out, &opts.url, opts.rev, indent)?;
|
}
|
||||||
|
(Some(FetcherFunction::FetchFromGitLab), Some(host)) => {
|
||||||
|
FetchFromGitLab(Some(host.to_string())).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
(Some(Fetcher::FetchFromSourcehut), Some(host)) => {
|
(None | Some(FetcherFunction::FetchFromSourcehut), Some(Host::Domain("git.sr.ht"))) => {
|
||||||
FetchFromSourcehut((host != Host::Domain("git.sr.ht")).then_some(&host.to_string()))
|
FetchFromSourcehut(None).into()
|
||||||
.fetch_nix(out, &opts.url, opts.rev, indent)?;
|
|
||||||
}
|
}
|
||||||
(None, Some(Host::Domain("git.sr.ht"))) => {
|
(Some(FetcherFunction::FetchFromSourcehut), Some(host)) => {
|
||||||
FetchFromSourcehut(None).fetch_nix(out, &opts.url, opts.rev, indent)?;
|
FetchFromSourcehut(Some(host.to_string())).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
Some(
|
Some(
|
||||||
fetcher @ (Fetcher::FetchFromGitHub
|
fetcher @ (FetcherFunction::FetchFromGitHub
|
||||||
| Fetcher::FetchFromGitLab
|
| FetcherFunction::FetchFromGitLab
|
||||||
| Fetcher::FetchFromSourcehut),
|
| FetcherFunction::FetchFromSourcehut),
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
) => {
|
) => {
|
||||||
bail!("{fetcher:?} does not support URLs without a host");
|
bail!("{fetcher:?} does not support URLs without a host");
|
||||||
}
|
}
|
||||||
|
|
||||||
(Some(Fetcher::Fetchgit), _) | (None, _) => {
|
(Some(FetcherFunction::Fetchgit), _) | (None, _) => Fetchgit.into(),
|
||||||
Fetchgit.fetch_nix(out, &opts.url, opts.rev, indent)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
(Some(Fetcher::Fetchhg), _) => {
|
(Some(FetcherFunction::Fetchhg), _) => Fetchhg.into(),
|
||||||
Fetchhg.fetch_nix(out, &opts.url, opts.rev, indent)?;
|
};
|
||||||
}
|
|
||||||
}
|
fetcher.fetch_nix(&mut stdout().lock(), &opts.url, opts.rev, indent)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user