use std::fmt;
use std::process::Command;
#[derive(Clone, Debug, Default)]
pub struct Fedrq {
pub branch: Option<String>,
pub repo: Option<String>,
}
#[derive(Debug)]
pub enum Error {
Spawn(std::io::Error),
Exit { status: std::process::ExitStatus, stderr: String },
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Spawn(e) => write!(f, "failed to run fedrq: {e}"),
Error::Exit { status, stderr } => {
write!(f, "fedrq exited with {status}")?;
if !stderr.is_empty() {
write!(f, ": {stderr}")?;
}
Ok(())
}
}
}
}
impl std::error::Error for Error {}
impl Fedrq {
fn apply_opts(&self, cmd: &mut Command) {
if let Some(branch) = &self.branch {
cmd.args(["-b", branch]);
}
if let Some(repo) = &self.repo {
cmd.args(["-r", repo]);
}
}
fn run(cmd: &mut Command) -> Result<Vec<String>, Error> {
let output = cmd.output().map_err(Error::Spawn)?;
if !output.status.success() {
return Err(Error::Exit {
status: output.status,
stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(),
});
}
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<String> = stdout
.lines()
.map(|l| l.trim().to_string())
.filter(|l| !l.is_empty())
.collect();
Ok(lines)
}
fn subpkgs_query(&self, formatter: &str, srpm: &str) -> Result<Vec<String>, Error> {
let mut cmd = Command::new("fedrq");
cmd.args(["subpkgs", "-S", "-F", formatter]);
self.apply_opts(&mut cmd);
cmd.arg(srpm);
Self::run(&mut cmd)
}
pub fn subpkgs_provides(&self, srpm: &str) -> Result<Vec<String>, Error> {
self.subpkgs_query("provides", srpm)
}
pub fn subpkgs_requires(&self, srpm: &str) -> Result<Vec<String>, Error> {
self.subpkgs_query("requires", srpm)
}
pub fn subpkgs_names(&self, srpm: &str) -> Result<Vec<String>, Error> {
self.subpkgs_query("name", srpm)
}
pub fn whatrequires(&self, packages: &[String]) -> Result<Vec<String>, Error> {
let mut cmd = Command::new("fedrq");
cmd.args(["whatrequires", "-F", "source_name"]);
self.apply_opts(&mut cmd);
cmd.args(packages);
Self::run(&mut cmd)
}
pub fn src_buildrequires(&self, srpm: &str) -> Result<Vec<String>, Error> {
let mut cmd = Command::new("fedrq");
cmd.args(["pkgs", "--src", "-F", "requires"]);
self.apply_opts(&mut cmd);
cmd.arg(srpm);
Self::run(&mut cmd)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_command_defaults() {
let fq = Fedrq::default();
assert!(fq.branch.is_none());
assert!(fq.repo.is_none());
}
#[test]
fn display_spawn_error() {
let err = Error::Spawn(std::io::Error::new(std::io::ErrorKind::NotFound, "not found"));
let msg = err.to_string();
assert!(msg.contains("failed to run fedrq"));
assert!(msg.contains("not found"));
}
#[test]
fn display_exit_error_with_stderr() {
let status = Command::new("false").status().expect("can run false");
let err = Error::Exit {
status,
stderr: "package not found".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("fedrq exited with"));
assert!(msg.contains("package not found"));
}
#[test]
fn display_exit_error_empty_stderr() {
let status = Command::new("false").status().expect("can run false");
let err = Error::Exit {
status,
stderr: String::new(),
};
let msg = err.to_string();
assert!(msg.contains("fedrq exited with"));
assert!(!msg.contains("package not found"));
}
}