use std::path::{Path, PathBuf};
use super::error::ImatrixError;
const CDV3_CORPUS: &str = include_str!("../../../data/calibration/cdv3.txt");
pub const BAKED_CORPUS_NAMES: &[&str] = &["cdv3", "mudler", "user-file"];
#[derive(Debug, Clone)]
pub enum CorpusSource {
Cdv3,
Mudler,
UserFile(PathBuf),
}
impl CorpusSource {
pub fn from_cli(s: &str) -> Result<Self, ImatrixError> {
match s {
"cdv3" => Ok(CorpusSource::Cdv3),
"mudler" => Ok(CorpusSource::Mudler),
other => {
if let Some(path) = other.strip_prefix("user-file:") {
return Ok(CorpusSource::UserFile(PathBuf::from(path)));
}
Err(ImatrixError::UnknownBakedCorpus {
name: s.to_string(),
supported: BAKED_CORPUS_NAMES,
})
}
}
}
pub fn dataset_label(&self) -> String {
match self {
CorpusSource::Cdv3 => "cdv3".to_string(),
CorpusSource::Mudler => "mudler".to_string(),
CorpusSource::UserFile(p) => p
.file_name()
.and_then(|s| s.to_str())
.unwrap_or_else(|| p.to_str().unwrap_or("user-file"))
.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct CorpusBytes {
pub text: String,
pub label: String,
}
impl CorpusBytes {
pub fn load(source: &CorpusSource) -> Result<Self, ImatrixError> {
match source {
CorpusSource::Cdv3 => Ok(CorpusBytes {
text: CDV3_CORPUS.to_string(),
label: "cdv3".to_string(),
}),
CorpusSource::Mudler => {
Err(ImatrixError::CorpusRead {
path: "<baked:mudler>".to_string(),
detail: "mudler corpus is not bundled in Phase A. \
Either use `cdv3` (default) or supply your own via \
`user-file:<path>`. See ADR-033 §Pi for the mudler-style \
sampling recipe."
.to_string(),
})
}
CorpusSource::UserFile(path) => load_user_file(path),
}
}
pub fn as_str(&self) -> &str {
&self.text
}
pub fn approx_word_count(&self) -> usize {
self.text.split_whitespace().count()
}
pub fn byte_count(&self) -> usize {
self.text.len()
}
}
fn load_user_file(path: &Path) -> Result<CorpusBytes, ImatrixError> {
let text = std::fs::read_to_string(path).map_err(|e| ImatrixError::CorpusRead {
path: path.display().to_string(),
detail: e.to_string(),
})?;
let label = path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("user-file")
.to_string();
Ok(CorpusBytes { text, label })
}
pub fn chunk_tokens(tokens: &[u32], chunk_size: usize) -> Vec<&[u32]> {
if chunk_size == 0 {
return vec![];
}
tokens.chunks_exact(chunk_size).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cdv3_baked_corpus_is_non_trivial() {
let bytes = CorpusBytes::load(&CorpusSource::Cdv3).unwrap();
assert_eq!(bytes.label, "cdv3");
assert!(
bytes.byte_count() > 100_000,
"baked cdv3.txt unexpectedly small: {} bytes",
bytes.byte_count()
);
assert!(
bytes.approx_word_count() > 10_000,
"baked cdv3.txt unexpectedly few words: {}",
bytes.approx_word_count()
);
}
#[test]
fn mudler_corpus_not_yet_bundled() {
let err = CorpusBytes::load(&CorpusSource::Mudler).unwrap_err();
match err {
ImatrixError::CorpusRead { path, detail } => {
assert_eq!(path, "<baked:mudler>");
assert!(detail.contains("not bundled"));
}
other => panic!("expected CorpusRead, got {other:?}"),
}
}
#[test]
fn from_cli_parses_baked_and_user_file() {
assert!(matches!(
CorpusSource::from_cli("cdv3"),
Ok(CorpusSource::Cdv3)
));
assert!(matches!(
CorpusSource::from_cli("mudler"),
Ok(CorpusSource::Mudler)
));
let uf = CorpusSource::from_cli("user-file:/tmp/x.txt").unwrap();
match uf {
CorpusSource::UserFile(p) => assert_eq!(p.to_str(), Some("/tmp/x.txt")),
other => panic!("expected UserFile, got {other:?}"),
}
let err = CorpusSource::from_cli("wikitext").unwrap_err();
assert!(matches!(err, ImatrixError::UnknownBakedCorpus { .. }));
}
#[test]
fn dataset_label_round_trips_for_baked() {
assert_eq!(CorpusSource::Cdv3.dataset_label(), "cdv3");
assert_eq!(CorpusSource::Mudler.dataset_label(), "mudler");
let uf = CorpusSource::UserFile(PathBuf::from("/x/y/my_corpus.txt"));
assert_eq!(uf.dataset_label(), "my_corpus.txt");
}
#[test]
fn missing_user_file_errors_typed() {
let err = CorpusBytes::load(&CorpusSource::UserFile(PathBuf::from(
"/nonexistent/path/should/not/be/here.txt",
)))
.unwrap_err();
assert!(matches!(err, ImatrixError::CorpusRead { .. }));
}
#[test]
fn chunk_tokens_drops_partial_trailing() {
let toks: Vec<u32> = (0u32..10).collect();
let chunks = chunk_tokens(&toks, 3);
assert_eq!(chunks.len(), 3);
assert_eq!(chunks[0], &[0u32, 1, 2][..]);
assert_eq!(chunks[1], &[3u32, 4, 5][..]);
assert_eq!(chunks[2], &[6u32, 7, 8][..]);
}
#[test]
fn chunk_tokens_zero_chunk_size() {
let toks = vec![1u32, 2, 3];
assert!(chunk_tokens(&toks, 0).is_empty());
}
}