use std::error;
use std::fmt;
use std::io::{self, Read};
use std::process::{Child, ChildStdout, Command, Stdio};
use xml;
use xml::common::{Position, TextPosition};
use xml::reader::{EventReader, XmlEvent};
use crate::package::{PathOrigin, StorePath};
pub fn query_packages(
nixpkgs: &str,
system: Option<&str>,
scope: Option<&str>,
show_trace: bool,
) -> PackagesQuery<ChildStdout> {
let mut cmd = Command::new("nix-env");
cmd.arg("-qaP")
.arg("--out-path")
.arg("--xml")
.arg("--arg")
.arg("config")
.arg("{ allowAliases = false; }") .arg("--arg")
.arg("overlays")
.arg("[ ]")
.arg("--file")
.arg(nixpkgs)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null());
if let Some(system) = system {
cmd.arg("--argstr").arg("system").arg(system);
}
if let Some(scope) = scope {
cmd.arg("-A").arg(scope);
}
if show_trace {
cmd.arg("--show-trace");
}
PackagesQuery {
parser: None,
child: None,
cmd: Some(cmd),
}
}
pub struct PackagesQuery<R: Read> {
parser: Option<PackagesParser<R>>,
child: Option<Child>,
cmd: Option<Command>,
}
impl PackagesQuery<ChildStdout> {
fn ensure_initialized(&mut self) -> Result<(), Error> {
if let Some(mut cmd) = self.cmd.take() {
let mut child = cmd.spawn()?;
let stdout = child.stdout.take().expect("should have stdout pipe");
let parser = PackagesParser::new(stdout);
self.child = Some(child);
self.parser = Some(parser);
}
Ok(())
}
fn check_error(&mut self) -> Option<Error> {
let mut run = || {
let child = match self.child.take() {
Some(c) => c,
None => return Ok(()),
};
let result = child.wait_with_output()?;
if !result.status.success() {
let message = String::from_utf8_lossy(&result.stderr);
return Err(Error::Command(format!(
"nix-env failed with {}:\n{}",
result.status, message,
)));
}
Ok(())
};
run().err()
}
}
impl Iterator for PackagesQuery<ChildStdout> {
type Item = Result<StorePath, Error>;
fn next(&mut self) -> Option<Self::Item> {
if let Err(e) = self.ensure_initialized() {
return Some(Err(e));
}
self.parser.take().and_then(|mut parser| {
parser
.next()
.map(|v| {
self.parser = Some(parser);
v.map_err(|e| self.check_error().unwrap_or_else(|| Error::from(e)))
})
.or_else(|| {
self.parser = None;
self.check_error().map(Err)
})
})
}
}
struct PackagesParser<R: Read> {
events: EventReader<R>,
current_item: Option<(String, String)>,
}
#[derive(Debug)]
pub struct ParserError {
position: TextPosition,
kind: ParserErrorKind,
}
#[derive(Debug)]
pub enum ParserErrorKind {
MissingParent {
element_name: String,
expected_parent: String,
},
ParentNotAllowed {
element_name: String,
found_parent: String,
},
MissingAttribute {
element_name: String,
attribute_name: String,
},
MissingStartTag { element_name: String },
XmlError { error: xml::reader::Error },
InvalidStorePath { path: String },
}
impl fmt::Display for ParserError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::ParserErrorKind::*;
write!(f, "error at {}: ", self.position)?;
match self.kind {
MissingParent {
ref element_name,
ref expected_parent,
} => {
write!(
f,
"element {} appears outside of expected parent {}",
element_name, expected_parent
)
}
ParentNotAllowed {
ref element_name,
ref found_parent,
} => {
write!(
f,
"element {} must not appear as child of {}",
element_name, found_parent
)
}
MissingAttribute {
ref element_name,
ref attribute_name,
} => {
write!(
f,
"element {} must have an attribute named {}",
element_name, attribute_name
)
}
MissingStartTag { ref element_name } => {
write!(f, "element {} does not have a start tag", element_name)
}
XmlError { ref error } => write!(f, "document not well-formed: {}", error),
InvalidStorePath { ref path } => {
write!(
f,
"store path does not match expected format /prefix/hash-name: {}",
path
)
}
}
}
}
impl<R: Read> PackagesParser<R> {
pub fn new(reader: R) -> PackagesParser<R> {
PackagesParser {
events: EventReader::new(reader),
current_item: None,
}
}
fn err(&self, kind: ParserErrorKind) -> ParserError {
ParserError {
position: self.events.position(),
kind,
}
}
fn next_err(&mut self) -> Result<Option<StorePath>, ParserError> {
use self::ParserErrorKind::*;
use self::XmlEvent::*;
loop {
let event = self
.events
.next()
.map_err(|e| self.err(XmlError { error: e }))?;
match event {
StartElement {
name: element_name,
attributes,
..
} => {
if element_name.local_name == "item" {
if self.current_item.is_some() {
return Err(self.err(ParentNotAllowed {
element_name: "item".to_string(),
found_parent: "item".to_string(),
}));
}
let mut attr_path = None;
let mut system = None;
for attr in attributes {
if attr.name.local_name == "attrPath" {
attr_path = Some(attr.value);
continue;
}
if attr.name.local_name == "system" {
system = Some(attr.value);
continue;
}
}
let attr_path = attr_path.ok_or_else(|| {
self.err(MissingAttribute {
element_name: "item".into(),
attribute_name: "attrPath".into(),
})
})?;
let system = system.ok_or_else(|| {
self.err(MissingAttribute {
element_name: "item".into(),
attribute_name: "system".into(),
})
})?;
self.current_item = Some((attr_path, system));
continue;
}
if element_name.local_name == "output" {
if let Some((item, system)) = self.current_item.clone() {
let mut output_name = None;
let mut output_path = None;
for attr in attributes {
if attr.name.local_name == "name" {
output_name = Some(attr.value);
continue;
}
if attr.name.local_name == "path" {
output_path = Some(attr.value);
continue;
}
}
let output_name = output_name.ok_or_else(|| {
self.err(MissingAttribute {
element_name: "output".into(),
attribute_name: "name".into(),
})
})?;
let output_path = output_path.ok_or_else(|| {
self.err(MissingAttribute {
element_name: "output".into(),
attribute_name: "path".into(),
})
})?;
let origin = PathOrigin {
attr: item,
output: output_name,
toplevel: true,
system: Some(system),
};
let store_path = StorePath::parse(origin, &output_path);
let store_path = store_path
.ok_or_else(|| self.err(InvalidStorePath { path: output_path }))?;
return Ok(Some(store_path));
} else {
return Err(self.err(MissingParent {
element_name: "output".into(),
expected_parent: "item".into(),
}));
}
}
}
EndElement { name: element_name } => {
if element_name.local_name == "item" {
if self.current_item.is_none() {
return Err(self.err(MissingStartTag {
element_name: "item".into(),
}));
}
self.current_item = None
}
}
EndDocument => break,
_ => {}
}
}
Ok(None)
}
}
impl<R: Read> Iterator for PackagesParser<R> {
type Item = Result<StorePath, ParserError>;
fn next(&mut self) -> Option<Result<StorePath, ParserError>> {
match self.next_err() {
Err(e) => Some(Err(e)),
Ok(Some(i)) => Some(Ok(i)),
Ok(None) => None,
}
}
}
#[derive(Debug)]
pub enum Error {
Parse(ParserError),
Io(io::Error),
Command(String),
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::Parse(_) => "nix-env output parse error",
Error::Io(_) => "io error",
Error::Command(_) => "nix-env error",
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::Error::*;
match *self {
Parse(ref e) => write!(f, "parsing XML output of nix-env failed: {}", e),
Io(ref e) => write!(f, "IO error: {}", e),
Command(ref e) => write!(f, "nix-env failed with error: {}", e),
}
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::Io(err)
}
}
impl From<ParserError> for Error {
fn from(err: ParserError) -> Error {
Error::Parse(err)
}
}