1use crate::error::{Error, InvalidKrateName};
2
3#[cfg(test)]
4#[macro_export]
6macro_rules! kn {
7 ($kn:literal) => {
8 $crate::KrateName($kn)
9 };
10}
11
12#[derive(Copy, Clone)]
16pub struct KrateName<'name>(pub(crate) &'name str);
17
18impl<'name> KrateName<'name> {
19 #[inline]
32 pub fn crates_io(name: &'name str) -> Result<Self, Error> {
33 Self::validated(name, Some(64))
34 }
35
36 #[inline]
47 pub fn cargo(name: &'name str) -> Result<Self, Error> {
48 Self::validated(name, None)
49 }
50
51 fn validated(name: &'name str, max_len: Option<usize>) -> Result<Self, Error> {
52 if name.is_empty() {
53 return Err(InvalidKrateName::InvalidLength(0).into());
54 }
55
56 let mut chars = name.chars().enumerate();
57
58 while let Some((i, c)) = chars.next() {
59 if i == 0 && c != '_' && !c.is_ascii_alphabetic() {
60 return Err(InvalidKrateName::InvalidCharacter {
61 invalid: c,
62 index: i,
63 }
64 .into());
65 }
66
67 if max_len == Some(i) {
68 return Err(InvalidKrateName::InvalidLength(i + 1 + chars.count()).into());
69 }
70
71 if c != '-' && c != '_' && !c.is_ascii_alphanumeric() {
72 return Err(InvalidKrateName::InvalidCharacter {
73 invalid: c,
74 index: i,
75 }
76 .into());
77 }
78 }
79
80 use crate::error::ReservedNameKind::{Artifact, Keyword, Standard, Windows};
88 const DISALLOWED: &[(&str, crate::error::ReservedNameKind)] = &[
89 ("Self", Keyword),
90 ("abstract", Keyword),
91 ("alloc", Standard),
92 ("as", Keyword),
93 ("async", Keyword),
94 ("aux", Windows),
95 ("await", Keyword),
96 ("become", Keyword),
97 ("box", Keyword),
98 ("break", Keyword),
99 ("build", Artifact),
100 ("com1", Windows),
101 ("com2", Windows),
102 ("com3", Windows),
103 ("com4", Windows),
104 ("com5", Windows),
105 ("com6", Windows),
106 ("com7", Windows),
107 ("com8", Windows),
108 ("com9", Windows),
109 ("con", Windows),
110 ("const", Keyword),
111 ("continue", Keyword),
112 ("core", Standard),
113 ("crate", Keyword),
114 ("deps", Artifact),
115 ("do", Keyword),
116 ("dyn", Keyword),
117 ("else", Keyword),
118 ("enum", Keyword),
119 ("examples", Artifact),
120 ("extern", Keyword),
121 ("false", Keyword),
122 ("final", Keyword),
123 ("fn", Keyword),
124 ("for", Keyword),
125 ("if", Keyword),
126 ("impl", Keyword),
127 ("in", Keyword),
128 ("incremental", Artifact),
129 ("let", Keyword),
130 ("loop", Keyword),
131 ("lpt1", Windows),
132 ("lpt2", Windows),
133 ("lpt3", Windows),
134 ("lpt4", Windows),
135 ("lpt5", Windows),
136 ("lpt6", Windows),
137 ("lpt7", Windows),
138 ("lpt8", Windows),
139 ("lpt9", Windows),
140 ("macro", Keyword),
141 ("match", Keyword),
142 ("mod", Keyword),
143 ("move", Keyword),
144 ("mut", Keyword),
145 ("nul", Windows),
146 ("override", Keyword),
147 ("priv", Keyword),
148 ("prn", Windows),
149 ("proc-macro", Standard),
150 ("proc_macro", Standard),
151 ("pub", Keyword),
152 ("ref", Keyword),
153 ("return", Keyword),
154 ("self", Keyword),
155 ("static", Keyword),
156 ("std", Standard),
157 ("struct", Keyword),
158 ("super", Keyword),
159 ("test", Standard),
160 ("trait", Keyword),
161 ("true", Keyword),
162 ("try", Keyword),
163 ("type", Keyword),
164 ("typeof", Keyword),
165 ("unsafe", Keyword),
166 ("unsized", Keyword),
167 ("use", Keyword),
168 ("virtual", Keyword),
169 ("where", Keyword),
170 ("while", Keyword),
171 ("yield", Keyword),
172 ];
173
174 if let Ok(i) = DISALLOWED.binary_search_by_key(&name, |(k, _v)| k) {
175 let (reserved, kind) = DISALLOWED[i];
176 Err(InvalidKrateName::ReservedName { reserved, kind }.into())
177 } else {
178 Ok(Self(name))
179 }
180 }
181}
182
183impl<'name> TryFrom<&'name str> for KrateName<'name> {
187 type Error = Error;
188 #[inline]
189 fn try_from(s: &'name str) -> Result<Self, Self::Error> {
190 if s.is_empty() {
191 Err(InvalidKrateName::InvalidLength(0).into())
192 } else if let Some((index, invalid)) = s
193 .chars()
194 .enumerate()
195 .find(|(_i, c)| *c != '-' && *c != '_' && !c.is_ascii_alphanumeric())
196 {
197 Err(InvalidKrateName::InvalidCharacter { invalid, index }.into())
198 } else {
199 Ok(Self(s))
200 }
201 }
202}
203
204impl KrateName<'_> {
205 pub fn prefix(&self, acc: &mut String, sep: char) {
215 let name = self.0;
216 match name.len() {
217 0 => unreachable!(),
218 1 => acc.push('1'),
219 2 => acc.push('2'),
220 3 => {
221 acc.push('3');
222 acc.push(sep);
223 acc.push_str(&name[..1]);
224 }
225 _ => {
226 acc.push_str(&name[..2]);
227 acc.push(sep);
228 acc.push_str(&name[2..4]);
229 }
230 }
231 }
232
233 pub fn relative_path(&self, sep: Option<char>) -> String {
244 let name = self.0;
245 let mut rel_path = String::with_capacity(name.len() + 6);
247 let sep = sep.unwrap_or(std::path::MAIN_SEPARATOR);
248
249 self.prefix(&mut rel_path, sep);
250 rel_path.push(sep);
251 rel_path.push_str(name);
252
253 rel_path.make_ascii_lowercase();
256
257 rel_path
258 }
259}
260
261use std::fmt;
262
263impl fmt::Display for KrateName<'_> {
264 #[inline]
265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 f.write_str(self.0)
267 }
268}
269
270impl fmt::Debug for KrateName<'_> {
271 #[inline]
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 f.write_str(self.0)
274 }
275}
276
277#[cfg(test)]
278mod test {
279 use super::KrateName;
280 use crate::error::{Error, InvalidKrateName, ReservedNameKind};
281
282 #[test]
285 fn rejects_simple() {
286 assert!(matches!(
287 TryInto::<KrateName<'_>>::try_into("").unwrap_err(),
288 Error::InvalidKrateName(InvalidKrateName::InvalidLength(0))
289 ));
290 assert!(matches!(
291 KrateName::crates_io("").unwrap_err(),
292 Error::InvalidKrateName(InvalidKrateName::InvalidLength(0))
293 ));
294 assert!(matches!(
295 TryInto::<KrateName<'_>>::try_into("no.pe").unwrap_err(),
296 Error::InvalidKrateName(InvalidKrateName::InvalidCharacter {
297 index: 2,
298 invalid: '.',
299 })
300 ));
301 assert!(matches!(
302 KrateName::crates_io("no.pe").unwrap_err(),
303 Error::InvalidKrateName(InvalidKrateName::InvalidCharacter {
304 index: 2,
305 invalid: '.',
306 })
307 ));
308 }
309
310 #[test]
312 fn rejects_leading_digit() {
313 assert!(matches!(
314 KrateName::crates_io("3nop").unwrap_err(),
315 Error::InvalidKrateName(InvalidKrateName::InvalidCharacter {
316 index: 0,
317 invalid: '3',
318 })
319 ));
320 }
321
322 #[test]
324 fn rejects_too_long() {
325 assert!(matches!(
326 KrateName::crates_io(
327 "aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccddddddddxxxxxxx"
328 )
329 .unwrap_err(),
330 Error::InvalidKrateName(InvalidKrateName::InvalidLength(71))
331 ));
332
333 assert!(
334 KrateName::cargo(
335 "aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccddddddddxxxxxxx"
336 )
337 .is_ok()
338 );
339 }
340
341 #[test]
343 fn rejects_reserved() {
344 assert!(matches!(
345 KrateName::cargo("nul").unwrap_err(),
346 Error::InvalidKrateName(InvalidKrateName::ReservedName {
347 reserved: "nul",
348 kind: ReservedNameKind::Windows
349 })
350 ));
351 assert!(matches!(
352 KrateName::cargo("deps").unwrap_err(),
353 Error::InvalidKrateName(InvalidKrateName::ReservedName {
354 reserved: "deps",
355 kind: ReservedNameKind::Artifact
356 })
357 ));
358 assert!(matches!(
359 KrateName::cargo("Self").unwrap_err(),
360 Error::InvalidKrateName(InvalidKrateName::ReservedName {
361 reserved: "Self",
362 kind: ReservedNameKind::Keyword
363 })
364 ));
365 assert!(matches!(
366 KrateName::cargo("yield").unwrap_err(),
367 Error::InvalidKrateName(InvalidKrateName::ReservedName {
368 reserved: "yield",
369 kind: ReservedNameKind::Keyword
370 })
371 ));
372 assert!(matches!(
373 KrateName::cargo("proc-macro").unwrap_err(),
374 Error::InvalidKrateName(InvalidKrateName::ReservedName {
375 reserved: "proc-macro",
376 kind: ReservedNameKind::Standard
377 })
378 ));
379 assert!(matches!(
380 KrateName::cargo("proc_macro").unwrap_err(),
381 Error::InvalidKrateName(InvalidKrateName::ReservedName {
382 reserved: "proc_macro",
383 kind: ReservedNameKind::Standard
384 })
385 ));
386 }
387
388 #[inline]
389 fn rp(n: &str) -> String {
390 KrateName(n).relative_path(Some('/'))
391 }
392
393 #[test]
395 fn relative_path() {
396 assert_eq!(rp("a"), "1/a");
397 assert_eq!(rp("ab"), "2/ab");
398 assert_eq!(rp("abc"), "3/a/abc");
399 assert_eq!(rp("AbCd"), "ab/cd/abcd");
400 assert_eq!(rp("normal"), "no/rm/normal");
401 assert_eq!(rp("_boop-"), "_b/oo/_boop-");
402 assert_eq!(rp("Inflector"), "in/fl/inflector");
403 }
404}