use std::path::{Component, Path, PathBuf};
use harn_stdlib_catalog::{CatalogNotInstalled, StdlibCatalog};
pub fn try_stdlib_catalog() -> Result<&'static StdlibCatalog, CatalogNotInstalled> {
#[cfg(any(test, feature = "bundled-stdlib"))]
let _ = harn_stdlib::install();
harn_stdlib_catalog::installed()
}
pub fn stdlib_catalog() -> &'static StdlibCatalog {
try_stdlib_catalog().unwrap_or_else(|error| panic!("{error}"))
}
pub(crate) fn get_stdlib_source(module: &str) -> Option<&'static str> {
stdlib_catalog().source(module)
}
pub(crate) fn builtin_reexports(module: &str) -> &'static [&'static str] {
harn_stdlib_catalog::builtin_reexports(module)
}
pub(crate) fn stdlib_virtual_path(module: &str) -> PathBuf {
PathBuf::from(format!("<std>/{module}"))
}
pub(crate) fn is_stdlib_virtual_path(path: &Path) -> bool {
path.to_str().is_some_and(|path| path.starts_with("<std>/"))
}
pub(crate) fn canonical_virtual_path(path: &Path) -> Option<PathBuf> {
if is_stdlib_virtual_path(path) {
return Some(path.to_path_buf());
}
is_embedded_stdlib_spelling(path).then(|| crate::manifest_walk::normalize_lexically(path))
}
pub(crate) fn is_embedded_stdlib_spelling(path: &Path) -> bool {
let Some(path) = path.to_str() else {
return false;
};
["<std>", "<stdlib>"].iter().any(|root| {
path.strip_prefix(root)
.is_some_and(|rest| rest.is_empty() || rest.starts_with('/'))
})
}
pub(crate) fn relative_stdlib_module(current_file: &Path, import_path: &str) -> Option<String> {
let current = current_file.to_str()?.strip_prefix("<std>/")?;
let mut parts = current.split('/').collect::<Vec<_>>();
parts.pop()?;
for component in Path::new(import_path).components() {
match component {
Component::CurDir => {}
Component::ParentDir => {
parts.pop()?;
}
Component::Normal(part) => parts.push(part.to_str()?),
Component::RootDir | Component::Prefix(_) => return None,
}
}
let last = parts.last_mut()?;
*last = last.strip_suffix(".harn").unwrap_or(last);
let module = parts.join("/");
get_stdlib_source(&module).map(|_| module)
}
#[cfg(test)]
mod tests {
#[test]
fn compiler_crates_do_not_depend_on_the_embedded_sources() {
let crates = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("crates directory");
for (crate_name, allowed_optional) in [("harn-modules", true), ("harn-vm", false)] {
let manifest = std::fs::read_to_string(crates.join(crate_name).join("Cargo.toml"))
.expect("read manifest");
let manifest: toml::Table = toml::from_str(&manifest).expect("parse manifest");
let dependency = manifest
.get("dependencies")
.and_then(|dependencies| dependencies.get("harn-stdlib"));
let optional = dependency
.and_then(|dependency| dependency.get("optional"))
.and_then(toml::Value::as_bool)
== Some(true);
assert!(
dependency.is_none() || (allowed_optional && optional),
"{crate_name} must not depend on harn-stdlib; read the stdlib through harn-stdlib-catalog"
);
}
}
}