1use std::{cmp, fmt};
5
6use nom::bytes::complete::tag;
7use nom::combinator::{eof, opt, recognize, verify};
8use nom::sequence::{preceded, terminated};
9use nom::{IResult, Parser};
10
11use crate::error::{Error, Result};
12use crate::misc::partial_eq_field;
13
14#[derive(Debug, PartialEq, Eq, Clone)]
15pub(crate) struct PackageNamePositions {
16 scope_end: Option<usize>,
17 pub(crate) total_length: usize,
18}
19
20pub fn package_name_part(input: &str) -> IResult<&str, &str> {
22 let input_bytes = input.as_bytes();
23 let mut ind = 0usize;
24 for byte in input_bytes {
25 if !matches!(byte, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'.' | b'-' | b'_' | b'(' | b')' | b'~' | b'\'' | b'!' | b'*')
28 {
29 break;
30 }
31 ind += 1;
32 }
33 match ind {
34 0 => Err(nom::Err::Error(nom::error::Error::new(
35 input,
36 nom::error::ErrorKind::Many1,
37 ))),
38 i => {
39 let output = &input[..i];
40 if output.starts_with("_") || output.starts_with(".") {
41 Err(nom::Err::Error(nom::error::Error::new(
42 input,
43 nom::error::ErrorKind::Verify,
44 )))
45 } else {
46 Ok((&input[i..], output))
47 }
48 }
49 }
50}
51
52fn package_name_str_internal(
53 input: &str,
54) -> IResult<&str, (Option<&str>, &str), nom::error::Error<&str>> {
55 (
56 opt(preceded(tag("@"), terminated(package_name_part, tag("/")))),
57 verify(package_name_part, |part: &str| {
58 part != "node_modules" && part != "favicon.ico"
59 }),
60 )
61 .parse(input)
62}
63
64pub(crate) fn package_name(
65 input: &str,
66) -> IResult<&str, PackageNamePositions, nom::error::Error<&str>> {
67 package_name_str_internal
68 .parse(input)
69 .map(|(inp, (scope, rest))| {
70 let scope_end = scope.map(|s| s.len() + 1);
71 (
72 inp,
73 PackageNamePositions {
74 scope_end,
75 total_length: scope_end.map(|e| e + 1).unwrap_or(0) + rest.len(),
76 },
77 )
78 })
79}
80
81pub fn package_name_str(input: &str) -> IResult<&str, &str, nom::error::Error<&str>> {
83 recognize(package_name_str_internal).parse(input)
84}
85
86impl PackageNamePositions {
87 fn parse(input: &str) -> Result<Self> {
88 terminated(package_name, eof)
89 .parse(input)
90 .map(|(_, pos)| pos)
91 .map_err(|_| crate::Error::InvalidPackageName(input.to_string()))
92 }
93
94 fn scope(&self) -> Option<(usize, usize)> {
96 self.scope_end.map(|end| (0, end))
97 }
98 fn scope_prefix(&self) -> Option<(usize, usize)> {
100 self.scope_end.map(|end| (0, end + 1))
101 }
102 fn scope_name(&self) -> Option<(usize, usize)> {
104 self.scope_end.map(|end| (1, end))
105 }
106 fn name_rest(&self) -> (usize, usize) {
108 match self.scope_end {
109 Some(scope_end) => (scope_end + 1, self.total_length),
110 None => (0, self.total_length),
111 }
112 }
113}
114
115#[derive(Debug, PartialEq, Eq, Clone)]
116pub struct PackageName {
117 inner: String,
118 positions: PackageNamePositions,
119}
120
121#[derive(Debug, PartialEq, Eq, Clone)]
122pub struct PackageNameBorrowed<'a> {
123 pub(crate) inner: &'a str,
124 pub(crate) positions: &'a PackageNamePositions,
125}
126
127partial_eq_field!(PackageName, inner, String);
128partial_eq_field!(PackageName, inner, &str);
129partial_eq_field!(PackageNameBorrowed<'_>, inner, String);
130
131impl PartialEq<&str> for PackageNameBorrowed<'_> {
132 fn eq(&self, other: &&str) -> bool {
133 self.inner.eq(*other)
134 }
135}
136
137impl PackageNameBorrowed<'_> {
138 pub fn to_owned(&self) -> PackageName {
139 PackageName {
140 inner: self.inner.to_string(),
141 positions: self.positions.clone(),
142 }
143 }
144}
145
146macro_rules! option_segment {
147 ($name:ident) => {
148 pub fn $name(&self) -> Option<&str> {
149 self.positions
150 .$name()
151 .map(|(start, end)| &self.inner[start..end])
152 }
153 };
154}
155
156macro_rules! required_segment {
157 ($name:ident) => {
158 pub fn $name(&self) -> &str {
159 let (start, end) = self.positions.$name();
160 &self.inner[start..end]
161 }
162 };
163}
164
165impl PackageName {
166 pub fn new(name: String) -> Result<Self> {
167 Ok(Self {
168 positions: PackageNamePositions::parse(&name)?,
169 inner: name,
170 })
171 }
172
173 pub fn as_borrowed(&self) -> PackageNameBorrowed<'_> {
174 PackageNameBorrowed {
175 inner: &self.inner,
176 positions: &self.positions,
177 }
178 }
179
180 option_segment!(scope);
181 option_segment!(scope_prefix);
182 option_segment!(scope_name);
183 required_segment!(name_rest);
184}
185
186impl PackageNameBorrowed<'_> {
187 option_segment!(scope);
188 option_segment!(scope_prefix);
189 option_segment!(scope_name);
190 required_segment!(name_rest);
191}
192
193impl TryFrom<String> for PackageName {
194 type Error = Error;
195
196 fn try_from(value: String) -> std::result::Result<Self, Self::Error> {
197 PackageName::new(value)
198 }
199}
200
201impl From<PackageName> for String {
202 fn from(value: PackageName) -> Self {
203 value.inner
204 }
205}
206impl fmt::Display for PackageName {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 self.inner.fmt(f)
209 }
210}
211impl fmt::Display for PackageNameBorrowed<'_> {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 self.inner.fmt(f)
214 }
215}
216impl AsRef<str> for PackageName {
217 fn as_ref(&self) -> &str {
218 &self.inner
219 }
220}
221impl AsRef<str> for PackageNameBorrowed<'_> {
222 fn as_ref(&self) -> &str {
223 self.inner
224 }
225}
226impl PartialOrd for PackageName {
227 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
228 Some(self.inner.cmp(&other.inner))
229 }
230}
231impl PartialOrd for PackageNameBorrowed<'_> {
232 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
233 Some(self.inner.cmp(other.inner))
234 }
235}
236impl Ord for PackageName {
237 fn cmp(&self, other: &Self) -> cmp::Ordering {
238 self.inner.cmp(&other.inner)
239 }
240}
241impl Ord for PackageNameBorrowed<'_> {
242 fn cmp(&self, other: &Self) -> cmp::Ordering {
243 self.inner.cmp(other.inner)
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use crate::error::{Error, Result};
250
251 use super::PackageName;
252
253 #[test]
254 fn test_positions_scoped() -> Result<()> {
255 let name = PackageName::new("@scope/name".to_string())?;
256 assert_eq!(name.scope(), Some("@scope"));
257 assert_eq!(name.scope_name(), Some("scope"));
258 assert_eq!(name.scope_prefix(), Some("@scope/"));
259 assert_eq!(name.name_rest(), "name");
260 Ok(())
261 }
262
263 #[test]
264 fn test_positions_unscoped() -> Result<()> {
265 let name = PackageName::new("name__1".to_string())?;
266 assert_eq!(name.scope(), None);
267 assert_eq!(name.scope_name(), None);
268 assert_eq!(name.scope_prefix(), None);
269 assert_eq!(name.name_rest(), "name__1");
270 Ok(())
271 }
272
273 #[test]
274 fn test_cursed_chars() -> Result<()> {
275 let name = PackageName::new("@a/verboden(name~'!*)".to_string())?;
276 assert_eq!(name.scope(), Some("@a"));
277 assert_eq!(name.scope_name(), Some("a"));
278 assert_eq!(name.scope_prefix(), Some("@a/"));
279 assert_eq!(name.name_rest(), "verboden(name~'!*)");
280 Ok(())
281 }
282
283 #[test]
284 fn test_invalid_names() {
285 macro_rules! assert_name_error {
286 ($name:expr) => {
287 assert_eq!(
288 PackageName::new($name.to_string()),
289 Err(Error::InvalidPackageName($name.to_string()))
290 );
291 };
292 }
293 assert_name_error!("");
294 assert_name_error!("ą");
295 assert_name_error!(".bin");
296 assert_name_error!("a/");
297 assert_name_error!("a@a/a");
298 assert_name_error!("@");
299 assert_name_error!("@a");
300 assert_name_error!("@a/");
301 assert_name_error!("/");
302 assert_name_error!("@/a");
303 assert_name_error!("@/");
304 assert_name_error!("@chastelock/node_modules");
305 assert_name_error!("node_modules");
306 }
307}