code-moniker-core 0.2.0

Core symbol-graph types and per-language extractors for code-moniker (pure Rust, no pgrx). Consumed by the CLI and the PostgreSQL extension.
Documentation
use std::path::Path;

use crate::core::moniker::{Moniker, MonikerBuilder};

use super::kinds;

pub(super) fn compute_module_moniker(anchor: &Moniker, uri: &str) -> Moniker {
	let (pkg_pieces, module_name) = split_path(uri);
	let mut b = MonikerBuilder::from_view(anchor.as_view());
	b.segment(crate::lang::kinds::LANG, b"python");
	for piece in pkg_pieces {
		b.segment(kinds::PACKAGE, piece.as_bytes());
	}
	b.segment(kinds::MODULE, module_name.as_bytes());
	b.build()
}

fn split_path(uri: &str) -> (Vec<&str>, &str) {
	let after_scheme = uri.split("://").last().unwrap_or(uri);
	let pieces: Vec<&str> = after_scheme.split('/').filter(|s| !s.is_empty()).collect();
	if pieces.is_empty() {
		return (Vec::new(), "");
	}
	let (last, head) = pieces.split_last().expect("non-empty");
	(head.to_vec(), strip_py_suffix(last))
}

fn strip_py_suffix(name: &str) -> &str {
	Path::new(name)
		.file_stem()
		.and_then(|s| s.to_str())
		.unwrap_or(name)
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn split_path_strips_extension() {
		let (pkg, name) = split_path("foo.py");
		assert!(pkg.is_empty());
		assert_eq!(name, "foo");
	}

	#[test]
	fn split_path_keeps_init_module_name() {
		let (pkg, name) = split_path("acme/__init__.py");
		assert_eq!(pkg, vec!["acme"]);
		assert_eq!(name, "__init__");
	}

	#[test]
	fn split_path_emits_package_chain() {
		let (pkg, name) = split_path("acme/util/text.py");
		assert_eq!(pkg, vec!["acme", "util"]);
		assert_eq!(name, "text");
	}
}