150 lines
4.4 KiB
Rust
150 lines
4.4 KiB
Rust
|
extern crate serde_json;
|
||
|
|
||
|
use serde_json::Value;
|
||
|
use std::convert::TryFrom;
|
||
|
use std::ffi::OsString;
|
||
|
use std::io::{stderr, stdout, Error, ErrorKind, Write};
|
||
|
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||
|
use std::process::Command;
|
||
|
|
||
|
fn render_nix_string(s: &OsString) -> OsString {
|
||
|
let mut rendered = Vec::new();
|
||
|
|
||
|
rendered.extend(b"\"");
|
||
|
|
||
|
for b in s.as_os_str().as_bytes() {
|
||
|
match char::from(*b) {
|
||
|
'\"' => rendered.extend(b"\\\""),
|
||
|
'\\' => rendered.extend(b"\\\\"),
|
||
|
'$' => rendered.extend(b"\\$"),
|
||
|
_ => rendered.push(*b),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rendered.extend(b"\"");
|
||
|
|
||
|
OsString::from_vec(rendered)
|
||
|
}
|
||
|
|
||
|
fn render_nix_list(arr: &[OsString]) -> OsString {
|
||
|
let mut rendered = Vec::new();
|
||
|
|
||
|
rendered.extend(b"[ ");
|
||
|
|
||
|
for el in arr {
|
||
|
rendered.extend(render_nix_string(el).as_os_str().as_bytes());
|
||
|
rendered.extend(b" ");
|
||
|
}
|
||
|
|
||
|
rendered.extend(b"]");
|
||
|
|
||
|
OsString::from_vec(rendered)
|
||
|
}
|
||
|
|
||
|
/// Slightly overkill helper macro which takes a `Map<String, Value>` obtained
|
||
|
/// from `Value::Object` and an output name (`stderr` or `stdout`) as an
|
||
|
/// identifier. If a value exists for the given output in the object it gets
|
||
|
/// written to the appropriate output.
|
||
|
macro_rules! handle_set_output {
|
||
|
($map_name:ident, $output_name:ident) => {
|
||
|
match $map_name.get(stringify!($output_name)) {
|
||
|
Some(Value::String(s)) => $output_name().write_all(s.as_bytes()),
|
||
|
Some(_) => Err(Error::new(
|
||
|
ErrorKind::Other,
|
||
|
format!("Attribute {} must be a string!", stringify!($output_name)),
|
||
|
)),
|
||
|
None => Ok(()),
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn main() -> std::io::Result<()> {
|
||
|
let mut nix_args = Vec::new();
|
||
|
|
||
|
let mut args = std::env::args_os().into_iter();
|
||
|
let mut in_args = true;
|
||
|
|
||
|
let mut argv: Vec<OsString> = Vec::new();
|
||
|
|
||
|
// skip argv[0]
|
||
|
args.next();
|
||
|
|
||
|
loop {
|
||
|
let arg = match args.next() {
|
||
|
Some(a) => a,
|
||
|
None => break,
|
||
|
};
|
||
|
|
||
|
if !arg.to_str().map(|s| s.starts_with("-")).unwrap_or(false) {
|
||
|
in_args = false;
|
||
|
}
|
||
|
|
||
|
if in_args {
|
||
|
match (arg.to_str()) {
|
||
|
Some("--arg") | Some("--argstr") => {
|
||
|
nix_args.push(arg);
|
||
|
nix_args.push(args.next().unwrap());
|
||
|
nix_args.push(args.next().unwrap());
|
||
|
Ok(())
|
||
|
}
|
||
|
_ => Err(Error::new(ErrorKind::Other, "unknown argument")),
|
||
|
}?
|
||
|
} else {
|
||
|
argv.push(arg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if argv.len() < 1 {
|
||
|
Err(Error::new(ErrorKind::Other, "missing argv"))
|
||
|
} else {
|
||
|
let cd = std::env::current_dir()?.into_os_string();
|
||
|
|
||
|
nix_args.push(OsString::from("--arg"));
|
||
|
nix_args.push(OsString::from("currentDir"));
|
||
|
nix_args.push(cd);
|
||
|
|
||
|
nix_args.push(OsString::from("--arg"));
|
||
|
nix_args.push(OsString::from("argv"));
|
||
|
nix_args.push(render_nix_list(&argv[..]));
|
||
|
|
||
|
nix_args.push(OsString::from("--eval"));
|
||
|
nix_args.push(OsString::from("--strict"));
|
||
|
nix_args.push(OsString::from("--json"));
|
||
|
|
||
|
nix_args.push(argv[0].clone());
|
||
|
|
||
|
let run = Command::new("nix-instantiate").args(nix_args).output()?;
|
||
|
|
||
|
match serde_json::from_slice(&run.stdout[..]) {
|
||
|
Ok(Value::String(s)) => stdout().write_all(s.as_bytes()),
|
||
|
Ok(Value::Object(m)) => {
|
||
|
handle_set_output!(m, stdout)?;
|
||
|
handle_set_output!(m, stderr)?;
|
||
|
|
||
|
match m.get("exit") {
|
||
|
Some(Value::Number(n)) => {
|
||
|
let code = n.as_i64().and_then(|v| i32::try_from(v).ok());
|
||
|
|
||
|
match code {
|
||
|
Some(i) => std::process::exit(i),
|
||
|
None => {
|
||
|
Err(Error::new(ErrorKind::Other, "Attribute exit is not an i32"))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Some(_) => Err(Error::new(ErrorKind::Other, "exit must be a number")),
|
||
|
None => Ok(()),
|
||
|
}
|
||
|
}
|
||
|
Ok(_) => Err(Error::new(
|
||
|
ErrorKind::Other,
|
||
|
"output must be a string or an object",
|
||
|
)),
|
||
|
_ => {
|
||
|
stderr().write_all(&run.stderr[..]);
|
||
|
Err(Error::new(ErrorKind::Other, "internal nix error"))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|