harn_stdlib_catalog/
lib.rs1use std::sync::OnceLock;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct StdlibSource {
19 pub module: &'static str,
20 pub source: &'static str,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct StdlibPromptAsset {
26 pub path: &'static str,
27 pub source: &'static str,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct StdlibCliScript {
35 pub name: &'static str,
39 pub source: &'static str,
42}
43
44#[derive(Debug)]
46pub struct StdlibCatalog {
47 pub sources: &'static [StdlibSource],
48 pub prompt_assets: &'static [StdlibPromptAsset],
49 pub cli_scripts: &'static [StdlibCliScript],
50}
51
52impl StdlibCatalog {
53 pub fn source(&self, module: &str) -> Option<&'static str> {
55 self.sources
56 .iter()
57 .find_map(|entry| (entry.module == module).then_some(entry.source))
58 }
59
60 pub fn prompt_asset(&self, path: &str) -> Option<&'static str> {
62 let path = path.strip_prefix("std/").unwrap_or(path);
63 self.prompt_assets
64 .iter()
65 .find_map(|entry| (entry.path == path).then_some(entry.source))
66 }
67
68 pub fn cli_script(&self, name: &str) -> Option<&'static str> {
70 self.cli_scripts
71 .iter()
72 .find_map(|entry| (entry.name == name).then_some(entry.source))
73 }
74}
75
76static INSTALLED: OnceLock<&'static StdlibCatalog> = OnceLock::new();
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct CatalogNotInstalled;
81
82impl std::fmt::Display for CatalogNotInstalled {
83 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 formatter.write_str(
85 "no Harn standard library is installed: the host must link harn-stdlib and call \
86 harn_stdlib::install() before running Harn code",
87 )
88 }
89}
90
91impl std::error::Error for CatalogNotInstalled {}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct CatalogAlreadyInstalled;
96
97impl std::fmt::Display for CatalogAlreadyInstalled {
98 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 formatter.write_str("a different Harn standard library is already installed")
100 }
101}
102
103impl std::error::Error for CatalogAlreadyInstalled {}
104
105pub fn install(catalog: &'static StdlibCatalog) -> Result<(), CatalogAlreadyInstalled> {
108 let installed = INSTALLED.get_or_init(|| catalog);
109 if std::ptr::eq(*installed, catalog) {
110 Ok(())
111 } else {
112 Err(CatalogAlreadyInstalled)
113 }
114}
115
116pub fn installed() -> Result<&'static StdlibCatalog, CatalogNotInstalled> {
118 INSTALLED.get().copied().ok_or(CatalogNotInstalled)
119}
120
121pub fn builtin_reexports(module: &str) -> &'static [&'static str] {
141 match module {
142 "testing" => &[
143 "assert",
144 "assert_approx",
145 "assert_eq",
146 "assert_matches",
147 "assert_ne",
148 "skip",
149 "value_diff",
150 ],
151 _ => &[],
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 static ONE: StdlibCatalog = StdlibCatalog {
160 sources: &[StdlibSource {
161 module: "text",
162 source: "pub fn shout() {}",
163 }],
164 prompt_assets: &[StdlibPromptAsset {
165 path: "agent/prompts/x.harn.prompt",
166 source: "hello",
167 }],
168 cli_scripts: &[],
169 };
170 static OTHER: StdlibCatalog = StdlibCatalog {
171 sources: &[],
172 prompt_assets: &[],
173 cli_scripts: &[],
174 };
175
176 #[test]
177 fn lookups_answer_by_module_and_by_prefixed_path() {
178 assert_eq!(ONE.source("text"), Some("pub fn shout() {}"));
179 assert_eq!(ONE.source("missing"), None);
180 assert_eq!(
181 ONE.prompt_asset("std/agent/prompts/x.harn.prompt"),
182 Some("hello")
183 );
184 }
185
186 #[test]
187 fn a_reader_before_install_gets_a_typed_error_then_the_one_catalog() {
188 assert_eq!(installed().err(), Some(CatalogNotInstalled));
189 install(&ONE).expect("first install");
190 install(&ONE).expect("same catalog again");
191 assert_eq!(install(&OTHER), Err(CatalogAlreadyInstalled));
192 assert!(std::ptr::eq(
193 installed().expect("installed"),
194 &raw const ONE
195 ));
196 }
197}