use flate2::read::GzDecoder;
use std::path::Path;
use tar::Archive;
const SPECS_ARCHIVE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/specs.tar.gz"));
const VERSION: &str = env!("CARGO_PKG_VERSION");
const VERSION_FILE: &str = ".specs_version";
pub enum ExtractResult {
Extracted { count: usize },
AlreadyCurrent,
NoEmbeddedSpecs,
}
pub fn has_embedded_specs() -> bool {
!SPECS_ARCHIVE.is_empty()
}
pub fn extract_specs(dest: &Path) -> Result<ExtractResult, Box<dyn std::error::Error>> {
if !has_embedded_specs() {
return Ok(ExtractResult::NoEmbeddedSpecs);
}
let version_path = dest.join(VERSION_FILE);
if version_path.exists() {
let existing = std::fs::read_to_string(&version_path).unwrap_or_default();
if existing.trim() == VERSION {
return Ok(ExtractResult::AlreadyCurrent);
}
}
std::fs::create_dir_all(dest)?;
let decoder = GzDecoder::new(SPECS_ARCHIVE);
let mut archive = Archive::new(decoder);
let mut count = 0;
for entry in archive.entries()? {
let mut entry = entry?;
let path = entry.path()?.into_owned();
if path.extension().map(|e| e == "json").unwrap_or(false) {
let filename = path
.file_name()
.ok_or_else(|| format!("Invalid entry path: {}", path.display()))?;
let dest_path = dest.join(filename);
let mut file = std::fs::File::create(&dest_path)?;
std::io::copy(&mut entry, &mut file)?;
count += 1;
}
}
std::fs::write(&version_path, VERSION)?;
Ok(ExtractResult::Extracted { count })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn has_specs_when_built_with_specs_dir() {
if !has_embedded_specs() {
eprintln!("Skipping: no embedded specs in this build");
return;
}
assert!(SPECS_ARCHIVE.len() > 100);
}
#[test]
fn extract_to_tempdir() {
if !has_embedded_specs() {
eprintln!("Skipping: no embedded specs in this build");
return;
}
let dir = tempfile::tempdir().unwrap();
let result = extract_specs(dir.path()).unwrap();
match result {
ExtractResult::Extracted { count } => {
assert!(count > 100, "Expected 100+ specs, got {count}");
let version = std::fs::read_to_string(dir.path().join(VERSION_FILE)).unwrap();
assert_eq!(version.trim(), VERSION);
let git_spec = dir.path().join("git.json");
assert!(git_spec.exists(), "git.json should exist");
let contents = std::fs::read_to_string(&git_spec).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&contents).unwrap();
assert_eq!(parsed["name"], "git");
}
other => panic!(
"Expected Extracted, got {}",
match other {
ExtractResult::AlreadyCurrent => "AlreadyCurrent",
ExtractResult::NoEmbeddedSpecs => "NoEmbeddedSpecs",
_ => "unknown",
}
),
}
}
#[test]
fn extract_twice_returns_already_current() {
if !has_embedded_specs() {
eprintln!("Skipping: no embedded specs in this build");
return;
}
let dir = tempfile::tempdir().unwrap();
let result = extract_specs(dir.path()).unwrap();
assert!(matches!(result, ExtractResult::Extracted { .. }));
let result = extract_specs(dir.path()).unwrap();
assert!(matches!(result, ExtractResult::AlreadyCurrent));
}
#[test]
fn re_extracts_on_version_change() {
if !has_embedded_specs() {
eprintln!("Skipping: no embedded specs in this build");
return;
}
let dir = tempfile::tempdir().unwrap();
extract_specs(dir.path()).unwrap();
std::fs::write(dir.path().join(VERSION_FILE), "0.0.0").unwrap();
let result = extract_specs(dir.path()).unwrap();
assert!(matches!(result, ExtractResult::Extracted { .. }));
let version = std::fs::read_to_string(dir.path().join(VERSION_FILE)).unwrap();
assert_eq!(version.trim(), VERSION);
}
}