harn_stdlib_catalog/
lib.rs1use std::sync::{LazyLock, 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
76pub type LazyCatalog = LazyLock<StdlibCatalog>;
79
80static INSTALLED: OnceLock<&'static LazyCatalog> = OnceLock::new();
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub struct CatalogNotInstalled;
85
86impl std::fmt::Display for CatalogNotInstalled {
87 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 formatter.write_str(
89 "no Harn standard library is installed: the host must link harn-stdlib and call \
90 harn_stdlib::install() before running Harn code",
91 )
92 }
93}
94
95impl std::error::Error for CatalogNotInstalled {}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub struct CatalogAlreadyInstalled;
100
101impl std::fmt::Display for CatalogAlreadyInstalled {
102 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 formatter.write_str("a different Harn standard library is already installed")
104 }
105}
106
107impl std::error::Error for CatalogAlreadyInstalled {}
108
109pub fn install(catalog: &'static LazyCatalog) -> Result<(), CatalogAlreadyInstalled> {
112 let installed = INSTALLED.get_or_init(|| catalog);
113 if std::ptr::eq(*installed, catalog) {
114 Ok(())
115 } else {
116 Err(CatalogAlreadyInstalled)
117 }
118}
119
120pub fn ensure_installed() -> Result<(), CatalogNotInstalled> {
123 INSTALLED.get().map(|_| ()).ok_or(CatalogNotInstalled)
124}
125
126pub fn installed() -> Result<&'static StdlibCatalog, CatalogNotInstalled> {
128 INSTALLED
129 .get()
130 .map(|catalog| &***catalog)
131 .ok_or(CatalogNotInstalled)
132}
133
134pub fn builtin_reexports(module: &str) -> &'static [&'static str] {
154 match module {
155 "testing" => &[
156 "assert",
157 "assert_approx",
158 "assert_eq",
159 "assert_matches",
160 "assert_ne",
161 "skip",
162 "value_diff",
163 ],
164 _ => &[],
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 static ONE: LazyCatalog = LazyCatalog::new(|| StdlibCatalog {
173 sources: &[StdlibSource {
174 module: "text",
175 source: "pub fn shout() {}",
176 }],
177 prompt_assets: &[StdlibPromptAsset {
178 path: "agent/prompts/x.harn.prompt",
179 source: "hello",
180 }],
181 cli_scripts: &[],
182 });
183 static OTHER: LazyCatalog = LazyCatalog::new(|| StdlibCatalog {
184 sources: &[],
185 prompt_assets: &[],
186 cli_scripts: &[],
187 });
188
189 #[test]
190 fn lookups_answer_by_module_and_by_prefixed_path() {
191 assert_eq!(ONE.source("text"), Some("pub fn shout() {}"));
192 assert_eq!(ONE.source("missing"), None);
193 assert_eq!(
194 ONE.prompt_asset("std/agent/prompts/x.harn.prompt"),
195 Some("hello")
196 );
197 }
198
199 static BUILDS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
200 static COUNTED: LazyCatalog = LazyCatalog::new(|| {
201 BUILDS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
202 StdlibCatalog {
203 sources: &[],
204 prompt_assets: &[],
205 cli_scripts: &[],
206 }
207 });
208
209 #[test]
210 fn a_reader_before_install_gets_a_typed_error_then_the_one_catalog() {
211 assert_eq!(installed().err(), Some(CatalogNotInstalled));
212 assert_eq!(ensure_installed(), Err(CatalogNotInstalled));
213 install(&COUNTED).expect("first install");
214 install(&COUNTED).expect("same catalog again");
215 assert_eq!(install(&OTHER), Err(CatalogAlreadyInstalled));
216 ensure_installed().expect("installed");
218 assert_eq!(BUILDS.load(std::sync::atomic::Ordering::SeqCst), 0);
219 let counted: &StdlibCatalog = &COUNTED;
220 assert!(std::ptr::eq(installed().expect("installed"), counted));
221 assert_eq!(BUILDS.load(std::sync::atomic::Ordering::SeqCst), 1);
222 }
223}