cargo_docs_md/utils.rs
1//! Shared utility functions used across the documentation generator.
2//!
3//! This module contains small, general-purpose helpers that are used
4//! by multiple components and don't belong to any specific domain.
5//!
6//! # Organization
7//!
8//! Utilities are organized into unit structs by category:
9//! - [`PathUtils`]: Rust path manipulation utilities
10
11/// Utilities for working with Rust paths (e.g., `std::vec::Vec`).
12///
13/// Rust paths use `::` as a separator. This struct provides methods
14/// for common path operations used throughout the documentation generator.
15///
16/// # Design Note
17///
18/// This is a unit struct (no fields) that serves as a namespace for
19/// related utility functions. All methods are associated functions
20/// that don't require an instance.
21///
22/// # Examples
23///
24/// ```
25/// use cargo_docs_md::utils::PathUtils;
26///
27/// // Extract the short name from a qualified path
28/// assert_eq!(PathUtils::short_name("std::vec::Vec"), "Vec");
29/// assert_eq!(PathUtils::short_name("Clone"), "Clone");
30/// ```
31pub struct PathUtils;
32
33impl PathUtils {
34 /// Extract the short name (last segment) from a qualified Rust path.
35 ///
36 /// Rust paths use `::` as a separator. This function returns the final
37 /// segment, which is typically the item's simple name without module prefix.
38 ///
39 /// # Examples
40 ///
41 /// ```
42 /// use cargo_docs_md::utils::PathUtils;
43 ///
44 /// assert_eq!(PathUtils::short_name("std::vec::Vec"), "Vec");
45 /// assert_eq!(PathUtils::short_name("std::collections::HashMap"), "HashMap");
46 /// assert_eq!(PathUtils::short_name("Clone"), "Clone");
47 /// assert_eq!(PathUtils::short_name(""), "");
48 /// ```
49 ///
50 /// # Edge Cases
51 ///
52 /// - Empty string returns empty string
53 /// - Path ending with `::` returns empty string (e.g., `"foo::"` -> `""`)
54 /// - Single segment returns itself (e.g., `"Vec"` -> `"Vec"`)
55 #[inline]
56 #[must_use]
57 pub fn short_name(path: &str) -> &str {
58 // Using rsplit().next() is more efficient than split().last()
59 // because it doesn't need to traverse the entire iterator
60 path.rsplit("::").next().unwrap_or(path)
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn short_name_qualified_path() {
70 assert_eq!(PathUtils::short_name("std::vec::Vec"), "Vec");
71 assert_eq!(
72 PathUtils::short_name("std::collections::HashMap"),
73 "HashMap"
74 );
75 assert_eq!(PathUtils::short_name("tokio::sync::mpsc::Sender"), "Sender");
76 }
77
78 #[test]
79 fn short_name_simple() {
80 assert_eq!(PathUtils::short_name("Vec"), "Vec");
81 assert_eq!(PathUtils::short_name("Clone"), "Clone");
82 assert_eq!(PathUtils::short_name("u32"), "u32");
83 }
84
85 #[test]
86 fn short_name_edge_cases() {
87 assert_eq!(PathUtils::short_name(""), "");
88 assert_eq!(PathUtils::short_name("::"), "");
89 assert_eq!(PathUtils::short_name("foo::"), "");
90 assert_eq!(PathUtils::short_name("::foo"), "foo");
91 }
92
93 #[test]
94 fn short_name_with_turbofish() {
95 // Turbofish syntax `Type::<T>` contains `::` so it gets split
96 // This extracts the generic part `<T>` since `::` is the separator
97 assert_eq!(PathUtils::short_name("Type::<T>"), "<T>");
98
99 // A single colon is NOT a Rust path separator
100 assert_eq!(PathUtils::short_name("Type:T"), "Type:T");
101 }
102}