#![deny(missing_docs)]
#![forbid(unsafe_code)]
use std::{
ffi::{OsStr, OsString},
io::Read,
path::{Path, PathBuf},
};
use bstr::{BString, ByteSlice};
pub mod parse;
mod prepare;
pub mod shebang {
use std::{ffi::OsString, path::PathBuf};
use bstr::{BStr, ByteSlice};
pub fn parse(buf: &BStr) -> Option<Data> {
let mut line = buf.lines().next()?;
line = line.strip_prefix(b"#!")?;
let slash_idx = line.rfind_byteset(br"/\")?;
let space_idx = line[slash_idx..]
.find_byte(b' ')
.map_or(line.len(), |space_idx| slash_idx + space_idx);
let (interpreter, args) = line.split_at(space_idx);
Some(Data {
interpreter: gix_path::try_from_byte_slice(interpreter.trim()).ok()?.to_owned(),
args: crate::parse::arguments(args.trim().as_bstr()).unwrap_or_default(),
})
}
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct Data {
pub interpreter: PathBuf,
pub args: Vec<OsString>,
}
}
pub struct Prepare {
pub command: OsString,
pub context: Option<Context>,
pub stdin: std::process::Stdio,
pub stdout: std::process::Stdio,
pub stderr: std::process::Stdio,
pub args: Vec<OsString>,
pub env: Vec<(OsString, OsString)>,
pub use_shell: bool,
pub quote_command: bool,
pub shell_program: Option<OsString>,
pub allow_manual_arg_splitting: bool,
}
#[derive(Debug, Default, Clone)]
pub struct Context {
pub git_dir: Option<PathBuf>,
pub worktree_dir: Option<PathBuf>,
pub no_replace_objects: Option<bool>,
pub ref_namespace: Option<BString>,
pub literal_pathspecs: Option<bool>,
pub glob_pathspecs: Option<bool>,
pub icase_pathspecs: Option<bool>,
pub stderr: Option<bool>,
}
#[cfg(windows)]
fn is_exe(executable: &Path) -> bool {
executable.extension() == Some(std::ffi::OsStr::new("exe"))
}
fn split_paths(joined_paths: &OsStr) -> impl Iterator<Item = PathBuf> + '_ {
std::env::split_paths(joined_paths).filter(|path| !path.as_os_str().is_empty())
}
fn is_bare_command(command: &Path) -> bool {
command.components().take(2).count() == 1
}
fn win_path_lookup(command: &Path, joined_paths: &std::ffi::OsStr) -> Option<PathBuf> {
fn lookup(root: &Path, command: &Path, has_extension: bool) -> Option<PathBuf> {
let mut path = root.join(command);
if has_extension {
return path.is_file().then_some(path);
}
path.set_extension("exe");
if path.is_file() {
return Some(path);
}
path.set_extension("");
path.is_file().then_some(path)
}
if !is_bare_command(command) {
return None;
}
let has_extension = command.extension().is_some();
for root in split_paths(joined_paths) {
if let Some(executable) = lookup(&root, command, has_extension) {
return Some(executable);
}
}
None
}
pub fn extract_interpreter(executable: &Path) -> Option<shebang::Data> {
#[cfg(windows)]
if is_exe(executable) {
return None;
}
let mut buf = [0; 100]; let mut file = std::fs::File::open(executable).ok()?;
let n = file.read(&mut buf).ok()?;
shebang::parse(buf[..n].as_bstr())
}
pub fn prepare(cmd: impl Into<OsString>) -> Prepare {
Prepare {
command: cmd.into(),
shell_program: None,
context: None,
stdin: std::process::Stdio::null(),
stdout: std::process::Stdio::piped(),
stderr: std::process::Stdio::inherit(),
args: Vec::new(),
env: Vec::new(),
use_shell: false,
quote_command: false,
allow_manual_arg_splitting: cfg!(windows),
}
}
#[cfg(test)]
mod tests;