pub mod environment;
mod hidden_input;
pub(crate) mod path_serde;
pub(crate) mod paths;
pub(crate) mod program;
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
#[cfg(unix)]
use unix as os;
#[cfg(windows)]
use windows as os;
pub(crate) use os::{
CRLF_LINE_ENDINGS, Child, ENV_NAMES_IGNORE_CASE, HOME_VARIABLE, PrivateTempDir,
STATE_VARIABLES, TEMP_VARIABLES, USER_VARIABLES, create_private_dir, create_private_dir_all,
exit_signal, file_identity, home_directory, host_name, ipc, is_owner_only, is_private_dir,
is_private_file, kill_saved_group, kill_saved_session, path_from_bytes, private_options,
private_tempdir_in, process_start_time, remove_abandoned_directory, resolve_system_aliases,
restrict_file, running_process_start_time, spawn_detached, spawn_gated, spawn_run,
spawn_watched, sync_dir, transient_file_access,
};
pub use os::{HiddenInput, VISIBLE_INPUT_FALLBACK, stop_requested};
pub(crate) use os::{PRIVATE_DIRECTORY, PRIVATE_FILE};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct FileIdentity {
volume: u64,
index: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ProcessEnd {
Exited(i32),
Signaled(Option<i32>),
}
pub(crate) fn process_end(status: &std::process::ExitStatus) -> ProcessEnd {
match status.code() {
Some(code) => ProcessEnd::Exited(code),
None => ProcessEnd::Signaled(exit_signal(status)),
}
}
pub(crate) fn exit_description(status: &std::process::ExitStatus) -> String {
status.to_string()
}
pub(crate) const FILE_LOCK_RETRY_INTERVAL: std::time::Duration =
std::time::Duration::from_millis(25);
pub(crate) use artifactize_tools::files::{
EntryName, FileKind, canonicalize, entry_kind, link_target, open_directory, open_entry,
open_no_follow, read_dir,
};
pub use artifactize_tools::files::{open_nonblocking, path_text};
pub fn open_regular(path: &std::path::Path) -> std::io::Result<std::fs::File> {
let file = open_no_follow(std::fs::OpenOptions::new().read(true), path)?;
if file.metadata()?.is_file() {
Ok(file)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"not a regular file",
))
}
}
pub(crate) fn path_from_line(bytes: &[u8]) -> std::path::PathBuf {
let path = if CRLF_LINE_ENDINGS {
bytes
.strip_suffix(b"\r\n")
.or_else(|| bytes.strip_suffix(b"\n"))
.unwrap_or(bytes)
} else {
bytes.strip_suffix(b"\n").unwrap_or(bytes)
};
path_from_bytes(path)
}
pub(crate) fn state_directory() -> Option<std::path::PathBuf> {
resolve_state_directory(
environment::var("ARTIFACTIZE_STATE_HOME").map(Into::into),
STATE_VARIABLES
.iter()
.find_map(|name| environment::var(name).filter(|v| !v.is_empty()))
.map(Into::into),
home_directory(),
)
}
fn resolve_state_directory(
artifactize: Option<std::path::PathBuf>,
state: Option<std::path::PathBuf>,
home: Option<std::path::PathBuf>,
) -> Option<std::path::PathBuf> {
artifactize
.filter(|p| !p.as_os_str().is_empty())
.or_else(|| {
state
.filter(|p| !p.as_os_str().is_empty())
.map(|p| p.join("artifactize"))
})
.or_else(|| {
home.filter(|p| !p.as_os_str().is_empty())
.map(|p| p.join(".local/state/artifactize"))
})
}
pub(crate) fn user_name() -> Option<String> {
USER_VARIABLES
.iter()
.find_map(|name| environment::var_text(name).filter(|value| !value.is_empty()))
}
pub(crate) fn fingerprint_line_ending(stdout: &[u8]) -> (&[u8], &'static str) {
if CRLF_LINE_ENDINGS {
(
stdout
.strip_suffix(b"\r\n")
.or_else(|| stdout.strip_suffix(b"\n"))
.unwrap_or(stdout),
"LF or CRLF",
)
} else {
(stdout.strip_suffix(b"\n").unwrap_or(stdout), "LF")
}
}
pub(crate) fn path_kind(path: &std::path::Path) -> std::io::Result<FileKind> {
match (path.parent(), path.file_name()) {
(Some(parent), Some(name)) => entry_kind(&open_directory(parent)?, name),
_ => open_directory(path).map(|_| FileKind::Directory),
}
}
pub(crate) fn serialize_paths<S: serde::Serializer>(
paths: &[std::path::PathBuf],
serializer: S,
) -> Result<S::Ok, S::Error> {
use serde::Serialize;
paths
.iter()
.map(|path| path_text(path))
.collect::<Vec<_>>()
.serialize(serializer)
}
#[cfg(test)]
mod state_directory_tests {
use super::*;
use std::path::PathBuf;
#[test]
fn state_home_precedence_and_empty_values() {
let artifactize = PathBuf::from("custom/state");
let state = PathBuf::from("state");
let home = PathBuf::from("reviewer");
assert_eq!(
resolve_state_directory(
Some(artifactize.clone()),
Some(state.clone()),
Some(home.clone())
),
Some(artifactize)
);
assert_eq!(
resolve_state_directory(None, Some(state.clone()), Some(home.clone())),
Some(state.join("artifactize"))
);
assert_eq!(
resolve_state_directory(None, None, Some(home.clone())),
Some(home.join(".local/state/artifactize"))
);
let empty = Some(PathBuf::new());
assert_eq!(
resolve_state_directory(empty.clone(), empty.clone(), Some(home.clone())),
Some(home.join(".local/state/artifactize"))
);
assert_eq!(
resolve_state_directory(empty.clone(), empty.clone(), empty),
None
);
assert_eq!(resolve_state_directory(None, None, None), None);
}
}
mod console;
pub use console::pause_after_help;
mod interaction;
pub(crate) use interaction::{is_within, label, paths_equal, private_anonymous_file};
pub(crate) fn entry_exists(path: &std::path::Path) -> std::io::Result<bool> {
let Some(parent) = path.parent() else {
return Ok(false);
};
let directory = match open_directory(parent) {
Ok(directory) => directory,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(error) => return Err(error),
};
let Some(name) = path.file_name() else {
return Ok(false);
};
match entry_kind(&directory, name) {
Ok(_) => Ok(true),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(error) => Err(error),
}
}
pub(crate) fn marker_metadata(path: &std::path::Path) -> std::io::Result<std::fs::Metadata> {
path.symlink_metadata()
}