#![warn(clippy::all)]
#![warn(clippy::pedantic)]
#![warn(clippy::cargo)]
#![allow(clippy::manual_let_else)]
#![allow(clippy::question_mark)] #![allow(unknown_lints)]
#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![warn(missing_copy_implementations)]
#![warn(rust_2018_idioms)]
#![warn(rust_2021_compatibility)]
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unused_qualifications)]
#![warn(variant_size_differences)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_alias))]
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
mod readme {}
use std::path::PathBuf;
#[must_use]
pub fn repl_history_file() -> Option<PathBuf> {
let data_dir = repl_history_dir()?;
#[cfg(not(any(test, doctest, miri)))] let _ignored = std::fs::create_dir_all(&data_dir);
Some(data_dir.join(history_file_basename()))
}
#[must_use]
#[cfg(target_os = "macos")]
fn repl_history_dir() -> Option<PathBuf> {
use std::env;
use std::ffi::{c_char, CStr, OsString};
use std::os::unix::ffi::OsStringExt;
use sysdir::{
sysdir_get_next_search_path_enumeration, sysdir_search_path_directory_t, sysdir_start_search_path_enumeration,
PATH_MAX, SYSDIR_DOMAIN_MASK_USER,
};
let mut path = [0; PATH_MAX as usize];
let dir = sysdir_search_path_directory_t::SYSDIR_DIRECTORY_APPLICATION_SUPPORT;
let domain_mask = SYSDIR_DOMAIN_MASK_USER;
let application_support_bytes = unsafe {
let mut state = sysdir_start_search_path_enumeration(dir, domain_mask);
let path = path.as_mut_ptr().cast::<c_char>();
state = sysdir_get_next_search_path_enumeration(state, path);
if state.is_finished() {
return None;
}
let path = CStr::from_ptr(path);
path.to_bytes()
};
#[allow(deprecated)]
let application_support = match application_support_bytes {
[] => return None,
[b'~'] => env::home_dir()?,
[b'~', b'/', tail @ ..] => {
let home = env::home_dir()?;
let mut home = home.into_os_string().into_vec();
home.try_reserve_exact(1 + tail.len()).ok()?;
home.push(b'/');
home.extend_from_slice(tail);
OsString::from_vec(home).into()
}
path => {
let mut buf = vec![];
buf.try_reserve_exact(path.len()).ok()?;
buf.extend_from_slice(path);
OsString::from_vec(buf).into()
}
};
Some(application_support.join("org.artichokeruby.airb"))
}
#[must_use]
#[cfg(all(unix, not(target_os = "macos")))]
fn repl_history_dir() -> Option<PathBuf> {
use std::env;
let state_dir = match env::var_os("XDG_STATE_HOME") {
Some(path) if path.is_empty() => None,
Some(path) => Some(path),
None => None,
};
let state_dir = if let Some(state_dir) = state_dir {
PathBuf::from(state_dir)
} else {
#[allow(deprecated)]
let mut state_dir = env::home_dir()?;
state_dir.extend([".local", "state"]);
state_dir
};
Some(state_dir.join("artichokeruby"))
}
#[must_use]
#[cfg(windows)]
fn repl_history_dir() -> Option<PathBuf> {
use known_folders::{get_known_folder_path, KnownFolder};
let local_app_data = get_known_folder_path(KnownFolder::LocalAppData)?;
Some(local_app_data.join("Artichoke Ruby").join("airb").join("data"))
}
#[must_use]
fn history_file_basename() -> &'static str {
if cfg!(windows) {
return "history.txt";
}
if cfg!(target_os = "macos") {
return "history";
}
if cfg!(unix) {
return "airb_history";
}
"history"
}
#[cfg(test)]
mod tests {
use std::ffi::OsStr;
use std::path;
use super::*;
#[cfg(all(unix, not(target_os = "macos")))]
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn history_file_basename_is_non_empty() {
assert!(!history_file_basename().is_empty());
}
#[test]
fn history_file_basename_does_not_contain_path_separators() {
let filename = history_file_basename();
for c in filename.chars() {
assert!(!path::is_separator(c));
}
}
#[test]
#[cfg(target_os = "macos")]
fn history_dir_on_macos() {
let dir = repl_history_dir().unwrap();
let mut components = dir.components();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("/"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("Users"));
let _skip_user_dir = components.next().unwrap();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("Library"));
assert_eq!(
components.next().unwrap().as_os_str(),
OsStr::new("Application Support")
);
assert_eq!(
components.next().unwrap().as_os_str(),
OsStr::new("org.artichokeruby.airb")
);
assert!(components.next().is_none());
}
#[test]
#[cfg(target_os = "macos")]
fn history_file_on_macos() {
let file = repl_history_file().unwrap();
let mut components = file.components();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("/"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("Users"));
let _skip_user_dir = components.next().unwrap();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("Library"));
assert_eq!(
components.next().unwrap().as_os_str(),
OsStr::new("Application Support")
);
assert_eq!(
components.next().unwrap().as_os_str(),
OsStr::new("org.artichokeruby.airb")
);
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("history"));
assert!(components.next().is_none());
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn history_dir_on_unix_xdg_unset() {
use std::env;
let _guard = ENV_LOCK.lock();
env::remove_var("XDG_STATE_HOME");
let dir = repl_history_dir().unwrap();
let mut components = dir.components();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("/"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("home"));
let _skip_user_dir = components.next().unwrap();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new(".local"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("state"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("artichokeruby"));
assert!(components.next().is_none());
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn history_file_on_unix_xdg_unset() {
use std::env;
let _guard = ENV_LOCK.lock();
env::remove_var("XDG_STATE_HOME");
let file = repl_history_file().unwrap();
let mut components = file.components();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("/"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("home"));
let _skip_user_dir = components.next().unwrap();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new(".local"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("state"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("artichokeruby"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("airb_history"));
assert!(components.next().is_none());
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn history_dir_on_unix_empty_xdg_state_dir() {
use std::env;
let _guard = ENV_LOCK.lock();
env::remove_var("XDG_STATE_HOME");
env::set_var("XDG_STATE_HOME", "");
let dir = repl_history_dir().unwrap();
let mut components = dir.components();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("/"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("home"));
let _skip_user_dir = components.next().unwrap();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new(".local"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("state"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("artichokeruby"));
assert!(components.next().is_none());
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn history_file_on_unix_empty_xdg_state_dir() {
use std::env;
let _guard = ENV_LOCK.lock();
env::remove_var("XDG_STATE_HOME");
env::set_var("XDG_STATE_HOME", "");
let file = repl_history_file().unwrap();
let mut components = file.components();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("/"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("home"));
let _skip_user_dir = components.next().unwrap();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new(".local"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("state"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("artichokeruby"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("airb_history"));
assert!(components.next().is_none());
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn history_dir_on_unix_set_xdg_state_dir() {
use std::env;
let _guard = ENV_LOCK.lock();
env::remove_var("XDG_STATE_HOME");
env::set_var("XDG_STATE_HOME", "/opt/artichoke/state");
let dir = repl_history_dir().unwrap();
let mut components = dir.components();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("/"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("opt"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("artichoke"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("state"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("artichokeruby"));
assert!(components.next().is_none());
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn history_file_on_unix_set_xdg_state_dir() {
use std::env;
let _guard = ENV_LOCK.lock();
env::remove_var("XDG_STATE_HOME");
env::set_var("XDG_STATE_HOME", "/opt/artichoke/state");
let file = repl_history_file().unwrap();
let mut components = file.components();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("/"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("opt"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("artichoke"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("state"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("artichokeruby"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("airb_history"));
assert!(components.next().is_none());
}
#[test]
#[cfg(windows)]
fn history_dir_on_windows() {
let dir = repl_history_dir().unwrap();
let mut components = dir.components();
let _skip_prefix = components.next().unwrap();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new(r"\"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("Users"));
let _skip_user_dir = components.next().unwrap();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("AppData"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("Local"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("Artichoke Ruby"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("airb"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("data"));
assert!(components.next().is_none());
}
#[test]
#[cfg(windows)]
fn history_file_on_windows() {
let file = repl_history_file().unwrap();
let mut components = file.components();
let _skip_prefix = components.next().unwrap();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new(r"\"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("Users"));
let _skip_user_dir = components.next().unwrap();
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("AppData"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("Local"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("Artichoke Ruby"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("airb"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("data"));
assert_eq!(components.next().unwrap().as_os_str(), OsStr::new("history.txt"));
assert!(components.next().is_none());
}
}