use super::normalize_path;
use crate::formats::lex::formatting_rules::FormattingRules;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssetKind {
Image,
Video,
Audio,
Data,
}
impl AssetKind {
pub fn from_extension(ext: Option<&str>) -> Self {
let ext = ext.unwrap_or("").to_ascii_lowercase();
if matches!(
ext.as_str(),
"png" | "jpg" | "jpeg" | "gif" | "bmp" | "webp" | "svg"
) {
AssetKind::Image
} else if matches!(ext.as_str(), "mp4" | "mov" | "webm" | "avi" | "mkv") {
AssetKind::Video
} else if matches!(ext.as_str(), "mp3" | "wav" | "flac" | "ogg" | "aac") {
AssetKind::Audio
} else {
AssetKind::Data
}
}
pub fn label(self) -> &'static str {
match self {
AssetKind::Image => "doc.image",
AssetKind::Video => "doc.video",
AssetKind::Audio => "doc.audio",
AssetKind::Data => "doc.data",
}
}
}
pub struct AssetSnippetRequest<'a> {
pub asset_path: &'a Path,
pub document_directory: Option<&'a Path>,
pub formatting: &'a FormattingRules,
pub indent_level: usize,
}
impl<'a> AssetSnippetRequest<'a> {
pub fn new(asset_path: &'a Path, formatting: &'a FormattingRules) -> Self {
Self {
asset_path,
document_directory: None,
formatting,
indent_level: 0,
}
}
fn indentation(&self) -> String {
self.formatting.indent_string.repeat(self.indent_level)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AssetSnippet {
pub kind: AssetKind,
pub text: String,
pub cursor_offset: usize,
}
pub fn build_asset_snippet(request: &AssetSnippetRequest<'_>) -> AssetSnippet {
let kind =
AssetKind::from_extension(request.asset_path.extension().and_then(|ext| ext.to_str()));
let normalized_path = normalize_path(request.asset_path, request.document_directory);
let indent = request.indentation();
let mut text = String::new();
text.push_str(&indent);
text.push_str(":: ");
text.push_str(kind.label());
text.push_str(" src=\"");
text.push_str(&normalized_path);
text.push_str("\"\n");
AssetSnippet {
kind,
cursor_offset: text.len(),
text,
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::{tempdir, NamedTempFile};
#[test]
fn chooses_kind_based_on_extension() {
let png = NamedTempFile::new().unwrap();
let path = png.path().with_extension("png");
let rules = FormattingRules::default();
let request = AssetSnippetRequest::new(path.as_path(), &rules);
let snippet = build_asset_snippet(&request);
assert_eq!(snippet.kind, AssetKind::Image);
assert!(snippet.text.contains(":: doc.image"));
}
#[test]
fn normalizes_to_relative_path_when_possible() {
let temp = tempdir().unwrap();
let doc_dir = temp.path();
let asset_path = doc_dir.join("assets").join("diagram.png");
std::fs::create_dir_all(asset_path.parent().unwrap()).unwrap();
std::fs::write(&asset_path, b"binary").unwrap();
let rules = FormattingRules::default();
let request = AssetSnippetRequest {
asset_path: asset_path.as_path(),
document_directory: Some(doc_dir),
formatting: &rules,
indent_level: 0,
};
let snippet = build_asset_snippet(&request);
assert!(snippet.text.contains("src=\"./assets/diagram.png\""));
}
#[test]
fn indent_level_is_respected() {
let path = Path::new("./diagram.png");
let rules = FormattingRules::default();
let request = AssetSnippetRequest {
asset_path: path,
document_directory: None,
formatting: &rules,
indent_level: 2,
};
let snippet = build_asset_snippet(&request);
assert!(snippet.text.starts_with(" ::"));
}
#[test]
fn non_image_extensions_fallback_to_data() {
let path = Path::new("./archive.zip");
let rules = FormattingRules::default();
let request = AssetSnippetRequest::new(path, &rules);
let snippet = build_asset_snippet(&request);
assert_eq!(snippet.kind, AssetKind::Data);
assert!(snippet.text.contains(":: doc.data"));
}
}