From eafb9ebb320f0f2f4f4000cf5ebfde80f04a8bb7 Mon Sep 17 00:00:00 2001 From: figsoda Date: Thu, 29 Dec 2022 21:04:19 -0500 Subject: [PATCH] initial implementation --- CHANGELOG.md | 4 + Cargo.lock | 470 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 15 +- README.md | 19 ++ src/cli.rs | 32 +++ src/fetcher/git.rs | 8 + src/fetcher/github.rs | 12 + src/fetcher/gitlab.rs | 12 + src/fetcher/hg.rs | 8 + src/fetcher/mod.rs | 130 +++++++++++ src/fetcher/sourcehut.rs | 12 + src/main.rs | 68 +++++- 12 files changed, 786 insertions(+), 4 deletions(-) create mode 100644 src/cli.rs create mode 100644 src/fetcher/git.rs create mode 100644 src/fetcher/github.rs create mode 100644 src/fetcher/gitlab.rs create mode 100644 src/fetcher/hg.rs create mode 100644 src/fetcher/mod.rs create mode 100644 src/fetcher/sourcehut.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 825c32f..6751d7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,5 @@ # Changelog + +## v0.1.0 - 2022-12-29 + +First release diff --git a/Cargo.lock b/Cargo.lock index 6321ec2..60772fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,476 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + +[[package]] +name = "clap" +version = "4.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", + "terminal_size", + "unicase", + "unicode-width", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indoc" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780" + +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "nurl" version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "indoc", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.36.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" +dependencies = [ + "rustix", + "windows-sys", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml index 8296beb..f39ceb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,15 +3,24 @@ name = "nurl" version = "0.1.0" authors = ["figsoda "] edition = "2021" -description = "" +description = "Generate Nix fetcher calls from repository URLs" readme = "README.md" homepage = "https://github.com/nix-community/nurl" repository = "https://github.com/nix-community/nurl" license = "MPL-2.0" -keywords = [] -categories = [] +keywords = ["cli", "fetch", "git", "nix", "prefetch"] +categories = ["command-line-utilities"] [dependencies] +anyhow = "1.0.68" +indoc = "1.0.8" +serde = { version = "1.0.152", features = ["derive"] } +serde_json = "1.0.91" +url = "2.3.1" + +[dependencies.clap] +version = "4.0.32" +features = ["cargo", "derive", "unicode", "wrap_help"] [profile.release] lto = true diff --git a/README.md b/README.md index c3e3905..7e21586 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ [![license](https://img.shields.io/badge/license-MPL--2.0-blue?style=flat-square)](https://www.mozilla.org/en-US/MPL/2.0) [![ci](https://img.shields.io/github/actions/workflow/status/nix-community/nurl/ci.yml?label=ci&logo=github-actions&style=flat-square)](https://github.com/nix-community/nurl/actions?query=workflow:ci) +Generate Nix fetcher calls from repository URLs + ## Installation @@ -27,6 +29,23 @@ cargo build --release ## Usage +``` +Generate Nix fetcher calls from repository URLs +https://github.com/nix-community/nurl + +Usage: nurl [OPTIONS] + +Arguments: + URL to the repository to be fetched + the revision or reference to be fetched + +Options: + -f, --fetcher specify the fetcher function instead of inferring from the URL [possible values: fetchFromGitHub, fetchFromGitLab, fetchFromSourcehut, fetchgit, fetchhg] + -i, --indent extra indentation (in number of spaces) [default: 0] + -h, --help Print help information + -V, --version Print version information +``` + ## Changelog See [CHANGELOG.md](CHANGELOG.md) diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..9dc8992 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,32 @@ +use clap::{Parser, ValueEnum}; +use url::Url; + +/// Generate Nix fetcher calls from repository URLs +/// https://github.com/nix-community/nurl +#[derive(Parser)] +#[command(version, verbatim_doc_comment)] +pub struct Opts { + /// URL to the repository to be fetched + pub url: Url, + + /// the revision or reference to be fetched + pub rev: String, + + /// specify the fetcher function instead of inferring from the URL + #[arg(short, long)] + pub fetcher: Option, + + /// extra indentation (in number of spaces) + #[arg(short, long, default_value_t = 0)] + pub indent: usize, +} + +#[derive(Clone, ValueEnum)] +#[clap(rename_all = "camelCase")] +pub enum Fetcher { + FetchFromGitHub, + FetchFromGitLab, + FetchFromSourcehut, + Fetchgit, + Fetchhg, +} diff --git a/src/fetcher/git.rs b/src/fetcher/git.rs new file mode 100644 index 0000000..e2a6054 --- /dev/null +++ b/src/fetcher/git.rs @@ -0,0 +1,8 @@ +use crate::fetcher::UrlFlakeFetcher; + +pub struct Fetchgit; + +impl UrlFlakeFetcher for Fetchgit { + const NAME: &'static str = "fetchgit"; + const FLAKE_TYPE: &'static str = "git"; +} diff --git a/src/fetcher/github.rs b/src/fetcher/github.rs new file mode 100644 index 0000000..28050bb --- /dev/null +++ b/src/fetcher/github.rs @@ -0,0 +1,12 @@ +use crate::fetcher::SimpleFlakeFetcher; + +pub struct FetchFromGitHub<'a>(pub Option<&'a str>); + +impl<'a> SimpleFlakeFetcher<'a> for FetchFromGitHub<'a> { + const FLAKE_TYPE: &'static str = "github"; + const NAME: &'static str = "fetchFromGitHub"; + + fn host(&self) -> Option<&'a str> { + self.0 + } +} diff --git a/src/fetcher/gitlab.rs b/src/fetcher/gitlab.rs new file mode 100644 index 0000000..0257597 --- /dev/null +++ b/src/fetcher/gitlab.rs @@ -0,0 +1,12 @@ +use crate::fetcher::SimpleFlakeFetcher; + +pub struct FetchFromGitLab<'a>(pub Option<&'a str>); + +impl<'a> SimpleFlakeFetcher<'a> for FetchFromGitLab<'a> { + const FLAKE_TYPE: &'static str = "gitlab"; + const NAME: &'static str = "fetchFromGitLab"; + + fn host(&self) -> Option<&'a str> { + self.0 + } +} diff --git a/src/fetcher/hg.rs b/src/fetcher/hg.rs new file mode 100644 index 0000000..35e422f --- /dev/null +++ b/src/fetcher/hg.rs @@ -0,0 +1,8 @@ +use crate::fetcher::UrlFlakeFetcher; + +pub struct Fetchhg; + +impl UrlFlakeFetcher for Fetchhg { + const FLAKE_TYPE: &'static str = "hg"; + const NAME: &'static str = "fetchhg"; +} diff --git a/src/fetcher/mod.rs b/src/fetcher/mod.rs new file mode 100644 index 0000000..fe184c9 --- /dev/null +++ b/src/fetcher/mod.rs @@ -0,0 +1,130 @@ +mod git; +mod github; +mod gitlab; +mod hg; +mod sourcehut; + +pub use git::Fetchgit; +pub use github::FetchFromGitHub; +pub use gitlab::FetchFromGitLab; +pub use hg::Fetchhg; +use indoc::writedoc; +pub use sourcehut::FetchFromSourcehut; + +use anyhow::{bail, Context, Result}; +use serde::Deserialize; +use url::Url; + +use std::{ + io::Write, + process::{Command, Output, Stdio}, +}; + +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>; +} + +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) +} + +pub trait SimpleFlakeFetcher<'a> { + const NAME: &'static str; + const FLAKE_TYPE: &'static str; + + fn host(&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 fetch_nix(&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))?; + + let hash = flake_prefetch(if let Some(host) = self.host() { + format!("{}:{owner}/{repo}/{rev}?host={}", Self::FLAKE_TYPE, host) + } else { + format!("{}:{owner}/{repo}/{rev}", Self::FLAKE_TYPE) + })?; + + writeln!(out, "{} {{", Self::NAME)?; + + if let Some(domain) = self.host() { + writeln!(out, r#"{indent} domain = "{domain}";"#)?; + } + + writedoc!( + out, + r#" + {indent} owner = "{owner}"; + {indent} repo = "{repo}"; + {indent} rev = "{rev}"; + {indent} hash = "{hash}"; + {indent}}}"#, + )?; + + Ok(()) + } +} + +pub(crate) trait UrlFlakeFetcher { + const NAME: &'static str; + const FLAKE_TYPE: &'static str; + + fn fetch_nix(&self, out: &mut impl Write, url: &Url, rev: String, indent: &str) -> Result<()> { + let hash = flake_prefetch(format!( + "{}+{url}?{}={rev}", + Self::FLAKE_TYPE, + if rev.len() == 40 { "rev" } else { "ref" }, + ))?; + + writedoc!( + out, + r#" + {} {{ + {indent} url = "{url}"; + {indent} rev = "{rev}"; + {indent} hash = "{hash}"; + {indent}}}"#, + Self::NAME, + )?; + + Ok(()) + } +} diff --git a/src/fetcher/sourcehut.rs b/src/fetcher/sourcehut.rs new file mode 100644 index 0000000..fc909a4 --- /dev/null +++ b/src/fetcher/sourcehut.rs @@ -0,0 +1,12 @@ +use crate::fetcher::SimpleFlakeFetcher; + +pub struct FetchFromSourcehut<'a>(pub Option<&'a str>); + +impl<'a> SimpleFlakeFetcher<'a> for FetchFromSourcehut<'a> { + const FLAKE_TYPE: &'static str = "sourcehut"; + const NAME: &'static str = "fetchFromSourcehut"; + + fn host(&self) -> Option<&'a str> { + self.0 + } +} diff --git a/src/main.rs b/src/main.rs index f328e4d..5ce0519 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1 +1,67 @@ -fn main() {} +mod cli; +mod fetcher; + +use anyhow::{bail, Result}; +use clap::Parser; +use url::Host; + +use crate::{ + cli::{Fetcher, Opts}, + fetcher::{ + FetchFromGitHub, FetchFromGitLab, FetchFromSourcehut, Fetchgit, Fetchhg, + SimpleFlakeFetcher, UrlFlakeFetcher, + }, +}; + +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)?; + } + (None, Some(Host::Domain("github.com"))) => { + FetchFromGitHub(None).fetch_nix(out, &opts.url, opts.rev, indent)?; + } + + (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(Host::Domain(host))) if host.starts_with("gitlab.") => { + FetchFromGitLab((host != "gitlab.com").then_some(host)) + .fetch_nix(out, &opts.url, opts.rev, indent)?; + } + + (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(Host::Domain("git.sr.ht"))) => { + FetchFromSourcehut(None).fetch_nix(out, &opts.url, opts.rev, indent)?; + } + + ( + Some(Fetcher::FetchFromGitHub | Fetcher::FetchFromGitLab | Fetcher::FetchFromSourcehut), + None, + ) => { + bail!("bad"); + } + + (Some(Fetcher::Fetchgit), _) | (None, _) => { + Fetchgit.fetch_nix(out, &opts.url, opts.rev, indent)?; + } + + (Some(Fetcher::Fetchhg), _) => { + Fetchhg.fetch_nix(out, &opts.url, opts.rev, indent)?; + } + } + + Ok(()) +}