diff --git a/Cargo.lock b/Cargo.lock index 22ed527..ac3153a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,12 +172,6 @@ dependencies = [ "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" @@ -242,9 +236,9 @@ dependencies = [ "clap_complete", "clap_mangen", "enum_dispatch", - "indoc", "itertools", "owo-colors", + "rustc-hash", "serde", "serde_json", "url", @@ -325,6 +319,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.36.6" diff --git a/Cargo.toml b/Cargo.toml index c6bc748..bc9b339 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,9 @@ categories = ["command-line-utilities"] [dependencies] anyhow = "1.0.68" enum_dispatch = "0.3.9" -indoc = "1.0.8" itertools = "0.10.5" owo-colors = { version = "3.5.0", features = ["supports-colors"] } +rustc-hash = "1.1.0" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.91" url = "2.3.1" diff --git a/src/cli.rs b/src/cli.rs index dede912..755cb07 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -38,6 +38,27 @@ pub struct Opts { #[arg(short, long = "arg", num_args = 2, value_names = ["KEY", "VALUE"])] pub args: Vec, + /// overwrite arguments in the final output, + /// not taken into consideration when fetching the hash + /// + /// Note that nurl does not verify any of the overwrites, + /// for the final output to be valid, + /// the user should not overwrite anything that would change the hash + /// + /// examples: + /// {n} --overwrite repo pname + /// {n} --overwrite rev version + #[arg(short, long = "overwrite", num_args = 2, value_names = ["NAME", "EXPR"])] + pub overwrites: Vec, + + /// same as --overwrite, but accepts strings instead Nix expressions + /// + /// examples: + /// {n} --overwrite-str rev 'v${version}' + /// {n} --overwrite-str meta.homepage https://example.org + #[arg(short = 'O', long = "overwrite-str", num_args = 2, value_names = ["NAME", "STRING"])] + pub overwrites_str: Vec, + /// List all available fetchers #[arg(short, long, group = "command")] pub list_fetchers: bool, diff --git a/src/fetcher/mod.rs b/src/fetcher/mod.rs index 72f759c..0c2435f 100644 --- a/src/fetcher/mod.rs +++ b/src/fetcher/mod.rs @@ -20,6 +20,7 @@ pub use sourcehut::FetchFromSourcehut; use anyhow::Result; use enum_dispatch::enum_dispatch; +use rustc_hash::FxHashMap; use url::Url; use std::io::Write; @@ -32,6 +33,7 @@ pub trait Fetcher { url: &Url, rev: String, args: Vec<(String, String)>, + overwrites: FxHashMap, indent: String, ) -> Result<()>; fn fetch_json( @@ -40,6 +42,8 @@ pub trait Fetcher { url: &Url, rev: String, args: Vec<(String, String)>, + overwrites: Vec<(String, String)>, + overwrites_str: Vec<(String, String)>, ) -> Result<()>; } @@ -66,9 +70,10 @@ macro_rules! impl_fetcher { url: &::url::Url, rev: String, args: Vec<(String, String)>, + overwrites: ::rustc_hash::FxHashMap, indent: String, ) -> ::anyhow::Result<()> { - self.fetch_nix_impl(out, url, rev, args, indent) + self.fetch_nix_impl(out, url, rev, args, overwrites, indent) } fn fetch_json( @@ -77,8 +82,10 @@ macro_rules! impl_fetcher { url: &::url::Url, rev: String, args: Vec<(String, String)>, + overwrites: Vec<(String, String)>, + overwrites_str: Vec<(String, String)>, ) -> ::anyhow::Result<()> { - self.fetch_json_impl(out, url, rev, args) + self.fetch_json_impl(out, url, rev, args, overwrites, overwrites_str) } } }; diff --git a/src/main.rs b/src/main.rs index 0e3cce2..e8ee63a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod simple; use anyhow::{bail, Result}; use clap::{Parser, ValueEnum}; use itertools::Itertools; +use rustc_hash::FxHashMap; use crate::{ cli::{FetcherFunction, Opts}, @@ -100,9 +101,29 @@ 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, + opts.overwrites.into_iter().tuples().collect(), + opts.overwrites_str.into_iter().tuples().collect(), + ) } else { - fetcher.fetch_nix(out, &opts.url, opts.rev, args, " ".repeat(opts.indent)) + let mut overwrites: FxHashMap<_, _> = opts.overwrites.into_iter().tuples().collect(); + + for (key, value) in opts.overwrites_str.into_iter().tuples() { + overwrites.insert(key, format!(r#""{value}""#)); + } + + fetcher.fetch_nix( + out, + &opts.url, + opts.rev, + args, + overwrites, + " ".repeat(opts.indent), + ) }?; Ok(()) diff --git a/src/simple.rs b/src/simple.rs index 6a6a798..ebf4ce0 100644 --- a/src/simple.rs +++ b/src/simple.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result}; -use indoc::writedoc; use itertools::Itertools; +use rustc_hash::FxHashMap; use serde_json::json; use url::Url; @@ -73,27 +73,43 @@ pub trait SimpleFetcher<'a, const N: usize = 2> { rev: String, hash: String, args: Vec<(String, String)>, + overwrites: FxHashMap, indent: String, ) -> Result<()> { + let mut overwrites = overwrites; + writeln!(out, "{} {{", Self::NAME)?; - if let Some(host) = self.host() { + if let Some(host) = overwrites.remove(Self::HOST_KEY) { + writeln!(out, r#"{indent} {} = {host};"#, Self::HOST_KEY)?; + } else if let Some(host) = self.host() { writeln!(out, r#"{indent} {} = "{host}";"#, Self::HOST_KEY)?; } for (key, value) in Self::KEYS.iter().zip(values) { - writeln!(out, r#"{indent} {key} = "{value}";"#)?; + if let Some(value) = overwrites.remove(*key) { + writeln!(out, r#"{indent} {key} = {value};"#)?; + } else { + writeln!(out, r#"{indent} {key} = "{value}";"#)?; + } } - writedoc!( - out, - r#" - {indent} rev = "{rev}"; - {indent} hash = "{hash}"; - "# - )?; + if let Some(rev) = overwrites.remove("rev") { + writeln!(out, "{indent} rev = {rev};")?; + } else { + writeln!(out, r#"{indent} rev = "{rev}";"#)?; + } + if let Some(hash) = overwrites.remove("hash") { + writeln!(out, "{indent} hash = {hash};")?; + } else { + writeln!(out, r#"{indent} hash = "{hash}";"#)?; + } for (key, value) in args { + let value = overwrites.remove(&key).unwrap_or(value); + writeln!(out, "{indent} {key} = {value};")?; + } + for (key, value) in overwrites { writeln!(out, "{indent} {key} = {value};")?; } @@ -109,6 +125,8 @@ pub trait SimpleFetcher<'a, const N: usize = 2> { rev: String, hash: String, args: Vec<(String, String)>, + overwrites: Vec<(String, String)>, + overwrites_str: Vec<(String, String)>, ) -> Result<()> { let mut fetcher_args = json! ({ "rev": rev, @@ -130,6 +148,16 @@ pub trait SimpleFetcher<'a, const N: usize = 2> { }); } + for (key, value) in overwrites { + fetcher_args[key] = json!({ + "type": "nix", + "value": value, + }) + } + for (key, value) in overwrites_str { + fetcher_args[key] = json!(value); + } + serde_json::to_writer( out, &json!({ @@ -149,10 +177,11 @@ pub trait SimpleFodFetcher<'a, const N: usize = 2>: SimpleFetcher<'a, N> { url: &'a Url, rev: String, args: Vec<(String, String)>, + overwrites: FxHashMap, indent: String, ) -> Result<()> { let (values, hash) = self.fetch_fod(url, &rev, &args)?; - self.write_nix(out, values, rev, hash, args, indent) + self.write_nix(out, values, rev, hash, args, overwrites, indent) } fn fetch_json_impl( @@ -161,9 +190,11 @@ pub trait SimpleFodFetcher<'a, const N: usize = 2>: SimpleFetcher<'a, N> { url: &'a Url, rev: String, args: Vec<(String, String)>, + overwrites: Vec<(String, String)>, + overwrites_str: Vec<(String, String)>, ) -> Result<()> { let (values, hash) = self.fetch_fod(url, &rev, &args)?; - self.write_json(out, values, rev, hash, args) + self.write_json(out, values, rev, hash, args, overwrites, overwrites_str) } } @@ -198,6 +229,7 @@ pub trait SimpleFlakeFetcher<'a, const N: usize = 2>: SimpleFetcher<'a, N> { url: &'a Url, rev: String, args: Vec<(String, String)>, + overwrites: FxHashMap, indent: String, ) -> Result<()> { let (values, hash) = if args.is_empty() { @@ -206,7 +238,7 @@ pub trait SimpleFlakeFetcher<'a, const N: usize = 2>: SimpleFetcher<'a, N> { self.fetch_fod(url, &rev, &args)? }; - self.write_nix(out, values, rev, hash, args, indent) + self.write_nix(out, values, rev, hash, args, overwrites, indent) } fn fetch_json_impl( @@ -215,13 +247,15 @@ pub trait SimpleFlakeFetcher<'a, const N: usize = 2>: SimpleFetcher<'a, N> { url: &'a Url, rev: String, args: Vec<(String, String)>, + overwrites: Vec<(String, String)>, + overwrites_str: Vec<(String, String)>, ) -> Result<()> { let (values, hash) = if args.is_empty() { self.fetch(url, &rev)? } else { self.fetch_fod(url, &rev, &args)? }; - self.write_json(out, values, rev, hash, args) + self.write_json(out, values, rev, hash, args, overwrites, overwrites_str) } } @@ -242,6 +276,7 @@ pub trait SimpleUrlFetcher<'a, const N: usize = 2>: SimpleFetcher<'a, N> { url: &'a Url, rev: String, args: Vec<(String, String)>, + overwrites: FxHashMap, indent: String, ) -> Result<()> { let (values, hash) = if args.is_empty() { @@ -250,7 +285,7 @@ pub trait SimpleUrlFetcher<'a, const N: usize = 2>: SimpleFetcher<'a, N> { self.fetch_fod(url, &rev, &args)? }; - self.write_nix(out, values, rev, hash, args, indent) + self.write_nix(out, values, rev, hash, args, overwrites, indent) } fn fetch_json_impl( @@ -259,12 +294,14 @@ pub trait SimpleUrlFetcher<'a, const N: usize = 2>: SimpleFetcher<'a, N> { url: &'a Url, rev: String, args: Vec<(String, String)>, + overwrites: Vec<(String, String)>, + overwrites_str: Vec<(String, String)>, ) -> Result<()> { let (values, hash) = if args.is_empty() { self.fetch(url, &rev)? } else { self.fetch_fod(url, &rev, &args)? }; - self.write_json(out, values, rev, hash, args) + self.write_json(out, values, rev, hash, args, overwrites, overwrites_str) } }