reflectapi_schema/
rename.rs1use std::ops::ControlFlow;
2
3use crate::Visitor;
4
5#[cfg(feature = "glob")]
6pub use self::glob::Glob;
7
8pub(crate) struct Renamer<'a, P> {
9 pattern: P,
10 replacer: &'a str,
11}
12
13impl<'a, P: Pattern> Renamer<'a, P> {
14 pub fn new(pattern: P, replacer: &'a str) -> Self {
15 Self { pattern, replacer }
16 }
17}
18
19impl<'a, P: Pattern> Visitor for Renamer<'a, P> {
20 type Output = usize;
21
22 fn visit_top_level_name(
23 &mut self,
24 name: &mut String,
25 ) -> ControlFlow<Self::Output, Self::Output> {
26 ControlFlow::Continue(
27 self.pattern
28 .rename(name, self.replacer)
29 .map_or(0, |new_name| {
30 *name = new_name;
31 1
32 }),
33 )
34 }
35}
36
37#[allow(private_bounds)]
38pub trait Pattern: Sealed + Copy {
39 fn rename(self, name: &str, with: &str) -> Option<String>;
40}
41
42impl Sealed for &str {}
43
44impl Pattern for &str {
45 fn rename(self, name: &str, replacer: &str) -> Option<String> {
46 if self.ends_with("::") {
47 if let Some(name) = name.strip_prefix(self) {
49 let replacer = replacer.trim_end_matches("::");
50 if replacer.is_empty() {
51 return Some(name.into());
52 }
53
54 Some(format!("{replacer}::{name}"))
55 } else {
56 None
57 }
58 } else {
59 if name == self {
61 Some(replacer.into())
62 } else {
63 None
64 }
65 }
66 }
67}
68
69#[cfg(feature = "glob")]
70mod glob {
71 use core::fmt;
72 use std::str::FromStr;
73
74 use super::{Pattern, Sealed};
75
76 #[derive(Clone)]
78 pub struct Glob(glob::Pattern);
79
80 impl fmt::Display for Glob {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(f, "{}", self.0.to_string().replace('/', "::"))
83 }
84 }
85
86 impl fmt::Debug for Glob {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 write!(f, "{self}")
89 }
90 }
91
92 impl FromStr for Glob {
93 type Err = glob::PatternError;
94
95 fn from_str(s: &str) -> Result<Self, Self::Err> {
96 let s = s.replace("::", "/");
97 Ok(Self(glob::Pattern::new(&s)?))
98 }
99 }
100
101 impl Sealed for &Glob {}
102
103 impl Pattern for &Glob {
104 fn rename(self, name: &str, replacer: &str) -> Option<String> {
105 let path = std::path::PathBuf::from(name.replace("::", "/"));
106 let file_name = path.file_name().expect("non-empty name");
107
108 if path.components().count() <= 1 {
109 return None;
111 }
112
113 if self.0.matches_path_with(
114 &path,
115 glob::MatchOptions {
116 case_sensitive: true,
117 require_literal_separator: true,
118 require_literal_leading_dot: false,
119 },
120 ) {
121 let replacer = replacer.trim_end_matches("::");
122 let name = file_name
123 .to_str()
124 .expect("is utf-8 as it was converted from str");
125 if replacer.is_empty() {
126 return Some(name.into());
127 }
128
129 Some(format!("{replacer}::{name}"))
130 } else {
131 None
132 }
133 }
134 }
135}
136
137trait Sealed {}
138
139#[cfg(test)]
140mod tests {
141 use super::Pattern;
142
143 #[track_caller]
144 fn check(matcher: impl Pattern, name: &str, replacer: &str, expected: Option<&str>) {
145 assert_eq!(matcher.rename(name, replacer).as_deref(), expected);
146 }
147
148 macro_rules! rename {
150 ($matcher:literal to $replacer:literal on $name:expr => $expected:expr) => {
151 check($matcher, $name, $replacer, $expected);
152 };
153 }
154
155 macro_rules! glob_rename {
156 ($matcher:literal to $replacer:literal on $name:expr => $expected:expr) => {
157 #[cfg(feature = "glob")]
158 check(
159 &$matcher.parse::<super::Glob>().unwrap(),
160 $name,
161 $replacer,
162 $expected,
163 );
164 };
165 }
166
167 #[test]
168 fn rename_str() {
169 rename!("foo" to "bar" on "foo" => Some("bar"));
170 rename!("foo" to "bar" on "baz" => None);
171
172 rename!("foo::" to "" on "foo::bar" => Some("bar"));
173
174 rename!("foo::" to "bar::" on "foo::a" => Some("bar::a"));
175 rename!("foo::" to "bar" on "foo::a" => Some("bar::a"));
177 }
178
179 #[test]
180 fn rename_glob() {
181 glob_rename!("foo*" to "bar" on "foo" => None);
183 glob_rename!("foo::*" to "bar::" on "foo::a" => Some("bar::a"));
184 glob_rename!("foo::*" to "bar" on "foo::a" => Some("bar::a"));
186 glob_rename!("foo::*" to "" on "foo::a" => Some("a"));
187 glob_rename!("foo::*" to "bar" on "foo::a" => Some("bar::a"));
188 glob_rename!("foo::**" to "bar" on "foo::a::b::c" => Some("bar::c"));
189
190 glob_rename!("fo*" to "bar" on "foo::a" => None);
192 }
193}