diff --git a/src/common/cvs.rs b/src/common/cvs.rs index 87a0b8b..7753ca5 100644 --- a/src/common/cvs.rs +++ b/src/common/cvs.rs @@ -25,7 +25,7 @@ pub trait CvsFetcher { fn write_nix( &self, out: &mut impl Write, - url: Url, + url: &Url, rev: String, hash: String, args: Vec<(String, String)>, @@ -54,7 +54,7 @@ pub trait CvsFetcher { fn write_json( &self, out: &mut impl Write, - url: Url, + url: &Url, rev: String, hash: String, args: Vec<(String, String)>, @@ -89,23 +89,23 @@ pub trait CvsFodFetcher: CvsFetcher { fn fetch_nix_impl( &self, out: &mut impl Write, - url: Url, + url: &Url, rev: String, args: Vec<(String, String)>, indent: String, ) -> Result<()> { - let hash = self.fetch_fod(&url, &rev, &args)?; + let hash = self.fetch_fod(url, &rev, &args)?; self.write_nix(out, url, rev, hash, args, indent) } fn fetch_json_impl( &self, out: &mut impl Write, - url: Url, + url: &Url, rev: String, args: Vec<(String, String)>, ) -> Result<()> { - let hash = self.fetch_fod(&url, &rev, &args)?; + let hash = self.fetch_fod(url, &rev, &args)?; self.write_json(out, url, rev, hash, args) } } @@ -124,15 +124,15 @@ pub trait CvsFlakeFetcher: CvsFetcher { fn fetch_nix_impl( &self, out: &mut impl Write, - url: Url, + url: &Url, rev: String, args: Vec<(String, String)>, indent: String, ) -> Result<()> { let hash = if args.is_empty() { - self.fetch(&url, &rev)? + self.fetch(url, &rev)? } else { - self.fetch_fod(&url, &rev, &args)? + self.fetch_fod(url, &rev, &args)? }; self.write_nix(out, url, rev, hash, args, indent) } @@ -140,14 +140,14 @@ pub trait CvsFlakeFetcher: CvsFetcher { fn fetch_json_impl( &self, out: &mut impl Write, - url: Url, + url: &Url, rev: String, args: Vec<(String, String)>, ) -> Result<()> { let hash = if args.is_empty() { - self.fetch(&url, &rev)? + self.fetch(url, &rev)? } else { - self.fetch_fod(&url, &rev, &args)? + self.fetch_fod(url, &rev, &args)? }; self.write_json(out, url, rev, hash, args) } diff --git a/src/common/simple.rs b/src/common/simple.rs index 25ce1f6..66bd1cb 100644 --- a/src/common/simple.rs +++ b/src/common/simple.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result}; use indoc::writedoc; +use itertools::Itertools; use serde_json::json; use url::Url; @@ -7,54 +8,66 @@ use std::{fmt::Write as _, io::Write}; use crate::common::{flake_prefetch, fod_prefetch, url_prefetch}; -pub trait SimpleFetcher<'a> { +pub trait SimpleFetcher<'a, const N: usize = 2> { const HOST_KEY: &'static str = "domain"; + const KEYS: [&'static str; N]; const NAME: &'static str; fn host(&'a self) -> Option<&'a str>; - fn get_repo(&self, url: &Url) -> Option<(String, String)> { - let mut xs = url.path_segments()?; - let owner = xs.next()?; - let repo = xs.next()?; - Some(( - owner.into(), - repo.strip_suffix(".git").unwrap_or(repo).into(), - )) + fn get_values(&self, url: &'a Url) -> Option<[&'a str; N]> { + let mut xs: [_; N] = url + .path_segments()? + .chunks(N) + .into_iter() + .next()? + .collect::>() + .try_into() + .ok()?; + xs[N - 1] = xs[N - 1].strip_suffix(".git").unwrap_or(xs[N - 1]); + Some(xs) } fn fetch_fod( &'a self, - url: &Url, + url: &'a Url, rev: &str, args: &[(String, String)], - ) -> Result<(String, String, String)> { - let (owner, repo) = self - .get_repo(url) + ) -> Result<([&str; N], String)> { + let values = self + .get_values(url) .with_context(|| format!("failed to parse {url}"))?; - let mut expr = format!( - r#"(import {{}}).{}{{owner="{owner}";repo="{repo}";rev="{rev}";hash="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";"#, - Self::NAME - ); + let mut expr = format!(r#"(import {{}}).{}{{"#, Self::NAME); + if let Some(host) = self.host() { write!(expr, r#"{}="{host}""#, Self::HOST_KEY)?; } + + for (key, value) in Self::KEYS.iter().zip(values) { + write!(expr, r#"{key}="{value}";"#)?; + } + + write!( + expr, + r#"rev="{rev}";hash="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";"# + )?; + for (key, value) in args { write!(expr, "{key}={value};")?; } + expr.push('}'); let hash = fod_prefetch(expr)?; - Ok((owner, repo, hash)) + Ok((values, hash)) } fn write_nix( &'a self, out: &mut impl Write, - owner: String, - repo: String, + values: [&str; N], rev: String, hash: String, args: Vec<(String, String)>, @@ -66,11 +79,13 @@ pub trait SimpleFetcher<'a> { writeln!(out, r#"{indent} {} = "{host}";"#, Self::HOST_KEY)?; } + for (key, value) in Self::KEYS.iter().zip(values) { + writeln!(out, r#"{indent} {key} = "{value}";"#)?; + } + writedoc!( out, r#" - {indent} owner = "{owner}"; - {indent} repo = "{repo}"; {indent} rev = "{rev}"; {indent} hash = "{hash}"; "# @@ -88,15 +103,12 @@ pub trait SimpleFetcher<'a> { fn write_json( &'a self, out: &mut impl Write, - owner: String, - repo: String, + values: [&str; N], rev: String, hash: String, args: Vec<(String, String)>, ) -> Result<()> { let mut fetcher_args = json! ({ - "owner": owner, - "repo": repo, "rev": rev, "hash": hash, }); @@ -105,7 +117,11 @@ pub trait SimpleFetcher<'a> { fetcher_args["host"] = json!(host); } - for (key, value) in args { + for (key, value) in Self::KEYS + .into_iter() + .zip(values) + .chain(args.iter().map(|(x, y)| (x.as_str(), y.as_str()))) + { fetcher_args[key] = json!(value); } @@ -121,37 +137,38 @@ pub trait SimpleFetcher<'a> { } } -pub trait SimpleFodFetcher<'a>: SimpleFetcher<'a> { +pub trait SimpleFodFetcher<'a, const N: usize = 2>: SimpleFetcher<'a, N> { fn fetch_nix_impl( &'a self, out: &mut impl Write, - url: Url, + url: &'a Url, rev: String, args: Vec<(String, String)>, indent: String, ) -> Result<()> { - let (owner, repo, hash) = self.fetch_fod(&url, &rev, &args)?; - self.write_nix(out, owner, repo, rev, hash, args, indent) + let (values, hash) = self.fetch_fod(url, &rev, &args)?; + self.write_nix(out, values, rev, hash, args, indent) } fn fetch_json_impl( &'a self, out: &mut impl Write, - url: Url, + url: &'a Url, rev: String, args: Vec<(String, String)>, ) -> Result<()> { - let (owner, repo, hash) = self.fetch_fod(&url, &rev, &args)?; - self.write_json(out, owner, repo, rev, hash, args) + let (values, hash) = self.fetch_fod(url, &rev, &args)?; + self.write_json(out, values, rev, hash, args) } } -pub trait SimpleFlakeFetcher<'a>: SimpleFetcher<'a> { +pub trait SimpleFlakeFetcher<'a>: SimpleFetcher<'a, 2> { + const KEYS: [&'static str; 2] = ["owner", "repo"]; const FLAKE_TYPE: &'static str; - fn fetch(&'a self, url: &Url, rev: &str) -> Result<(String, String, String)> { - let (owner, repo) = self - .get_repo(url) + fn fetch(&'a self, url: &'a Url, rev: &str) -> Result<([&str; 2], String)> { + let [owner, repo] = self + .get_values(url) .with_context(|| format!("failed to parse {url} as a {} url", Self::FLAKE_TYPE))?; let hash = flake_prefetch(if let Some(host) = self.host() { @@ -160,82 +177,82 @@ pub trait SimpleFlakeFetcher<'a>: SimpleFetcher<'a> { format!("{}:{owner}/{repo}/{rev}", Self::FLAKE_TYPE) })?; - Ok((owner, repo, hash)) + Ok(([owner, repo], hash)) } fn fetch_nix_impl( &'a self, out: &mut impl Write, - url: Url, + url: &'a Url, rev: String, args: Vec<(String, String)>, indent: String, ) -> Result<()> { - let (owner, repo, hash) = if args.is_empty() { - self.fetch(&url, &rev)? + let (values, hash) = if args.is_empty() { + self.fetch(url, &rev)? } else { - self.fetch_fod(&url, &rev, &args)? + self.fetch_fod(url, &rev, &args)? }; - self.write_nix(out, owner, repo, rev, hash, args, indent) + self.write_nix(out, values, rev, hash, args, indent) } fn fetch_json_impl( &'a self, out: &mut impl Write, - url: Url, + url: &'a Url, rev: String, args: Vec<(String, String)>, ) -> Result<()> { - let (owner, repo, hash) = if args.is_empty() { - self.fetch(&url, &rev)? + let (values, hash) = if args.is_empty() { + self.fetch(url, &rev)? } else { - self.fetch_fod(&url, &rev, &args)? + self.fetch_fod(url, &rev, &args)? }; - self.write_json(out, owner, repo, rev, hash, args) + self.write_json(out, values, rev, hash, args) } } -pub trait SimpleUrlFetcher<'a>: SimpleFetcher<'a> { - fn get_url(&self, owner: &str, repo: &str, rev: &str) -> String; +pub trait SimpleUrlFetcher<'a, const N: usize = 2>: SimpleFetcher<'a, N> { + fn get_url(&self, values: [&str; N], rev: &str) -> String; - fn fetch(&'a self, url: &Url, rev: &str) -> Result<(String, String, String)> { - let (owner, repo) = self - .get_repo(url) + fn fetch(&'a self, url: &'a Url, rev: &str) -> Result<([&str; N], String)> { + let values = self + .get_values(url) .with_context(|| format!("failed to parse {url}"))?; - let hash = url_prefetch(self.get_url(&owner, &repo, rev))?; - Ok((owner, repo, hash)) + let hash = url_prefetch(self.get_url(values, rev))?; + Ok((values, hash)) } fn fetch_nix_impl( &'a self, out: &mut impl Write, - url: Url, + url: &'a Url, rev: String, args: Vec<(String, String)>, indent: String, ) -> Result<()> { - let (owner, repo, hash) = if args.is_empty() { - self.fetch(&url, &rev)? + let (values, hash) = if args.is_empty() { + self.fetch(url, &rev)? } else { - self.fetch_fod(&url, &rev, &args)? + self.fetch_fod(url, &rev, &args)? }; - self.write_nix(out, owner, repo, rev, hash, args, indent) + self.write_nix(out, values, rev, hash, args, indent) } fn fetch_json_impl( &'a self, out: &mut impl Write, - url: Url, + url: &'a Url, rev: String, args: Vec<(String, String)>, ) -> Result<()> { - let (owner, repo, hash) = if args.is_empty() { - self.fetch(&url, &rev)? + let (values, hash) = if args.is_empty() { + self.fetch(url, &rev)? } else { - self.fetch_fod(&url, &rev, &args)? + self.fetch_fod(url, &rev, &args)? }; - self.write_json(out, owner, repo, rev, hash, args) + self.write_json(out, values, rev, hash, args) } } diff --git a/src/fetcher/bitbucket.rs b/src/fetcher/bitbucket.rs index 10d223e..9f8b50d 100644 --- a/src/fetcher/bitbucket.rs +++ b/src/fetcher/bitbucket.rs @@ -7,6 +7,7 @@ pub struct FetchFromBitBucket; impl_fetcher!(FetchFromBitBucket); impl<'a> SimpleFetcher<'a> for FetchFromBitBucket { + const KEYS: [&'static str; 2] = ["owner", "repo"]; const NAME: &'static str = "fetchFromBitBucket"; fn host(&'a self) -> Option<&'a str> { @@ -15,7 +16,7 @@ impl<'a> SimpleFetcher<'a> for FetchFromBitBucket { } impl<'a> SimpleUrlFetcher<'a> for FetchFromBitBucket { - fn get_url(&self, owner: &str, repo: &str, rev: &str) -> String { + fn get_url(&self, [owner, repo]: [&str; 2], rev: &str) -> String { format!("https://bitbucket.org/{owner}/{repo}/get/{rev}.tar.gz") } } diff --git a/src/fetcher/gitea.rs b/src/fetcher/gitea.rs index 775db9d..48bf4f1 100644 --- a/src/fetcher/gitea.rs +++ b/src/fetcher/gitea.rs @@ -7,6 +7,7 @@ pub struct FetchFromGitea(pub String); impl_fetcher!(FetchFromGitea); impl<'a> SimpleFetcher<'a> for FetchFromGitea { + const KEYS: [&'static str; 2] = ["owner", "repo"]; const NAME: &'static str = "fetchFromGitea"; fn host(&'a self) -> Option<&'a str> { @@ -15,7 +16,7 @@ impl<'a> SimpleFetcher<'a> for FetchFromGitea { } impl<'a> SimpleUrlFetcher<'a> for FetchFromGitea { - fn get_url(&self, owner: &str, repo: &str, rev: &str) -> String { + fn get_url(&self, [owner, repo]: [&str; 2], rev: &str) -> String { format!("https://{}/{owner}/{repo}/archive/{rev}.tar.gz", self.0) } } diff --git a/src/fetcher/github.rs b/src/fetcher/github.rs index fc509f7..483e1af 100644 --- a/src/fetcher/github.rs +++ b/src/fetcher/github.rs @@ -8,6 +8,7 @@ impl_fetcher!(FetchFromGitHub); impl<'a> SimpleFetcher<'a> for FetchFromGitHub { const HOST_KEY: &'static str = "githubBase"; + const KEYS: [&'static str; 2] = ["owner", "repo"]; const NAME: &'static str = "fetchFromGitHub"; fn host(&'a self) -> Option<&'a str> { diff --git a/src/fetcher/gitlab.rs b/src/fetcher/gitlab.rs index 287abcb..8a16914 100644 --- a/src/fetcher/gitlab.rs +++ b/src/fetcher/gitlab.rs @@ -7,6 +7,7 @@ pub struct FetchFromGitLab(pub Option); impl_fetcher!(FetchFromGitLab); impl<'a> SimpleFetcher<'a> for FetchFromGitLab { + const KEYS: [&'static str; 2] = ["owner", "repo"]; const NAME: &'static str = "fetchFromGitLab"; fn host(&'a self) -> Option<&'a str> { diff --git a/src/fetcher/mod.rs b/src/fetcher/mod.rs index 854cd63..72cbfa7 100644 --- a/src/fetcher/mod.rs +++ b/src/fetcher/mod.rs @@ -25,7 +25,7 @@ pub trait Fetcher { fn fetch_nix( &self, out: &mut impl Write, - url: Url, + url: &Url, rev: String, args: Vec<(String, String)>, indent: String, @@ -33,7 +33,7 @@ pub trait Fetcher { fn fetch_json( &self, out: &mut impl Write, - url: Url, + url: &Url, rev: String, args: Vec<(String, String)>, ) -> Result<()>; @@ -57,7 +57,7 @@ macro_rules! impl_fetcher { fn fetch_nix( &self, out: &mut impl ::std::io::Write, - url: ::url::Url, + url: &::url::Url, rev: String, args: Vec<(String, String)>, indent: String, @@ -68,7 +68,7 @@ macro_rules! impl_fetcher { fn fetch_json( &self, out: &mut impl ::std::io::Write, - url: ::url::Url, + url: &::url::Url, rev: String, args: Vec<(String, String)>, ) -> ::anyhow::Result<()> { diff --git a/src/fetcher/sourcehut.rs b/src/fetcher/sourcehut.rs index 3937496..f615e90 100644 --- a/src/fetcher/sourcehut.rs +++ b/src/fetcher/sourcehut.rs @@ -7,6 +7,7 @@ pub struct FetchFromSourcehut(pub Option); impl_fetcher!(FetchFromSourcehut); impl<'a> SimpleFetcher<'a> for FetchFromSourcehut { + const KEYS: [&'static str; 2] = ["owner", "repo"]; const NAME: &'static str = "fetchFromSourcehut"; fn host(&'a self) -> Option<&'a str> { diff --git a/src/main.rs b/src/main.rs index cf51a65..8de583b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -95,9 +95,9 @@ fn main() -> Result<()> { let out = &mut stdout().lock(); let args = opts.args.into_iter().tuples().collect(); if opts.json { - fetcher.fetch_json(out, opts.url, opts.rev, args) + fetcher.fetch_json(out, &opts.url, opts.rev, args) } else { - fetcher.fetch_nix(out, opts.url, opts.rev, args, " ".repeat(opts.indent)) + fetcher.fetch_nix(out, &opts.url, opts.rev, args, " ".repeat(opts.indent)) }?; Ok(())