use std::path::{Path, PathBuf};
pub const GENERATED_LIB_FILE: &str = "generated_lib.rs";
pub const RUN_METHOD_CRATE: &str = "nichlink_run_method";
pub const FACE_FIELD_PLUGIN: &str = "plugin";
pub const FACE_FIELD_COLLECTOR: &str = "collector";
pub const SCOPE_ENV: &str = "NICH_LINK_SCOPE";
pub const ENTRY_ENV: &str = "NICH_LINK_ENTRY";
pub const BUILD_VERBOSE_ENV: &str = "NICH_LINK_BUILD_VERBOSE";
pub const PACKAGE_ROOT_ENV: &str = "NICH_LINK_PACKAGE_ROOT";
pub const NAMESPACE_ENV: &str = "NICH_LINK_NAMESPACE";
pub const DEFAULT_NAMESPACE: &str = "nichlink.default";
pub const SCOPE_REGISTRATION_MODULE: &str = "registry_core";
pub const SCOPE_ALWAYS_INCLUDED: &[&str] = &[
SCOPE_REGISTRATION_MODULE,
"registry",
"rules",
"registry_rule",
"root_registry",
];
pub const NICHLINK_DIR: &str = ".nichlink";
pub const EXTERNAL_GRAFT_DIR: &str = "external-grafts";
pub const GRAFT_PLAN_FILE: &str = "graft.plan";
pub(crate) fn path_is_under(path: &str, prefix: &str) -> bool {
path == prefix
|| path
.strip_prefix(prefix)
.is_some_and(|rest| rest.starts_with('/'))
}
pub(crate) fn path_is_strictly_under(path: &str, prefix: &str) -> bool {
path != prefix && path_is_under(path, prefix)
}
pub fn is_registration_path(relative: &str) -> bool {
path_is_under(relative, SCOPE_REGISTRATION_MODULE) && relative != SCOPE_REGISTRATION_MODULE
}
pub const fn same_text(left: &str, right: &str) -> bool {
let (left, right) = (left.as_bytes(), right.as_bytes());
if left.len() != right.len() {
return false;
}
let mut index = 0;
while index < left.len() {
if left[index] != right[index] {
return false;
}
index += 1;
}
true
}
pub fn resolve_package_root(
configured: Option<&Path>,
current_dir: Option<&Path>,
current_dir_holds_a_package: bool,
fallback: &Path,
) -> PathBuf {
if let Some(path) = configured {
return if path.is_absolute() {
path.to_path_buf()
} else {
current_dir.unwrap_or_else(|| Path::new(".")).join(path)
};
}
if current_dir_holds_a_package && let Some(current) = current_dir {
return current.to_path_buf();
}
fallback.to_path_buf()
}
pub fn resolve_namespace(configured: Option<&str>) -> &str {
configured.unwrap_or(DEFAULT_NAMESPACE)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::registry_core::declaration::FACE_FIELD_ORDER;
#[test]
fn the_text_contracts_keep_their_published_values() {
assert_eq!(GENERATED_LIB_FILE, "generated_lib.rs");
assert_eq!(RUN_METHOD_CRATE, "nichlink_run_method");
assert_eq!(FACE_FIELD_PLUGIN, "plugin");
assert_eq!(FACE_FIELD_COLLECTOR, "collector");
assert_eq!(SCOPE_ENV, "NICH_LINK_SCOPE");
assert_eq!(ENTRY_ENV, "NICH_LINK_ENTRY");
assert_eq!(BUILD_VERBOSE_ENV, "NICH_LINK_BUILD_VERBOSE");
assert_eq!(PACKAGE_ROOT_ENV, "NICH_LINK_PACKAGE_ROOT");
assert_eq!(NAMESPACE_ENV, "NICH_LINK_NAMESPACE");
assert_eq!(DEFAULT_NAMESPACE, "nichlink.default");
assert_eq!(NICHLINK_DIR, ".nichlink");
assert_eq!(EXTERNAL_GRAFT_DIR, "external-grafts");
assert_eq!(GRAFT_PLAN_FILE, "graft.plan");
}
#[test]
fn the_package_root_rule_prefers_the_explicit_value_then_a_real_package() {
let fallback = Path::new("/fallback");
assert_eq!(
resolve_package_root(
Some(Path::new("/configured")),
Some(Path::new("/work")),
true,
fallback
),
Path::new("/configured")
);
assert_eq!(
resolve_package_root(
Some(Path::new("inner")),
Some(Path::new("/work")),
false,
fallback
),
Path::new("/work/inner")
);
assert_eq!(
resolve_package_root(None, Some(Path::new("/work")), true, fallback),
Path::new("/work")
);
assert_eq!(
resolve_package_root(None, Some(Path::new("/work")), false, fallback),
fallback
);
assert_eq!(resolve_package_root(None, None, false, fallback), fallback);
}
#[test]
fn the_namespace_default_is_the_documented_one() {
assert_eq!(resolve_namespace(None), DEFAULT_NAMESPACE);
assert_eq!(resolve_namespace(Some("app")), "app");
}
#[test]
fn scope_exemptions_are_the_registration_machinery() {
assert_eq!(
SCOPE_ALWAYS_INCLUDED,
[
"registry_core",
"registry",
"rules",
"registry_rule",
"root_registry"
]
);
assert_eq!(
SCOPE_ALWAYS_INCLUDED.first(),
Some(&SCOPE_REGISTRATION_MODULE)
);
}
#[test]
fn the_plugin_spelling_is_part_of_the_field_vocabulary() {
assert!(
FACE_FIELD_ORDER.contains(&FACE_FIELD_PLUGIN),
"{FACE_FIELD_ORDER:?}"
);
}
#[test]
fn same_text_compares_the_whole_text() {
assert!(same_text("generated_lib.rs", "generated_lib.rs"));
assert!(same_text("", ""));
assert!(!same_text("generated_lib.rs", "generated_lib.r"));
assert!(!same_text("generated_lib.rs", "generated_lib.rss"));
assert!(!same_text("generated_lib.rs", "generated_lib.rt"));
assert!(!same_text("", "generated_lib.rs"));
}
#[test]
fn the_registration_subtree_is_matched_by_directory_boundary() {
assert!(is_registration_path("registry_core/tree/tree.rs"));
assert!(!is_registration_path("registry_core"));
assert!(!is_registration_path("registry_core.rs"));
assert!(!is_registration_path("registry_core_extra/tree.rs"));
assert!(!is_registration_path("registry/rules.rs"));
assert!(!is_registration_path(""));
}
#[test]
fn the_shared_prefix_check_owns_equality_and_the_directory_boundary() {
assert!(path_is_under("registry_core", "registry_core"));
assert!(path_is_under("registry_core/tree/tree.rs", "registry_core"));
assert!(path_is_under("ui/controls/button.rs", "ui/controls"));
assert!(!path_is_under("registry_core.rs", "registry_core"));
assert!(!path_is_under(
"registry_core_extra/tree.rs",
"registry_core"
));
assert!(!path_is_under("", "registry_core"));
assert!(!path_is_under("ui2/button.rs", "ui"));
assert!(!is_registration_path(SCOPE_REGISTRATION_MODULE));
assert!(path_is_under(
"registry_core/tree/tree.rs",
SCOPE_REGISTRATION_MODULE
));
}
#[test]
fn the_strict_prefix_check_excludes_equality() {
assert!(!path_is_strictly_under("registry_core", "registry_core"));
assert!(path_is_strictly_under(
"registry_core/tree/tree.rs",
"registry_core"
));
assert!(!path_is_strictly_under("registry_core.rs", "registry_core"));
assert!(!path_is_strictly_under("ui2/button.rs", "ui"));
assert!(path_is_strictly_under("ui/button.rs", "ui"));
assert!(!path_is_strictly_under("", ""));
assert_eq!(
path_is_strictly_under("a/b", "a"),
path_is_under("a/b", "a")
);
}
}