use std::fs;
use std::io::{self, Cursor};
use std::path::PathBuf;
use std::sync::OnceLock;
use flate2::read::GzDecoder;
use tar::Archive;
pub const APIDOC_DATA_VERSION: &str = "1.0";
const EMBEDDED_APIDOC: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/apidoc.tar.gz"));
static CACHED_APIDOC_DIR: OnceLock<Option<PathBuf>> = OnceLock::new();
pub fn get_apidoc_dir() -> Option<PathBuf> {
CACHED_APIDOC_DIR
.get_or_init(|| extract_apidoc_if_needed().ok())
.clone()
}
fn get_cache_base_dir() -> Option<PathBuf> {
if let Ok(path) = std::env::var("LIBPERL_APIDOC_CACHE_DIR") {
return Some(PathBuf::from(path));
}
#[cfg(target_os = "linux")]
{
if let Ok(home) = std::env::var("HOME") {
return Some(PathBuf::from(home).join(".cache"));
}
}
#[cfg(target_os = "macos")]
{
if let Ok(home) = std::env::var("HOME") {
return Some(PathBuf::from(home).join("Library/Caches"));
}
}
#[cfg(target_os = "windows")]
{
if let Ok(local_app_data) = std::env::var("LOCALAPPDATA") {
return Some(PathBuf::from(local_app_data));
}
}
std::env::current_dir().ok()
}
fn extract_apidoc_if_needed() -> io::Result<PathBuf> {
let cache_base = get_cache_base_dir().ok_or_else(|| {
io::Error::new(io::ErrorKind::NotFound, "Could not determine cache directory")
})?;
let cache_dir = cache_base
.join("libperl-macrogen")
.join(format!("apidoc-v{}", APIDOC_DATA_VERSION));
let apidoc_dir = cache_dir.join("apidoc");
if apidoc_dir.is_dir() {
let version_file = cache_dir.join("version");
if let Ok(cached_version) = fs::read_to_string(&version_file) {
if cached_version.trim() == APIDOC_DATA_VERSION {
return Ok(apidoc_dir);
}
}
}
fs::create_dir_all(&cache_dir)?;
let cursor = Cursor::new(EMBEDDED_APIDOC);
let gz_decoder = GzDecoder::new(cursor);
let mut archive = Archive::new(gz_decoder);
archive.unpack(&cache_dir)?;
fs::write(cache_dir.join("version"), APIDOC_DATA_VERSION)?;
Ok(apidoc_dir)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_embedded_data_not_empty() {
assert!(!EMBEDDED_APIDOC.is_empty());
assert_eq!(EMBEDDED_APIDOC[0], 0x1f);
assert_eq!(EMBEDDED_APIDOC[1], 0x8b);
}
#[test]
fn test_get_apidoc_dir() {
let dir = get_apidoc_dir();
assert!(dir.is_some());
let dir = dir.unwrap();
assert!(dir.is_dir());
assert!(dir.join("v5.40.json").exists());
}
}