pub(crate) mod parser;
use std::{
io::ErrorKind,
path::Path,
process::{Command, Stdio},
};
use fluent_i18n::t;
use log::debug;
pub use parser::{BridgeOutput, ClearableValue, Keyword, RawPackageName, Value};
use which::which;
use crate::error::Error;
const DEFAULT_SCRIPT_NAME: &str = "alpm-pkgbuild-bridge";
pub fn run_bridge_script(pkgbuild_path: &Path) -> Result<String, Error> {
if !pkgbuild_path.exists() {
let source = std::io::Error::new(ErrorKind::NotFound, "No such file or directory.");
return Err(Error::IoPath {
path: pkgbuild_path.to_path_buf(),
context: t!("error-io-path-check-pkgbuild"),
source,
});
}
let Some(filename) = pkgbuild_path.file_name() else {
return Err(Error::InvalidFile {
path: pkgbuild_path.to_owned(),
context: t!("error-no-filename"),
});
};
let metadata = pkgbuild_path.metadata().map_err(|source| Error::IoPath {
path: pkgbuild_path.to_owned(),
context: t!("error-io-get-metadata"),
source,
})?;
if !metadata.file_type().is_file() {
return Err(Error::InvalidFile {
path: pkgbuild_path.to_owned(),
context: t!("error-not-a-file"),
});
};
let script_path = which(DEFAULT_SCRIPT_NAME).map_err(|source| Error::ScriptNotFound {
script_name: DEFAULT_SCRIPT_NAME.to_string(),
source,
})?;
let mut command = Command::new(script_path);
if let Some(parent) = pkgbuild_path.parent() {
if parent != Path::new("") {
command.current_dir(parent);
}
}
let parameters = vec![filename.to_string_lossy().to_string()];
command.args(¶meters);
command.stdout(Stdio::piped());
command.stderr(Stdio::piped());
debug!(
"Spawning command '{DEFAULT_SCRIPT_NAME} {}'",
parameters.join(" ")
);
let child = command.spawn().map_err(|source| Error::Script {
context: t!("error-script-spawn"),
parameters: parameters.clone(),
source,
})?;
debug!("Waiting for '{DEFAULT_SCRIPT_NAME}' to finish");
let output = child.wait_with_output().map_err(|source| Error::Script {
context: t!("error-script-finish"),
parameters: parameters.clone(),
source,
})?;
if !output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
return Err(Error::ScriptExecution {
parameters,
stdout,
stderr,
});
}
String::from_utf8(output.stdout).map_err(Error::from)
}
#[cfg(test)]
mod tests {
use std::{fs::File, io::Write};
use tempfile::tempdir;
use testresult::TestResult;
use super::*;
#[test]
fn fail_on_directory() -> TestResult {
let tempdir = tempdir()?;
let temp_path = tempdir.path();
let result = run_bridge_script(temp_path);
let Err(error) = result else {
panic!("Expected an error, got {result:?} instead.");
};
let Error::InvalidFile { path, context } = error else {
panic!("Expected an InvalidFile error, got {error:?} instead.");
};
assert_eq!(temp_path, path);
assert_eq!(context, "Path doesn't point to a file");
Ok(())
}
#[test]
fn fail_on_missing_file() -> TestResult {
let tempdir = tempdir()?;
let temp_path = tempdir.path().join("Nonexistent");
let result = run_bridge_script(&temp_path);
let Err(error) = result else {
panic!("Expected an error, got {result:?} instead.");
};
let Error::IoPath { path, context, .. } = error else {
panic!("Expected an IoPath error, got {error:?} instead.");
};
assert_eq!(temp_path, path);
assert_eq!(context, "checking for PKGBUILD");
Ok(())
}
#[test]
fn fail_on_no_filename() -> TestResult {
let tempdir = tempdir()?;
let temp_path = tempdir.path().join("..");
let result = run_bridge_script(&temp_path);
let Err(error) = result else {
panic!("Expected an error, got {result:?} instead.");
};
let Error::InvalidFile { path, context } = error else {
panic!("Expected an InvalidFile error, got {error:?} instead.");
};
assert_eq!(temp_path, path);
assert_eq!(context, "No filename provided in path");
Ok(())
}
#[test]
fn fail_on_bridge_failure() -> TestResult {
let tempdir = tempdir()?;
let temp_path = tempdir.path().join("PKGBUILd");
let mut file = File::create_new(&temp_path)?;
file.write_all("<->#!%@!Definitely some invalid bash syntax.".as_bytes())?;
let result = run_bridge_script(&temp_path);
let Err(error) = result else {
panic!("Expected an error, got {result:?} instead.");
};
let Error::ScriptExecution { .. } = error else {
panic!("Expected an ScriptExecutionError error, got {error:?} instead.");
};
Ok(())
}
}