use std::ffi::CString;
use bitflags::bitflags;
use crate::error::{Error, ok_or_error};
use crate::{ExecStatus, bash};
bitflags! {
pub struct Eval: u32 {
const NONE = 0;
const NON_INTERACTIVE = bash::SEVAL_NONINT;
const INTERACTIVE = bash::SEVAL_INTERACT;
const NO_HISTORY = bash::SEVAL_NOHIST;
const NO_FREE = bash::SEVAL_NOFREE;
const RESET_LINE = bash::SEVAL_RESETLINE;
const PARSE_ONLY = bash::SEVAL_PARSEONLY;
const NO_LONGJMP = bash::SEVAL_NOLONGJMP;
const FUNCDEF = bash::SEVAL_FUNCDEF;
const ONE_COMMAND = bash::SEVAL_ONECMD;
const NO_HISTORY_EXPANSION = bash::SEVAL_NOHISTEXP;
}
}
pub fn string<S: AsRef<str>>(s: S) -> crate::Result<ExecStatus> {
let c_str = CString::new(s.as_ref()).unwrap();
ok_or_error(|| unsafe {
let flags = Eval::RESET_LINE.bits() as i32;
let ret = bash::scallop_evalstring(c_str.as_ptr(), flags);
if ret == 0 {
Ok(ExecStatus::Success)
} else {
Err(Error::Base(format!("failed sourcing string: exit status {ret}")))
}
})
}
pub fn file<S: AsRef<str>>(path: S) -> crate::Result<ExecStatus> {
let path = path.as_ref();
let c_str = CString::new(path).unwrap();
ok_or_error(|| unsafe {
let ret = bash::scallop_source_file(c_str.as_ptr());
if ret == 0 {
Ok(ExecStatus::Success)
} else {
Err(Error::Base(format!("failed sourcing file: {path}: exit status {ret}")))
}
})
}
#[cfg(test)]
mod tests {
use std::io::Write;
use tempfile::NamedTempFile;
use crate::source;
use crate::variables::optional;
#[test]
fn test_source_string() {
assert_eq!(optional("VAR"), None);
source::string("VAR=1").unwrap();
assert_eq!(optional("VAR").unwrap(), "1");
source::string("VAR=").unwrap();
assert_eq!(optional("VAR").unwrap(), "");
source::string("unset -v VAR").unwrap();
assert_eq!(optional("VAR"), None);
}
#[test]
fn test_source_string_error() {
let err = source::string("local VAR").unwrap_err();
assert_eq!(err.to_string(), "line 1: local: can only be used in a function");
let err = source::string("local VAR\nVAR=1").unwrap_err();
assert_eq!(optional("VAR").unwrap(), "1");
assert_eq!(err.to_string(), "line 1: local: can only be used in a function");
}
#[test]
fn test_source_file() {
assert_eq!(optional("VAR"), None);
let mut file = NamedTempFile::new().unwrap();
let path = file.path().to_str().unwrap().to_string();
writeln!(file, "VAR=1").unwrap();
source::file(&path).unwrap();
assert_eq!(optional("VAR").unwrap(), "1");
writeln!(file, "VAR=").unwrap();
source::file(&path).unwrap();
assert_eq!(optional("VAR").unwrap(), "");
writeln!(file, "unset -v VAR").unwrap();
source::file(&path).unwrap();
assert_eq!(optional("VAR"), None);
}
#[test]
fn test_source_file_error() {
let mut file = NamedTempFile::new().unwrap();
let path = file.path().to_str().unwrap().to_string();
writeln!(file, "local VAR").unwrap();
let err = source::file(&path).unwrap_err();
assert_eq!(
err.to_string(),
format!("{path}: line 1: local: can only be used in a function")
);
writeln!(file, "VAR=0").unwrap();
let err = source::file(&path).unwrap_err();
assert_eq!(optional("VAR").unwrap(), "0");
assert_eq!(
err.to_string(),
format!("{path}: line 1: local: can only be used in a function")
);
let mut file1 = NamedTempFile::new().unwrap();
let mut file2 = NamedTempFile::new().unwrap();
let file1_path = file1.path().to_str().unwrap().to_string();
let file2_path = file2.path().to_str().unwrap().to_string();
writeln!(file1, "source {file2_path:?}").unwrap();
writeln!(file2, "VAR=1\nlocal VAR\nVAR=2").unwrap();
let err = source::file(file1_path).unwrap_err();
assert_eq!(optional("VAR").unwrap(), "2");
assert_eq!(
err.to_string(),
format!("{file2_path}: line 2: local: can only be used in a function")
);
}
#[test]
fn test_source_file_error_longjmp() {
crate::builtins::set(["-e"]).unwrap();
let mut file = NamedTempFile::new().unwrap();
let path = file.path().to_str().unwrap().to_string();
writeln!(file, "local VAR\nVAR=1").unwrap();
assert!(source::file(path).is_err());
assert_eq!(optional("VAR"), None);
let mut file1 = NamedTempFile::new().unwrap();
let mut file2 = NamedTempFile::new().unwrap();
let file1_path = file1.path().to_str().unwrap().to_string();
let file2_path = file2.path().to_str().unwrap().to_string();
writeln!(file1, "source {file2_path}").unwrap();
writeln!(file2, "VAR=1\nlocal VAR\nVAR=2").unwrap();
let err = source::file(file1_path).unwrap_err();
assert_eq!(optional("VAR").unwrap(), "1");
assert_eq!(
err.to_string(),
format!("{file2_path}: line 2: local: can only be used in a function")
);
}
}