use thiserror::Error;
use tatara_nix::{Artifact, MultiSynthesizer};
use crate::set::{PackageSet, PackageSetError};
#[derive(Debug, Error)]
pub enum NixpkgsMirrorError {
#[error("package set: {0}")]
Set(#[from] PackageSetError),
#[error("package missing while enumerating: {0}")]
EnumerationGap(String),
}
pub struct NixpkgsMirror {
pub out_prefix: String,
pub header: String,
}
impl Default for NixpkgsMirror {
fn default() -> Self {
Self {
out_prefix: "pkgs".into(),
header: "; AUTO-GENERATED by tatara-pkgs — do not hand-edit.\n\
; Source of truth: the upstream PackageSet pin in tend config.\n\n"
.into(),
}
}
}
impl NixpkgsMirror {
pub fn new() -> Self {
Self::default()
}
pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
self.out_prefix = prefix.into();
self
}
pub fn render_one(&self, name: &str, pkg_set_expr: Option<&str>) -> Artifact {
let path = format!("{}/{}.tl", self.out_prefix, name_to_file(name));
let pkg_set_line = match pkg_set_expr {
Some(expr) => format!(
" :bridge (:attr-path \"{}\" :pkg-set {})\n",
name,
nix_string_literal(expr)
),
None => format!(" :bridge (:attr-path \"{}\")\n", name),
};
let content = format!(
"{}(defderivation\n :name \"{}\"\n{})\n",
self.header, name, pkg_set_line
);
Artifact::new(path, content)
}
}
fn nix_string_literal(s: &str) -> String {
format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
}
fn name_to_file(name: &str) -> String {
name.replace('.', "/")
}
impl MultiSynthesizer for NixpkgsMirror {
type Input = dyn PackageSet;
fn generate_all(&self, input: &Self::Input) -> Vec<Artifact> {
let mut out = Vec::new();
for name in input.names() {
let d = match input.get(&name) {
Ok(Some(d)) => d,
_ => continue, };
let pkg_set_expr = d.bridge.as_ref().and_then(|b| b.pkg_set.clone());
out.push(self.render_one(&name, pkg_set_expr.as_deref()));
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::NixpkgsBridge;
#[test]
fn mirror_emits_one_artifact_per_package() {
let src = NixpkgsBridge::new()
.with_names(vec!["hello".into(), "bash".into()])
.with_label("nixpkgs");
let mirror = NixpkgsMirror::new().with_prefix("pkgs");
let arts = mirror.generate_all(&src);
assert_eq!(arts.len(), 2);
let paths: Vec<_> = arts.iter().map(|a| a.path.as_str()).collect();
assert!(paths.contains(&"pkgs/hello.tl"));
assert!(paths.contains(&"pkgs/bash.tl"));
}
#[test]
fn emitted_tl_reads_back_via_tatara_lisp() {
let mirror = NixpkgsMirror::new();
let art = mirror.render_one("hello", None);
let forms = tatara_lisp::read(&art.content).unwrap();
let last = forms.last().unwrap();
let list = last.as_list().unwrap();
assert_eq!(list[0].as_symbol(), Some("defderivation"));
}
#[test]
fn dotted_names_map_to_nested_directory_paths() {
let mirror = NixpkgsMirror::new().with_prefix("pkgs");
let art = mirror.render_one("python3Packages.requests", None);
assert_eq!(art.path, "pkgs/python3Packages/requests.tl");
}
#[test]
fn custom_pkg_set_is_quoted_into_emitted_form() {
let mirror = NixpkgsMirror::new();
let art = mirror.render_one("mypkg", Some("import ./release.nix {}"));
assert!(art.content.contains(":pkg-set \"import ./release.nix {}\""));
}
}