use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ModuleName(String);
impl ModuleName {
pub fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
pub fn is_parent_of(&self, other: &ModuleName) -> bool {
other.0.starts_with(&self.0)
&& other.0.len() > self.0.len()
&& other.0.chars().nth(self.0.len()) == Some('.')
}
pub fn parent(&self) -> Option<ModuleName> {
self.0
.rfind('.')
.map(|pos| ModuleName(self.0[..pos].to_string()))
}
}
impl Display for ModuleName {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.0)
}
}
impl FromStr for ModuleName {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Err("Module name cannot be empty")
} else {
Ok(ModuleName(s.to_string()))
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct FunctionName(String);
impl FunctionName {
pub fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
impl Display for FunctionName {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.0)
}
}
impl FromStr for FunctionName {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Err("Function name cannot be empty")
} else {
Ok(FunctionName(s.to_string()))
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct QualifiedName {
pub module: ModuleName,
pub function: FunctionName,
}
impl QualifiedName {
pub fn new(module: ModuleName, function: FunctionName) -> Self {
Self { module, function }
}
pub fn from_string(qualified_name: &str) -> Result<Self, &'static str> {
if let Some(last_dot) = qualified_name.rfind('.') {
let module_part = &qualified_name[..last_dot];
let function_part = &qualified_name[last_dot + 1..];
Ok(QualifiedName {
module: ModuleName::new(module_part),
function: FunctionName::new(function_part),
})
} else {
Err("Qualified name must contain at least one dot")
}
}
pub fn to_string(&self) -> String {
format!("{}.{}", self.module, self.function)
}
}
impl Display for QualifiedName {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}.{}", self.module, self.function)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SourcePath(std::path::PathBuf);
impl SourcePath {
pub fn new(path: impl Into<std::path::PathBuf>) -> Self {
Self(path.into())
}
pub fn as_path(&self) -> &std::path::Path {
&self.0
}
pub fn into_path_buf(self) -> std::path::PathBuf {
self.0
}
pub fn to_string_lossy(&self) -> std::borrow::Cow<str> {
self.0.to_string_lossy()
}
}
impl Display for SourcePath {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.0.display())
}
}
pub use crate::core::types::Version;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_name_parent() {
let module = ModuleName::new("parent.child.grandchild");
let parent = module.parent().unwrap();
assert_eq!(parent.as_str(), "parent.child");
let grandparent = parent.parent().unwrap();
assert_eq!(grandparent.as_str(), "parent");
assert!(grandparent.parent().is_none());
}
#[test]
fn test_module_is_parent_of() {
let parent = ModuleName::new("parent");
let child = ModuleName::new("parent.child");
let unrelated = ModuleName::new("other");
assert!(parent.is_parent_of(&child));
assert!(!parent.is_parent_of(&unrelated));
assert!(!child.is_parent_of(&parent));
}
#[test]
fn test_qualified_name_from_string() {
let qname = QualifiedName::from_string("module.submodule.function").unwrap();
assert_eq!(qname.module.as_str(), "module.submodule");
assert_eq!(qname.function.as_str(), "function");
assert!(QualifiedName::from_string("nomodule").is_err());
}
}