use std::fmt::{Display, Formatter, Result};
use std::ops::Deref;
use crate::constants::PATH_QUALIFIER_SELF;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub(crate) struct Segments(Vec<String>);
impl Segments {
pub(crate) fn new<I, S>(iter: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self(iter.into_iter().map(Into::into).collect())
}
#[must_use]
pub(crate) const fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub(crate) const fn len(&self) -> usize {
self.0.len()
}
}
impl Deref for Segments {
type Target = [String];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<Vec<String>> for Segments {
fn from(v: Vec<String>) -> Self {
Self(v)
}
}
impl Display for Segments {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.0.join("::"))
}
}
impl Display for TypeReference {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.to_path_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TypeReference {
segments: Segments,
prefix: PathPrefix,
suffix: PathSuffix,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum PathSuffix {
#[default]
None,
Alias(String),
Glob,
Group(Vec<GroupItem>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum PathPrefix {
#[default]
None,
Crate,
SelfModule,
Super(usize),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum GroupItem {
Simple(String),
Aliased { name: String, alias: String },
SelfItem { alias: Option<String> },
Glob,
Nested {
prefix: Vec<String>,
items: Vec<Self>,
},
}
impl TypeReference {
pub fn new<I, S>(segments: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
segments: Segments::new(segments),
prefix: PathPrefix::None,
suffix: PathSuffix::None,
}
}
#[must_use]
pub const fn with_crate_prefix(mut self) -> Self {
self.prefix = PathPrefix::Crate;
self
}
#[must_use]
pub const fn with_self_prefix(mut self) -> Self {
self.prefix = PathPrefix::SelfModule;
self
}
#[must_use]
pub const fn with_super(mut self, levels: usize) -> Self {
self.prefix = PathPrefix::Super(levels);
self
}
#[must_use]
pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
self.suffix = PathSuffix::Alias(alias.into());
self
}
#[must_use]
pub fn with_glob(mut self) -> Self {
self.suffix = PathSuffix::Glob;
self
}
#[must_use]
pub fn with_group(mut self, items: Vec<GroupItem>) -> Self {
self.suffix = PathSuffix::Group(items);
self
}
#[must_use]
pub fn segments(&self) -> &[String] {
&self.segments
}
#[must_use]
pub const fn prefix(&self) -> PathPrefix {
self.prefix
}
#[must_use]
pub(crate) const fn with_prefix(mut self, prefix: PathPrefix) -> Self {
self.prefix = prefix;
self
}
#[must_use]
pub const fn is_relative(&self) -> bool {
!matches!(self.prefix, PathPrefix::None)
}
#[must_use]
pub fn is_from_crate(&self, crate_name: &str) -> bool {
if self.prefix == PathPrefix::None
&& let Some(first_segment) = self.segments.first()
{
return first_segment == crate_name;
}
false
}
#[must_use]
pub const fn has_glob(&self) -> bool {
matches!(self.suffix, PathSuffix::Glob)
}
#[must_use]
pub const fn has_group(&self) -> bool {
matches!(self.suffix, PathSuffix::Group(_))
}
#[must_use]
pub fn final_name(&self) -> Option<&str> {
match &self.suffix {
PathSuffix::Alias(alias) => Some(alias.as_str()),
_ => self.segments.last().map(String::as_str),
}
}
pub(crate) fn append_segment(mut self, segment: impl Into<String>) -> Self {
self.segments.0.push(segment.into());
self
}
#[must_use]
pub(crate) fn clone_with(&self, prefix: bool, suffix: bool) -> Self {
Self {
segments: self.segments.clone(),
prefix: if prefix {
self.prefix
} else {
PathPrefix::None
},
suffix: if suffix {
self.suffix.clone()
} else {
PathSuffix::None
},
}
}
#[must_use]
pub(crate) const fn suffix(&self) -> &PathSuffix {
&self.suffix
}
#[must_use]
pub(crate) fn with_segments_and_prefix(
mut self,
segments: Vec<String>,
prefix: PathPrefix,
) -> Self {
self.segments = Segments::from(segments);
self.prefix = prefix;
self
}
#[must_use]
pub fn truncate_to_depth(&self, depth: usize) -> Self {
if self.segments.len() <= depth {
return self.clone();
}
Self {
segments: Segments::from(self.segments[..depth].to_vec()),
prefix: self.prefix,
suffix: PathSuffix::None,
}
}
#[must_use]
pub fn to_path_string(&self) -> String {
let mut result = String::new();
match self.prefix {
PathPrefix::None => {}
PathPrefix::Crate => result.push_str("crate::"),
PathPrefix::SelfModule => result.push_str("self::"),
PathPrefix::Super(n) => {
for _ in 0..n {
result.push_str("super::");
}
}
}
if !self.segments.is_empty() {
result.push_str(&self.segments.to_string());
}
match &self.suffix {
PathSuffix::None => {}
PathSuffix::Group(group) => {
if !self.segments.is_empty() {
result.push_str("::");
}
result.push('{');
let items: Vec<_> = group.iter().map(ToString::to_string).collect();
result.push_str(&items.join(", "));
result.push('}');
}
PathSuffix::Glob => {
result.push_str("::*");
}
PathSuffix::Alias(alias) => {
result.push_str(" as ");
result.push_str(alias);
}
}
result
}
}
impl Display for GroupItem {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::Simple(name) => write!(f, "{name}"),
Self::Aliased { name, alias } => write!(f, "{name} as {alias}"),
Self::SelfItem { alias: None } => write!(f, "{PATH_QUALIFIER_SELF}"),
Self::SelfItem { alias: Some(a) } => write!(f, "{PATH_QUALIFIER_SELF} as {a}"),
Self::Glob => write!(f, "*"),
Self::Nested { prefix, items } => {
write!(f, "{}", prefix.join("::"))?;
if items.is_empty() {
Ok(())
} else if items.len() == 1 {
if let Some(Self::Simple(name)) = items.first() {
write!(f, "::{name}")
} else {
write!(
f,
"::{{{}}}",
items
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
)
}
} else {
let items_str: Vec<_> = items.iter().map(ToString::to_string).collect();
write!(f, "::{{{}}}", items_str.join(", "))
}
}
}
}
}
impl From<&str> for GroupItem {
fn from(s: &str) -> Self {
if s == "*" {
Self::Glob
} else if s == PATH_QUALIFIER_SELF {
Self::SelfItem { alias: None }
} else {
Self::Simple(s.to_owned())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_path() {
let r = TypeReference::new(["std", "collections", "HashMap"]);
assert_eq!(r.to_path_string(), "std::collections::HashMap");
assert!(!r.is_relative());
assert!(!r.has_glob());
assert!(!r.has_group());
}
#[test]
fn test_crate_prefix() {
let r = TypeReference::new(["module", "Type"]).with_crate_prefix();
assert_eq!(r.to_path_string(), "crate::module::Type");
assert!(r.is_relative());
}
#[test]
fn test_self_prefix() {
let r = TypeReference::new(["submodule", "Type"]).with_self_prefix();
assert_eq!(r.to_path_string(), "self::submodule::Type");
assert!(r.is_relative());
}
#[test]
fn test_super_prefix() {
let r = TypeReference::new(["sibling", "Type"]).with_super(2);
assert_eq!(r.to_path_string(), "super::super::sibling::Type");
assert!(r.is_relative());
}
#[test]
fn test_glob() {
let r = TypeReference::new(["std", "collections"]).with_glob();
assert_eq!(r.to_path_string(), "std::collections::*");
assert!(r.has_glob());
}
#[test]
fn test_alias() {
let r = TypeReference::new(["std", "collections", "HashMap"]).with_alias("Map");
assert_eq!(r.to_path_string(), "std::collections::HashMap as Map");
assert_eq!(r.final_name(), Some("Map"));
}
#[test]
fn test_group() {
let r = TypeReference::new(["std", "collections"])
.with_group(vec!["HashMap".into(), "HashSet".into()]);
assert_eq!(r.to_path_string(), "std::collections::{HashMap, HashSet}");
assert!(r.has_group());
}
#[test]
fn test_group_with_alias() {
let r = TypeReference::new(["std", "collections"]).with_group(vec![
GroupItem::Simple("HashMap".into()),
GroupItem::Aliased {
name: "HashSet".into(),
alias: "Set".into(),
},
]);
assert_eq!(
r.to_path_string(),
"std::collections::{HashMap, HashSet as Set}"
);
}
#[test]
fn test_group_with_self() {
let r = TypeReference::new(["module"]).with_group(vec![
GroupItem::SelfItem { alias: None },
GroupItem::Simple("Type".into()),
]);
assert_eq!(r.to_path_string(), "module::{self, Type}");
}
#[test]
fn test_nested_group() {
let r = TypeReference::new(["std"]).with_group(vec![GroupItem::Nested {
prefix: vec!["collections".into()],
items: vec!["HashMap".into(), "HashSet".into()],
}]);
assert_eq!(r.to_path_string(), "std::{collections::{HashMap, HashSet}}");
}
#[test]
fn test_complex_nested_group() {
let r = TypeReference::new(["m", "n"]).with_group(vec![
GroupItem::Simple("a".into()),
GroupItem::Simple("b".into()),
GroupItem::Nested {
prefix: vec!["c".into()],
items: vec![GroupItem::Simple("x".into()), GroupItem::Simple("y".into())],
},
]);
assert_eq!(r.to_path_string(), "m::n::{a, b, c::{x, y}}");
assert!(r.has_group());
assert!(!r.is_relative());
}
#[test]
fn test_nested() {
let r = TypeReference::new(Vec::<&str>::new())
.with_super(1)
.with_group(vec![
GroupItem::Simple("Id".into()),
GroupItem::Simple("ItemDisplay".into()),
GroupItem::Simple("ItemDisplaySettings".into()),
GroupItem::Simple("OptionNameExt".into()),
GroupItem::Simple("OptionToString".into()),
GroupItem::Nested {
prefix: vec!["area".into()],
items: vec![GroupItem::Simple("Area".into())],
},
GroupItem::Nested {
prefix: vec!["display".into()],
items: vec![GroupItem::Simple("NameDisplay".into())],
},
GroupItem::Nested {
prefix: vec!["lifespan".into()],
items: vec![GroupItem::Simple("LifeSpan".into())],
},
GroupItem::Nested {
prefix: vec!["ratings".into()],
items: vec![
GroupItem::Simple("AllRatings".into()),
GroupItem::Simple("UserRating".into()),
GroupItem::Simple("get_rating".into()),
],
},
]);
assert_eq!(
r.to_path_string(),
"super::{Id, ItemDisplay, ItemDisplaySettings, OptionNameExt, OptionToString, area::Area, display::NameDisplay, lifespan::LifeSpan, ratings::{AllRatings, UserRating, get_rating}}"
);
assert!(r.has_group());
assert!(r.is_relative());
}
#[test]
fn truncate_to_depth_zero_yields_empty_segments() {
let r = TypeReference::new(["a", "b", "c"]).with_crate_prefix();
let t = r.truncate_to_depth(0);
assert_eq!(t.segments().len(), 0);
assert_eq!(t.prefix(), PathPrefix::Crate);
}
#[test]
fn truncate_to_depth_reduces_to_given_depth() {
let r = TypeReference::new(["a", "b", "c"]);
let t = r.truncate_to_depth(2);
assert_eq!(t.to_path_string(), "a::b");
}
#[test]
fn truncate_to_depth_equal_to_len_returns_clone() {
let r = TypeReference::new(["a", "b", "c"]);
let t = r.truncate_to_depth(3);
assert_eq!(t.to_path_string(), "a::b::c");
}
#[test]
fn truncate_to_depth_greater_than_len_returns_clone() {
let r = TypeReference::new(["a", "b"]);
let t = r.truncate_to_depth(10);
assert_eq!(t.to_path_string(), "a::b");
}
#[test]
fn truncate_to_depth_drops_suffix() {
let r = TypeReference::new(["a", "b", "c"]).with_glob();
let t = r.truncate_to_depth(2);
assert!(!t.has_glob());
assert_eq!(t.to_path_string(), "a::b");
}
#[test]
fn truncate_to_depth_drops_alias() {
let r = TypeReference::new(["a", "b", "c"]).with_alias("X");
let t = r.truncate_to_depth(2);
assert_eq!(t.to_path_string(), "a::b");
}
}