wml2 0.0.21

Pure Rust multi-format image decoding and encoding library supporting JPEG, PNG, GIF, WebP, TIFF and PC-98 legacy formats (MAG, MAKI, PI, PIC)
Documentation
#![allow(dead_code)]

use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;

fn repo_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .to_path_buf()
}

fn package_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}

fn sample_override_config_path() -> PathBuf {
    package_root().join("tests").join("test_samples.txt")
}

pub fn test_image_root() -> PathBuf {
    repo_root().join("test").join("images")
}

pub fn bundled_test_image_path(name: &str) -> PathBuf {
    test_image_root().join("bundled").join(name)
}

fn legacy_sample_path(name: &str) -> PathBuf {
    repo_root().join("test").join("samples").join(name)
}

fn tracked_sample_paths(name: &str) -> [PathBuf; 4] {
    [
        bundled_test_image_path(name),
        legacy_sample_path(name),
        repo_root().join("_test").join(name),
        repo_root().join("_test").join("animation_webp").join(name),
    ]
}

fn search_dir_recursive(root: &Path, name: &str) -> Option<PathBuf> {
    let entries = fs::read_dir(root).ok()?;
    for entry in entries.filter_map(Result::ok) {
        let path = entry.path();
        if path.is_file() {
            let matches = path
                .file_name()
                .and_then(|file_name| file_name.to_str())
                .is_some_and(|file_name| file_name.eq_ignore_ascii_case(name));
            if matches {
                return Some(path);
            }
        } else if path.is_dir() {
            if let Some(found) = search_dir_recursive(&path, name) {
                return Some(found);
            }
        }
    }
    None
}

fn resolve_config_path(config_dir: &Path, value: &str) -> PathBuf {
    let path = PathBuf::from(value);
    if path.is_absolute() {
        path
    } else {
        config_dir.join(path)
    }
}

fn parse_sample_config() -> HashMap<String, PathBuf> {
    let path = sample_override_config_path();
    let Ok(text) = fs::read_to_string(&path) else {
        return HashMap::new();
    };

    let config_dir = path.parent().unwrap_or_else(|| Path::new("."));
    let mut map = HashMap::new();
    for (line_no, raw_line) in text.lines().enumerate() {
        let line = raw_line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }

        let Some((key, value)) = line.split_once('=') else {
            eprintln!(
                "ignoring malformed sample config line {} in {}",
                line_no + 1,
                path.display()
            );
            continue;
        };
        let key = key.trim();
        let value = value.trim();
        if key.is_empty() || value.is_empty() {
            continue;
        }

        map.insert(key.to_string(), resolve_config_path(config_dir, value));
    }
    map
}

fn sample_config() -> &'static HashMap<String, PathBuf> {
    static CONFIG: OnceLock<HashMap<String, PathBuf>> = OnceLock::new();
    CONFIG.get_or_init(parse_sample_config)
}

pub fn sample_path(name: &str) -> Option<PathBuf> {
    for tracked in tracked_sample_paths(name) {
        if tracked.is_file() {
            return Some(tracked);
        }
    }

    if let Some(configured) = sample_config().get(name).cloned() {
        if configured.is_file() {
            return Some(configured);
        }
    }

    search_dir_recursive(&test_image_root().join("external"), name)
}

pub fn sample_bytes(name: &str) -> Option<Vec<u8>> {
    let path = sample_path(name)?;
    fs::read(path).ok()
}

pub fn sample_config_hint() -> PathBuf {
    test_image_root().join("README.md")
}