use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::surface::error::CommandError;
use crate::{GeneratedLinkBody, SirnoConfig};
const RG_PREPROCESSOR_ARGV0_PREFIX: &str = "sirno-rg-preprocess-";
pub(crate) fn rg_args_to_strings(args: Vec<OsString>) -> Result<Vec<String>, CommandError> {
args.into_iter().map(|arg| arg.into_string().map_err(CommandError::RgArgumentNotUtf8)).collect()
}
#[cfg(test)]
pub(crate) fn rg_args_include_preprocessor(args: &[OsString]) -> bool {
args.iter()
.filter_map(|arg| arg.to_str())
.any(|arg| arg == "--pre" || arg.starts_with("--pre="))
}
pub(crate) fn resolve_lake_path_for_rg(
lake_path: Option<&Path>, config_path: &Path,
) -> Result<PathBuf, CommandError> {
if let Some(lake_path) = lake_path {
return Ok(lake_path.to_path_buf());
}
let config = SirnoConfig::from_file(config_path)?;
Ok(config.resolve_lake(config_path))
}
pub(crate) fn is_rg_preprocessor_invocation() -> bool {
env::args_os()
.next()
.and_then(|arg| PathBuf::from(arg).file_name().map(|name| name.to_os_string()))
.is_some_and(|name| name.to_string_lossy().starts_with(RG_PREPROCESSOR_ARGV0_PREFIX))
}
pub(crate) fn run_rg_preprocessor_from_env() -> Result<ExitCode, CommandError> {
let mut args = env::args_os().skip(1);
let Some(path) = args.next() else {
return Err(CommandError::RgPreprocessorArgumentCount);
};
if args.next().is_some() {
return Err(CommandError::RgPreprocessorArgumentCount);
}
run_rg_preprocessor(&PathBuf::from(path))
}
fn run_rg_preprocessor(path: &Path) -> Result<ExitCode, CommandError> {
let body = fs::read_to_string(path).map_err(|source| {
CommandError::ReadRgPreprocessorInput { path: path.to_path_buf(), source }
})?;
let masked = GeneratedLinkBody::new(&body).mask()?;
io::stdout().write_all(masked.as_bytes()).map_err(CommandError::WriteRgPreprocessorOutput)?;
Ok(ExitCode::SUCCESS)
}
#[derive(Debug)]
pub(crate) struct RgPreprocessorLink {
path: PathBuf,
}
impl RgPreprocessorLink {
pub(crate) fn create() -> Result<Self, CommandError> {
let current_exe = env::current_exe().map_err(CommandError::LocateCurrentExe)?;
let mut path = env::temp_dir();
path.push(format!(
"{RG_PREPROCESSOR_ARGV0_PREFIX}{}-{}",
std::process::id(),
current_time_nanos()
));
#[cfg(not(unix))]
if let Some(extension) = current_exe.extension() {
path.set_extension(extension);
}
create_rg_preprocessor_invoker(¤t_exe, &path).map_err(|source| {
CommandError::CreateRgPreprocessorInvoker { path: path.clone(), source }
})?;
Ok(Self { path })
}
pub(crate) fn path(&self) -> &Path {
&self.path
}
}
impl Drop for RgPreprocessorLink {
fn drop(&mut self) {
let _ = fs::remove_file(&self.path);
}
}
fn current_time_nanos() -> u128 {
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos()
}
#[cfg(unix)]
fn create_rg_preprocessor_invoker(current_exe: &Path, path: &Path) -> io::Result<()> {
std::os::unix::fs::symlink(current_exe, path)
}
#[cfg(not(unix))]
fn create_rg_preprocessor_invoker(current_exe: &Path, path: &Path) -> io::Result<()> {
fs::copy(current_exe, path).map(|_| ())
}