use anyhow::Result;
use std::fs;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct Language {
pub code: String,
pub title: String,
}
pub fn parse_langs(book_dir: &Path) -> Result<Vec<Language>> {
let langs_path = book_dir.join("LANGS.md");
if !langs_path.exists() {
return Ok(Vec::new());
}
let content = fs::read_to_string(&langs_path)?;
let mut languages = Vec::new();
for line in content.lines() {
let line = line.trim();
if (line.starts_with('*') || line.starts_with('-'))
&& line.contains('[')
&& line.contains("](")
{
if let Some(lang) = parse_lang_line(line) {
languages.push(lang);
}
}
}
Ok(languages)
}
fn parse_lang_line(line: &str) -> Option<Language> {
let title_start = line.find('[')? + 1;
let rest = &line[title_start..];
let title_len = rest.find("](")?;
let title = rest[..title_len].to_string();
let after_sep = &rest[title_len + 2..];
let code_len = after_sep.find(')')?;
let mut code = after_sep[..code_len].to_string();
if code.ends_with('/') {
code.pop();
}
Some(Language { code, title })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_lang_line() {
let lang = parse_lang_line("* [Japanese](jp/)").unwrap();
assert_eq!(lang.code, "jp");
assert_eq!(lang.title, "Japanese");
let lang = parse_lang_line("- [Vietnamese](vn/)").unwrap();
assert_eq!(lang.code, "vn");
assert_eq!(lang.title, "Vietnamese");
}
#[test]
fn test_parse_lang_line_malformed_no_panic() {
let malformed = [
"* [Japanese](jp/", "* [Japanese(jp/)", "* Japanese](jp/)", "* [Japanese])(jp/", "* )[Japanese](", "* [](", ];
for line in malformed {
let _ = parse_lang_line(line);
}
}
#[test]
fn test_parse_langs_skips_malformed_line() {
use std::io::Write;
let dir = std::env::temp_dir().join("gb_langs_test");
std::fs::create_dir_all(&dir).unwrap();
let mut f = std::fs::File::create(dir.join("LANGS.md")).unwrap();
writeln!(f, "* [Japanese](jp/)").unwrap();
writeln!(f, "* [Broken](oops/").unwrap();
writeln!(f, "* [Vietnamese](vn/)").unwrap();
drop(f);
let langs = parse_langs(&dir).unwrap();
let codes: Vec<&str> = langs.iter().map(|l| l.code.as_str()).collect();
assert!(codes.contains(&"jp"));
assert!(codes.contains(&"vn"));
std::fs::remove_dir_all(&dir).ok();
}
}