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!(KrateName::cargo(
334 "aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccddddddddxxxxxxx"
335 )
336 .is_ok());
337 }
338
339 #[test]
341 fn rejects_reserved() {
342 assert!(matches!(
343 KrateName::cargo("nul").unwrap_err(),
344 Error::InvalidKrateName(InvalidKrateName::ReservedName {
345 reserved: "nul",
346 kind: ReservedNameKind::Windows
347 })
348 ));
349 assert!(matches!(
350 KrateName::cargo("deps").unwrap_err(),
351 Error::InvalidKrateName(InvalidKrateName::ReservedName {
352 reserved: "deps",
353 kind: ReservedNameKind::Artifact
354 })
355 ));
356 assert!(matches!(
357 KrateName::cargo("Self").unwrap_err(),
358 Error::InvalidKrateName(InvalidKrateName::ReservedName {
359 reserved: "Self",
360 kind: ReservedNameKind::Keyword
361 })
362 ));
363 assert!(matches!(
364 KrateName::cargo("yield").unwrap_err(),
365 Error::InvalidKrateName(InvalidKrateName::ReservedName {
366 reserved: "yield",
367 kind: ReservedNameKind::Keyword
368 })
369 ));
370 assert!(matches!(
371 KrateName::cargo("proc-macro").unwrap_err(),
372 Error::InvalidKrateName(InvalidKrateName::ReservedName {
373 reserved: "proc-macro",
374 kind: ReservedNameKind::Standard
375 })
376 ));
377 assert!(matches!(
378 KrateName::cargo("proc_macro").unwrap_err(),
379 Error::InvalidKrateName(InvalidKrateName::ReservedName {
380 reserved: "proc_macro",
381 kind: ReservedNameKind::Standard
382 })
383 ));
384 }
385
386 #[inline]
387 fn rp(n: &str) -> String {
388 KrateName(n).relative_path(Some('/'))
389 }
390
391 #[test]
393 fn relative_path() {
394 assert_eq!(rp("a"), "1/a");
395 assert_eq!(rp("ab"), "2/ab");
396 assert_eq!(rp("abc"), "3/a/abc");
397 assert_eq!(rp("AbCd"), "ab/cd/abcd");
398 assert_eq!(rp("normal"), "no/rm/normal");
399 assert_eq!(rp("_boop-"), "_b/oo/_boop-");
400 assert_eq!(rp("Inflector"), "in/fl/inflector");
401 }
402}