use std::collections::HashMap;
use crate::error::{McError, Result};
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct Frontmatter {
pub cyb: CybMeta,
#[serde(default, rename = "files")]
pub files: Vec<FileEntry>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CybMeta {
pub types: Vec<String>,
pub name: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FileEntry {
pub name: String,
pub format: String,
#[serde(default)]
pub size: Option<u64>,
}
pub fn split(bytes: &[u8]) -> Result<(&str, usize)> {
let mut i = 0;
while i + 4 <= bytes.len() {
if &bytes[i..i + 4] == b"\n~~~" {
let fm = std::str::from_utf8(&bytes[..i + 1])
.map_err(|e| McError::InvalidGraph(format!("non-utf8 frontmatter: {e}")))?;
return Ok((fm, i + 1));
}
i += 1;
}
Err(McError::InvalidGraph("no `~~~` delimiter found".into()))
}
pub fn parse(s: &str) -> Result<Frontmatter> {
Ok(toml::from_str(s)?)
}
pub fn index_sections(
bytes: &[u8],
body_start: usize,
entries: &[FileEntry],
) -> Result<HashMap<String, (usize, usize)>> {
let mut sections = HashMap::new();
let mut cursor = body_start;
for entry in entries {
let header = format!("~~~{}\n", entry.name);
if cursor + header.len() > bytes.len() || !bytes[cursor..].starts_with(header.as_bytes()) {
return Err(McError::InvalidGraph(format!(
"expected `{}` at byte {}",
header.trim_end(),
cursor
)));
}
let start = cursor + header.len();
let end = match entry.size {
Some(sz) => start + sz as usize,
None => find_text_end(bytes, start),
};
if end > bytes.len() {
return Err(McError::InvalidGraph(format!(
"section `{}` extends past EOF (end={}, file={})",
entry.name,
end,
bytes.len()
)));
}
sections.insert(entry.name.clone(), (start, end));
cursor = end;
if cursor < bytes.len() && bytes[cursor] == b'\n' {
cursor += 1;
}
}
Ok(sections)
}
fn find_text_end(bytes: &[u8], start: usize) -> usize {
let mut j = start;
while j + 4 <= bytes.len() {
if &bytes[j..j + 4] == b"\n~~~" {
return j;
}
j += 1;
}
bytes.len()
}