use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Prompt {
pub name: String,
pub description: String,
pub template: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PromptLibrary {
#[serde(default)]
pub prompts: Vec<Prompt>,
}
impl PromptLibrary {
pub fn load(path: &Path) -> Result<Self> {
let raw = std::fs::read_to_string(path).map_err(Error::Io)?;
serde_hjson::from_str(&raw).map_err(|e| Error::Config(e.to_string()))
}
pub fn find(&self, name: &str) -> Option<&Prompt> {
self.prompts.iter().find(|p| p.name == name)
}
pub fn find_lang(&self, name: &str, lang_filter: Option<&str>) -> Option<&Prompt> {
self.prompts.iter().find(|p| {
if p.name != name {
return false;
}
match (lang_filter, p.language.as_deref()) {
(Some(want), Some(have)) => have.eq_ignore_ascii_case(want),
(None, None) => true,
_ => false,
}
})
}
}
pub fn iso_from_long(language: &str) -> &'static str {
match language.to_lowercase().as_str() {
"russian" => "ru",
"french" => "fr",
"german" => "de",
"spanish" => "es",
_ => "en",
}
}
#[allow(dead_code)]
pub fn iso_to_long(code: &str) -> &'static str {
match code.to_lowercase().as_str() {
"ru" => "russian",
"fr" => "french",
"de" => "german",
"es" => "spanish",
"en" => "english",
_ => "english",
}
}
pub fn iso_from_alpha3(alpha3: &str) -> Option<&'static str> {
match alpha3.to_lowercase().as_str() {
"eng" => Some("en"),
"rus" => Some("ru"),
"fra" | "fre" => Some("fr"),
"deu" | "ger" => Some("de"),
"spa" => Some("es"),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn lib() -> PromptLibrary {
PromptLibrary {
prompts: vec![
Prompt {
name: "grammar-check".into(),
description: "".into(),
template: "EN body".into(),
language: Some("en".into()),
},
Prompt {
name: "grammar-check".into(),
description: "".into(),
template: "RU body".into(),
language: Some("ru".into()),
},
Prompt {
name: "legacy-prompt".into(),
description: "".into(),
template: "untagged".into(),
language: None,
},
],
}
}
#[test]
fn find_lang_strict_match_returns_in_language() {
let l = lib();
let p = l.find_lang("grammar-check", Some("ru")).unwrap();
assert_eq!(p.template, "RU body");
}
#[test]
fn find_lang_strict_match_is_case_insensitive() {
let l = lib();
let p = l.find_lang("grammar-check", Some("RU")).unwrap();
assert_eq!(p.template, "RU body");
}
#[test]
fn find_lang_strict_skips_untagged() {
let l = lib();
assert!(l.find_lang("legacy-prompt", Some("en")).is_none());
}
#[test]
fn find_lang_none_filter_only_matches_untagged() {
let l = lib();
let p = l.find_lang("legacy-prompt", None).unwrap();
assert_eq!(p.template, "untagged");
assert!(l.find_lang("grammar-check", None).is_none());
}
#[test]
fn iso_from_long_maps_supported_languages() {
assert_eq!(iso_from_long("English"), "en");
assert_eq!(iso_from_long("russian"), "ru");
assert_eq!(iso_from_long("FRENCH"), "fr");
assert_eq!(iso_from_long(""), "en");
assert_eq!(iso_from_long("klingon"), "en");
}
#[test]
fn iso_from_alpha3_filters_unsupported() {
assert_eq!(iso_from_alpha3("eng"), Some("en"));
assert_eq!(iso_from_alpha3("rus"), Some("ru"));
assert_eq!(iso_from_alpha3("fra"), Some("fr"));
assert_eq!(iso_from_alpha3("fre"), Some("fr"));
assert_eq!(iso_from_alpha3("ita"), None);
assert_eq!(iso_from_alpha3("zho"), None);
}
}