pub const EXT: &str = ".gguf";
const TAIL_LEN: usize = 15;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shard {
pub index: u32,
pub total: u32,
pub stem: String,
}
impl Shard {
pub fn path(&self, index: u32) -> String {
format!("{}-{index:05}-of-{:05}{EXT}", self.stem, self.total)
}
pub fn first(&self) -> String {
self.path(1)
}
pub fn all(&self) -> Vec<String> {
(1..=self.total).map(|i| self.path(i)).collect()
}
}
fn split_tail(stem: &str) -> Option<(usize, u32, u32)> {
let b = stem.as_bytes();
let head_len = b.len().checked_sub(TAIL_LEN)?;
let tail = &b[head_len..];
if head_len == 0 || tail[0] != b'-' || &tail[6..10] != b"-of-" {
return None;
}
let index = digits(&tail[1..6])?;
let total = digits(&tail[10..15])?;
(index > 0 && index <= total).then_some((head_len, index, total))
}
pub fn parse_shard(path: &str) -> Option<Shard> {
let stem = path.strip_suffix(EXT)?;
let (head_len, index, total) = split_tail(stem)?;
Some(Shard {
index,
total,
stem: stem[..head_len].to_string(),
})
}
pub fn display_name(path: &str) -> Option<String> {
let name = path.rsplit(['/', '\\']).next().unwrap_or(path);
let name = name.trim_end_matches(EXT);
let name = match split_tail(name) {
Some((head_len, _, _)) => &name[..head_len],
None => name,
};
(!name.is_empty()).then(|| name.to_string())
}
pub fn display_id(id: &str) -> String {
match id.ends_with(EXT) {
true => display_name(id).unwrap_or_else(|| id.to_string()),
false => id.to_string(),
}
}
fn digits(b: &[u8]) -> Option<u32> {
if !b.iter().all(u8::is_ascii_digit) {
return None;
}
Some(b.iter().fold(0, |n, c| n * 10 + u32::from(c - b'0')))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_a_split_part() {
let s = parse_shard("/models/gpt-oss-120b-Q8_0-00002-of-00003.gguf")
.expect("a gguf-split name must parse");
assert_eq!(s.index, 2);
assert_eq!(s.total, 3);
assert_eq!(s.stem, "/models/gpt-oss-120b-Q8_0");
assert_eq!(s.first(), "/models/gpt-oss-120b-Q8_0-00001-of-00003.gguf");
assert_eq!(
s.all(),
vec![
"/models/gpt-oss-120b-Q8_0-00001-of-00003.gguf".to_string(),
"/models/gpt-oss-120b-Q8_0-00002-of-00003.gguf".to_string(),
"/models/gpt-oss-120b-Q8_0-00003-of-00003.gguf".to_string(),
]
);
}
#[test]
fn parses_a_windows_path() {
let s = parse_shard(r"C:\GGUF\model-00001-of-00002.gguf").expect("must parse");
assert_eq!(s.stem, r"C:\GGUF\model");
assert_eq!(s.path(2), r"C:\GGUF\model-00002-of-00002.gguf");
}
#[test]
fn a_plain_gguf_is_not_a_split() {
for path in [
"gemma-4-it.gguf",
"model-1-of-3.gguf", "model-00001-of-00003.bin", "model-00001_of_00003.gguf", "model-00000-of-00003.gguf", "model-00004-of-00003.gguf", "-00001-of-00003.gguf", "модель-00001-of-00003.gguf.gz", ] {
assert!(parse_shard(path).is_none(), "{path}");
}
}
#[test]
fn handles_non_ascii_names() {
let s = parse_shard("модель-00001-of-00002.gguf").expect("must parse");
assert_eq!(s.stem, "модель");
assert_eq!(
display_name("модель-00001-of-00002.gguf").as_deref(),
Some("модель")
);
assert!(parse_shard("моделька.gguf").is_none());
}
#[test]
fn display_name_drops_directory_extension_and_tail() {
assert_eq!(
display_name("/models/gpt-oss-120b-Q8_0-00001-of-00003.gguf").as_deref(),
Some("gpt-oss-120b-Q8_0")
);
assert_eq!(
display_name(r"C:\GGUF\gemma-4-it.gguf").as_deref(),
Some("gemma-4-it")
);
assert_eq!(
display_name("bge-m3-Q8_0.gguf").as_deref(),
Some("bge-m3-Q8_0")
);
assert_eq!(display_name(""), None);
assert_eq!(display_name(".gguf"), None);
assert_eq!(
display_name("-00001-of-00003.gguf").as_deref(),
Some("-00001-of-00003")
);
}
#[test]
fn display_id_normalizes_files_and_leaves_ids_alone() {
assert_eq!(
display_id(r"D:\LLM\GGUF\gemma-4-31B_q4_0-it.gguf"),
"gemma-4-31B_q4_0-it"
);
assert_eq!(
display_id("/models/gpt-oss-120b-Q8_0-00001-of-00003.gguf"),
"gpt-oss-120b-Q8_0"
);
assert_eq!(display_id("meta-llama/Llama-3-8B"), "meta-llama/Llama-3-8B");
assert_eq!(
display_id("anthropic/claude-opus-4.5"),
"anthropic/claude-opus-4.5"
);
assert_eq!(display_id("gemma-3-4b-it"), "gemma-3-4b-it");
assert_eq!(display_id(".gguf"), ".gguf");
}
}