use std::cell::RefCell;
use std::path::{Path, PathBuf};
pub use nichlink::authoring::validation::*;
#[derive(Clone, Debug)]
pub struct AuthoringContext {
package_root: PathBuf,
namespace: String,
}
thread_local! {
static ACTIVE_CONTEXT: RefCell<Option<AuthoringContext>> = const { RefCell::new(None) };
}
struct ContextRestore(Option<AuthoringContext>);
impl Drop for ContextRestore {
fn drop(&mut self) {
ACTIVE_CONTEXT.with(|active| {
*active.borrow_mut() = self.0.take();
});
}
}
impl AuthoringContext {
pub fn new(package_root: impl Into<PathBuf>, namespace: impl Into<String>) -> Self {
Self {
package_root: package_root.into(),
namespace: namespace.into(),
}
}
pub fn scope<T>(&self, operation: impl FnOnce() -> T) -> T {
let previous = ACTIVE_CONTEXT.with(|active| active.replace(Some(self.clone())));
let _restore = ContextRestore(previous);
operation()
}
}
pub(super) fn authoring_namespace() -> String {
ACTIVE_CONTEXT
.with(|active| {
active
.borrow()
.as_ref()
.map(|context| context.namespace.clone())
})
.unwrap_or_else(|| {
nichlink::lexicon::resolve_namespace(
std::env::var(nichlink::lexicon::NAMESPACE_ENV)
.ok()
.as_deref(),
)
.to_owned()
})
}
pub(super) fn legacy_rule_path_for_source(source: &str) -> String {
let directory = Path::new(source).parent().unwrap_or_else(|| Path::new(""));
format!("src/{}/registry/rules/rules.rs", normalized_path(directory))
}
pub(super) fn package_root() -> PathBuf {
if let Some(root) = ACTIVE_CONTEXT.with(|active| {
active
.borrow()
.as_ref()
.map(|context| context.package_root.clone())
}) {
return root;
}
let configured = std::env::var_os(nichlink::lexicon::PACKAGE_ROOT_ENV).map(PathBuf::from);
let current = std::env::current_dir().ok();
nichlink::lexicon::resolve_package_root(
configured.as_deref(),
current.as_deref(),
current
.as_ref()
.is_some_and(|directory| directory.join("Cargo.toml").is_file()),
Path::new(env!("CARGO_MANIFEST_DIR")),
)
}
pub(super) fn source_root() -> PathBuf {
package_root().join("src")
}
#[cfg(test)]
mod tests {
use super::{AuthoringContext, authoring_namespace, package_root};
use std::path::Path;
#[test]
fn nested_authoring_contexts_restore_the_previous_project() {
let outer = AuthoringContext::new("/tmp/nichlink-outer", "outer");
let inner = AuthoringContext::new("/tmp/nichlink-inner", "inner");
outer.scope(|| {
assert_eq!(package_root(), Path::new("/tmp/nichlink-outer"));
assert_eq!(authoring_namespace(), "outer");
inner.scope(|| {
assert_eq!(package_root(), Path::new("/tmp/nichlink-inner"));
assert_eq!(authoring_namespace(), "inner");
});
assert_eq!(package_root(), Path::new("/tmp/nichlink-outer"));
assert_eq!(authoring_namespace(), "outer");
});
}
#[test]
fn panicking_authoring_context_still_restores_its_parent() {
let outer = AuthoringContext::new("/tmp/nichlink-outer", "outer");
let inner = AuthoringContext::new("/tmp/nichlink-inner", "inner");
outer.scope(|| {
let result = std::panic::catch_unwind(|| inner.scope(|| panic!("expected panic")));
assert!(result.is_err());
assert_eq!(package_root(), Path::new("/tmp/nichlink-outer"));
assert_eq!(authoring_namespace(), "outer");
});
}
}