//! This module implements the serialisation of derivations into the
//! [ATerm][] format used by C++ Nix.
//!
//! [ATerm]: http://program-transformation.org/Tools/ATermFormat.html
use crate::aterm::escape_bytes;
use crate::derivation::{ca_kind_prefix, output::Output};
use bstr::BString;
use std::{
collections::{BTreeMap, BTreeSet},
io,
io::Error,
io::Write,
};
pub const DERIVATION_PREFIX: &str = "Derive";
pub const PAREN_OPEN: char = '(';
pub const PAREN_CLOSE: char = ')';
pub const BRACKET_OPEN: char = '[';
pub const BRACKET_CLOSE: char = ']';
pub const COMMA: char = ',';
pub const QUOTE: char = '"';
// Writes a character to the writer.
pub(crate) fn write_char(writer: &mut impl Write, c: char) -> io::Result<()> {
let mut buf = [0; 4];
let b = c.encode_utf8(&mut buf).as_bytes();
writer.write_all(b)
}
// Write a string `s` as a quoted field to the writer.
// The `escape` argument controls whether escaping will be skipped.
// This is the case if `s` is known to only contain characters that need no
// escaping.
pub(crate) fn write_field<S: AsRef<[u8]>>(
writer: &mut impl Write,
s: S,
escape: bool,
) -> io::Result<()> {
write_char(writer, QUOTE)?;
if !escape {
writer.write_all(s.as_ref())?;
} else {
writer.write_all(&escape_bytes(s.as_ref()))?;
}
write_char(writer, QUOTE)?;
Ok(())
}
fn write_array_elements<S: AsRef<[u8]>>(
writer: &mut impl Write,
elements: &[S],
) -> Result<(), io::Error> {
for (index, element) in elements.iter().enumerate() {
if index > 0 {
write_char(writer, COMMA)?;
}
write_field(writer, element, true)?;
}
Ok(())
}
pub(crate) fn write_outputs(
writer: &mut impl Write,
outputs: &BTreeMap<String, Output>,
) -> Result<(), io::Error> {
write_char(writer, BRACKET_OPEN)?;
for (ii, (output_name, output)) in outputs.iter().enumerate() {
if ii > 0 {
write_char(writer, COMMA)?;
}
write_char(writer, PAREN_OPEN)?;
let mut elements: Vec<&str> = vec![output_name, &output.path];
let (mode_and_algo, digest) = match &output.ca_hash {
Some(ca_hash) => (
format!("{}{}", ca_kind_prefix(ca_hash), ca_hash.digest().algo()),
data_encoding::HEXLOWER.encode(ca_hash.digest().digest_as_bytes()),
),
None => ("".to_string(), "".to_string()),
};
elements.push(&mode_and_algo);
elements.push(&digest);
write_array_elements(writer, &elements)?;
write_char(writer, PAREN_CLOSE)?;
}
write_char(writer, BRACKET_CLOSE)?;
Ok(())
}
pub(crate) fn write_input_derivations(
writer: &mut impl Write,
input_derivations: &BTreeMap<String, BTreeSet<String>>,
) -> Result<(), io::Error> {
write_char(writer, BRACKET_OPEN)?;
for (ii, (input_derivation, output_names)) in input_derivations.iter().enumerate() {
if ii > 0 {
write_char(writer, COMMA)?;
}
write_char(writer, PAREN_OPEN)?;
write_field(writer, input_derivation.as_str(), false)?;
write_char(writer, COMMA)?;
write_char(writer, BRACKET_OPEN)?;
write_array_elements(
writer,
&output_names
.iter()
.map(String::as_bytes)
.collect::<Vec<_>>(),
)?;
write_char(writer, BRACKET_CLOSE)?;
write_char(writer, PAREN_CLOSE)?;
}
write_char(writer, BRACKET_CLOSE)?;
Ok(())
}
pub(crate) fn write_input_sources(
writer: &mut impl Write,
input_sources: &BTreeSet<String>,
) -> Result<(), io::Error> {
write_char(writer, BRACKET_OPEN)?;
write_array_elements(
writer,
&input_sources.iter().map(String::from).collect::<Vec<_>>(),
)?;
write_char(writer, BRACKET_CLOSE)?;
Ok(())
}
pub(crate) fn write_system(writer: &mut impl Write, platform: &str) -> Result<(), Error> {
write_field(writer, platform, true)?;
Ok(())
}
pub(crate) fn write_builder(writer: &mut impl Write, builder: &str) -> Result<(), Error> {
write_field(writer, builder, true)?;
Ok(())
}
pub(crate) fn write_arguments(
writer: &mut impl Write,
arguments: &[String],
) -> Result<(), io::Error> {
write_char(writer, BRACKET_OPEN)?;
write_array_elements(
writer,
&arguments
.iter()
.map(|s| s.as_bytes().to_vec().into())
.collect::<Vec<BString>>(),
)?;
write_char(writer, BRACKET_CLOSE)?;
Ok(())
}
pub(crate) fn write_environment<E, K, V>(
writer: &mut impl Write,
environment: E,
) -> Result<(), io::Error>
where
E: IntoIterator<Item = (K, V)>,
K: AsRef<[u8]>,
V: AsRef<[u8]>,
{
write_char(writer, BRACKET_OPEN)?;
for (i, (k, v)) in environment.into_iter().enumerate() {
if i > 0 {
write_char(writer, COMMA)?;
}
write_char(writer, PAREN_OPEN)?;
write_field(writer, k, false)?;
write_char(writer, COMMA)?;
write_field(writer, v, true)?;
write_char(writer, PAREN_CLOSE)?;
}
write_char(writer, BRACKET_CLOSE)?;
Ok(())
}