1use itertools::Itertools;
2
3use miette::Diagnostic;
4use serde::{de, Deserialize, Deserializer, Serialize};
5use std::{cmp::Ordering, fmt::Display, str::FromStr};
6use thiserror::Error;
7mod outdated;
8pub mod version;
9
10pub use outdated::*;
11pub use version::{Version, VersionReq};
12
13use version::{HasModRev, ParseError, ReqError, ReqToVersionError};
14
15pub(crate) use version::SpecRevIterator;
16
17use crate::{
18 lockfile::RemotePackageSourceUrl,
19 lua_rockspec::{DisplayAsLuaKV, DisplayLuaKV, DisplayLuaValue},
20 remote_package_source::RemotePackageSource,
21 rockspec::lua_dependency::LuaDependencySpec,
22 variables::{GetVariableError, HasVariables},
23};
24#[derive(Clone, Debug)]
25pub(crate) struct RemotePackage {
26 pub spec: Spec,
27 pub source: RemotePackageSource,
28 pub source_url: Option<RemotePackageSourceUrl>,
30}
31
32#[derive(PartialEq, Eq, Hash, Clone, Debug)]
33pub(crate) enum RemotePackageType {
34 Rockspec,
35 Src,
36 Binary,
37}
38
39impl Ord for RemotePackageType {
40 fn cmp(&self, other: &Self) -> Ordering {
41 match (self, other) {
43 (Self::Binary, Self::Binary)
44 | (Self::Rockspec, Self::Rockspec)
45 | (Self::Src, Self::Src) => Ordering::Equal,
46
47 (Self::Binary, _) => Ordering::Greater,
48 (_, Self::Binary) => Ordering::Less,
49 (Self::Rockspec, Self::Src) => Ordering::Greater,
50 (Self::Src, Self::Rockspec) => Ordering::Less,
51 }
52 }
53}
54
55impl PartialOrd for RemotePackageType {
56 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
57 Some(self.cmp(other))
58 }
59}
60
61#[derive(Clone, Serialize, Deserialize, Debug)]
62#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
63pub struct RemotePackageTypeFilterSpec {
64 pub rockspec: bool,
66 pub src: bool,
68 pub binary: bool,
70}
71
72impl Default for RemotePackageTypeFilterSpec {
73 fn default() -> Self {
74 Self {
75 rockspec: true,
76 src: true,
77 binary: true,
78 }
79 }
80}
81
82#[derive(Error, Debug, Diagnostic)]
83pub enum ParseRemotePackageError {
84 #[error("unable to parse package {0}. expected format: `name@version`")]
85 InvalidInput(String),
86 #[error("unable to parse package {package_str}:\n{error}")]
87 InvalidPackageVersion {
88 #[source]
89 error: ParseError,
90 package_str: String,
91 },
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Hash)]
96#[cfg_attr(feature = "clap", derive(clap::Args))]
97pub struct Req {
98 pub(crate) name: Name,
100 pub(crate) version_req: VersionReq,
102}
103
104impl Display for Req {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 if self.version_req.is_any() {
107 self.name.fmt(f)
108 } else {
109 f.write_str(format!("{}{}", self.name, self.version_req).as_str())
110 }
111 }
112}
113
114impl From<Spec> for Req {
115 fn from(value: Spec) -> Self {
116 value.into_package_req()
117 }
118}
119
120impl From<Name> for Req {
121 fn from(name: Name) -> Self {
122 Self {
123 name,
124 version_req: VersionReq::any(),
125 }
126 }
127}
128
129pub(crate) struct Dependencies<'a>(pub(crate) &'a Vec<LuaDependencySpec>);
131pub(crate) struct BuildDependencies<'a>(pub(crate) &'a Vec<LuaDependencySpec>);
132
133impl DisplayAsLuaKV for Dependencies<'_> {
134 fn display_lua(&self) -> DisplayLuaKV {
135 DisplayLuaKV {
136 key: "dependencies".to_string(),
137 value: DisplayLuaValue::List(
138 self.0
139 .iter()
140 .map(|req| DisplayLuaValue::String(req.to_string()))
141 .collect(),
142 ),
143 }
144 }
145}
146
147impl DisplayAsLuaKV for BuildDependencies<'_> {
148 fn display_lua(&self) -> DisplayLuaKV {
149 DisplayLuaKV {
150 key: "build_dependencies".to_string(),
151 value: DisplayLuaValue::List(
152 self.0
153 .iter()
154 .map(|req| DisplayLuaValue::String(req.to_string()))
155 .collect(),
156 ),
157 }
158 }
159}
160
161#[derive(Error, Debug, Diagnostic)]
162pub enum ReqParseError {
163 #[error("could not parse dependency name from '{0}'")]
164 InvalidDependencyName(String),
165 #[error("could not parse version requirement in '{str}': {error}")]
166 InvalidPackageVersionReq {
167 #[source]
168 error: ReqError,
169 str: String,
170 },
171}
172
173impl FromStr for Req {
174 type Err = ReqParseError;
175
176 fn from_str(str: &str) -> Result<Self, ReqParseError> {
177 let rock_name_str = str
178 .chars()
179 .peeking_take_while(|t| t.is_alphanumeric() || matches!(t, '-' | '_' | '.'))
180 .collect::<String>();
181
182 if rock_name_str.is_empty() {
183 return Err(ReqParseError::InvalidDependencyName(str.to_string()));
184 }
185
186 let constraints = str.trim_start_matches(&rock_name_str).trim();
187 let version_req = match constraints {
188 "" => VersionReq::any(),
189 constraints => VersionReq::parse(constraints.trim_start()).map_err(|error| {
190 ReqParseError::InvalidPackageVersionReq {
191 error,
192 str: str.to_string(),
193 }
194 })?,
195 };
196 Ok(Self {
197 name: Name::new(rock_name_str),
198 version_req,
199 })
200 }
201}
202
203impl<'de> Deserialize<'de> for Req {
204 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
205 where
206 D: Deserializer<'de>,
207 {
208 let s = String::deserialize(deserializer)?;
209 Self::from_str(&s).map_err(de::Error::custom)
210 }
211}
212
213impl RemotePackage {
214 pub fn new(
215 spec: Spec,
216 source: RemotePackageSource,
217 source_url: Option<RemotePackageSourceUrl>,
218 ) -> Self {
219 Self {
220 spec,
221 source,
222 source_url,
223 }
224 }
225}
226
227impl Req {
228 pub fn new(name: String, version: Option<String>) -> Result<Self, ReqError> {
229 let version_req = match version {
230 Some(version_req_str) => VersionReq::parse(version_req_str.as_str())?,
231 None => VersionReq::any(),
232 };
233 Ok(Self {
234 name: Name::new(name),
235 version_req,
236 })
237 }
238
239 pub unsafe fn new_unchecked(name: String, version: Option<String>) -> Self {
244 Self::new(name, version).unwrap_unchecked()
245 }
246
247 pub fn parse(pkg_constraints: &str) -> Result<Self, ReqParseError> {
248 Self::from_str(pkg_constraints)
249 }
250 pub fn name(&self) -> &Name {
251 &self.name
252 }
253 pub fn version_req(&self) -> &VersionReq {
254 &self.version_req
255 }
256 pub fn matches(&self, package: &Spec) -> bool {
258 self.name() == package.name() && self.version_req.matches(package.version())
259 }
260}
261
262#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
264#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
265#[cfg_attr(feature = "schema", serde(transparent))]
266pub struct Name(String);
267
268impl Name {
269 pub fn new(name: String) -> Self {
270 Self(name.to_lowercase())
271 }
272}
273
274impl<'de> Deserialize<'de> for Name {
275 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
276 where
277 D: serde::Deserializer<'de>,
278 {
279 Ok(Name::new(String::deserialize(deserializer)?))
280 }
281}
282
283impl Serialize for Name {
284 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
285 where
286 S: serde::Serializer,
287 {
288 self.0.serialize(serializer)
289 }
290}
291
292impl From<&str> for Name {
293 fn from(value: &str) -> Self {
294 Self::new(value.into())
295 }
296}
297
298impl Display for Name {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 f.write_str(self.0.as_str())
301 }
302}
303
304#[derive(Debug)]
305pub struct List(Vec<Name>);
306
307impl List {
308 pub(crate) fn new(package_names: Vec<Name>) -> Self {
309 Self(package_names)
310 }
311}
312
313impl Display for List {
314 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315 f.write_str(self.0.iter().map(|pkg| pkg.to_string()).join(", ").as_str())
316 }
317}
318
319#[derive(Debug, Error, Diagnostic)]
320pub enum SpecFromReqError {
321 #[error("invalid version for rock {rock}: {err}")]
322 Version { rock: Name, err: ReqToVersionError },
323}
324
325#[derive(Clone, Debug, PartialEq, Eq)]
327#[cfg_attr(feature = "clap", derive(clap::Args))]
328pub struct Spec {
329 name: Name,
330 version: Version,
331}
332
333impl Spec {
334 pub fn new(name: Name, version: Version) -> Self {
335 Self { name, version }
336 }
337 pub fn parse(name: String, version: String) -> Result<Self, ParseError> {
338 Ok(Self::new(Name::new(name), Version::parse(&version)?))
339 }
340 pub fn name(&self) -> &Name {
341 &self.name
342 }
343 pub fn version(&self) -> &Version {
344 &self.version
345 }
346 pub fn into_package_req(self) -> Req {
348 Req {
349 name: self.name,
350 version_req: self.version.into_version_req(),
351 }
352 }
353}
354
355impl TryFrom<Req> for Spec {
356 type Error = SpecFromReqError;
357
358 fn try_from(value: Req) -> Result<Self, Self::Error> {
359 let name = value.name;
360 let version = value
361 .version_req
362 .try_into()
363 .map_err(|err| SpecFromReqError::Version {
364 rock: name.clone(),
365 err,
366 })?;
367 Ok(Self { name, version })
368 }
369}
370
371impl<'de> Deserialize<'de> for Spec {
372 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
373 where
374 D: Deserializer<'de>,
375 {
376 let (name, version) = <_>::deserialize(deserializer)?;
377 Self::parse(name, version).map_err(serde::de::Error::custom)
378 }
379}
380
381impl HasVariables for Spec {
382 fn get_variable(&self, input: &str) -> Result<Option<String>, GetVariableError> {
383 Ok(match input {
384 "PACKAGE" => Some(self.name.to_string()),
385 "VERSION" => Some(self.version.to_modrev_string()),
386 _ => None,
387 })
388 }
389}
390
391impl FromStr for Spec {
392 type Err = ParseRemotePackageError;
393
394 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
395 let (name, version) = s
396 .split_once('@')
397 .ok_or_else(|| ParseRemotePackageError::InvalidInput(s.to_string()))?;
398
399 Self::parse(name.to_string(), version.to_string()).map_err(|error| {
400 ParseRemotePackageError::InvalidPackageVersion {
401 error,
402 package_str: s.to_string(),
403 }
404 })
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 use crate::package::version::Version;
413
414 #[test]
415 fn parse_name() {
416 let mut package_name: Name = "neorg".into();
417 assert_eq!(package_name.to_string(), "neorg");
418 package_name = "LuaFileSystem".into();
419 assert_eq!(package_name.to_string(), "luafilesystem");
420 }
421
422 #[test]
423 fn parse_lua_package() {
424 let neorg = Spec::parse("neorg".into(), "1.0.0".into()).unwrap();
425 let expected_version = Version::parse("1.0.0").unwrap();
426 assert_eq!(neorg.name().to_string(), "neorg");
427 assert!(matches!(
428 neorg.version().cmp(&expected_version),
429 std::cmp::Ordering::Equal
430 ));
431 let neorg = Spec::parse("neorg".into(), "1.0".into()).unwrap();
432 assert!(matches!(
433 neorg.version().cmp(&expected_version),
434 std::cmp::Ordering::Equal
435 ));
436 let neorg = Spec::parse("neorg".into(), "1".into()).unwrap();
437 assert!(matches!(
438 neorg.version().cmp(&expected_version),
439 std::cmp::Ordering::Equal
440 ));
441 }
442
443 #[test]
444 fn parse_lua_package_req() {
445 let mut package_req = Req::new("foo".into(), Some("1.0.0".into())).unwrap();
446 assert!(package_req.matches(&Spec::parse("foo".into(), "1.0.0".into()).unwrap()));
447 assert!(!package_req.matches(&Spec::parse("bar".into(), "1.0.0".into()).unwrap()));
448 assert!(!package_req.matches(&Spec::parse("foo".into(), "2.0.0".into()).unwrap()));
449 package_req = Req::new("foo".into(), Some(">= 1.0.0".into())).unwrap();
450 assert!(package_req.matches(&Spec::parse("foo".into(), "2.0.0".into()).unwrap()));
451 let package_req: Req = "lua >= 5.1".parse().unwrap();
452 assert_eq!(package_req.name.to_string(), "lua");
453 let package_req: Req = "lua>=5.1".parse().unwrap();
454 assert_eq!(package_req.name.to_string(), "lua");
455 let package_req: Req = "toml-edit >= 0.1.0".parse().unwrap();
456 assert_eq!(package_req.name.to_string(), "toml-edit");
457 let package_req: Req = "plugin.nvim >= 0.1.0".parse().unwrap();
458 assert_eq!(package_req.name.to_string(), "plugin.nvim");
459 let package_req: Req = "lfs".parse().unwrap();
460 assert_eq!(package_req.name.to_string(), "lfs");
461 let package_req: Req = "neorg 1.0.0".parse().unwrap();
462 assert_eq!(package_req.name.to_string(), "neorg");
463 let neorg = Spec::parse("neorg".into(), "1.0.0".into()).unwrap();
464 assert!(package_req.matches(&neorg));
465 let neorg = Spec::parse("neorg".into(), "2.0.0".into()).unwrap();
466 assert!(!package_req.matches(&neorg));
467 let package_req: Req = "neorg 2.0.0".parse().unwrap();
468 assert!(package_req.matches(&neorg));
469 let package_req: Req = "neorg = 2.0.0".parse().unwrap();
470 assert!(package_req.matches(&neorg));
471 let package_req: Req = "neorg == 2.0.0".parse().unwrap();
472 assert!(package_req.matches(&neorg));
473 let package_req: Req = "neorg = 2.0.0".parse().unwrap();
474 assert!(package_req.matches(&neorg));
475 let package_req: Req = "neorg >= 1.0, < 2.0".parse().unwrap();
476 let neorg = Spec::parse("neorg".into(), "1.5".into()).unwrap();
477 assert!(package_req.matches(&neorg));
478 let package_req: Req = "neorg > 1.0, < 2.0".parse().unwrap();
479 let neorg = Spec::parse("neorg".into(), "1.11.0".into()).unwrap();
480 assert!(package_req.matches(&neorg));
481 let neorg = Spec::parse("neorg".into(), "3.0.0".into()).unwrap();
482 assert!(!package_req.matches(&neorg));
483 let neorg = Spec::parse("neorg".into(), "0.5".into()).unwrap();
484 assert!(!package_req.matches(&neorg));
485 let package_req: Req = "neorg ~> 1".parse().unwrap();
486 assert!(!package_req.matches(&neorg));
487 let neorg = Spec::parse("neorg".into(), "3".into()).unwrap();
488 assert!(!package_req.matches(&neorg));
489 let neorg = Spec::parse("neorg".into(), "1.5".into()).unwrap();
490 assert!(package_req.matches(&neorg));
491 let package_req: Req = "neorg ~> 1.4".parse().unwrap();
492 let neorg = Spec::parse("neorg".into(), "1.3".into()).unwrap();
493 assert!(!package_req.matches(&neorg));
494 let neorg = Spec::parse("neorg".into(), "1.5".into()).unwrap();
495 assert!(!package_req.matches(&neorg));
496 let neorg = Spec::parse("neorg".into(), "1.4.10".into()).unwrap();
497 assert!(package_req.matches(&neorg));
498 let neorg = Spec::parse("neorg".into(), "1.4".into()).unwrap();
499 assert!(package_req.matches(&neorg));
500 let package_req: Req = "neorg ~> 1.0.5".parse().unwrap();
501 let neorg = Spec::parse("neorg".into(), "1.0.4".into()).unwrap();
502 assert!(!package_req.matches(&neorg));
503 let neorg = Spec::parse("neorg".into(), "1.0.5".into()).unwrap();
504 assert!(package_req.matches(&neorg));
505 let neorg = Spec::parse("neorg".into(), "1.0.6".into()).unwrap();
506 assert!(!package_req.matches(&neorg));
507 let package_req: Req = "lua-utils.nvim ~> 1.1-1".parse().unwrap();
509 let lua_utils = Spec::parse("lua-utils.nvim".into(), "1.1.4".into()).unwrap();
510 assert!(package_req.matches(&lua_utils));
511 let lua_utils = Spec::parse("lua-utils.nvim".into(), "1.1.5".into()).unwrap();
512 assert!(package_req.matches(&lua_utils));
513 let lua_utils = Spec::parse("lua-utils.nvim".into(), "1.2-1".into()).unwrap();
514 assert!(!package_req.matches(&lua_utils));
515 }
516
517 #[test]
518 pub fn display_package_req() {
519 let package_req = Req::new("foo".into(), Some("1.0.0".into())).unwrap();
520 assert_eq!(package_req.to_string(), "foo==1.0.0");
521 let package_req = Req::new("foo".into(), Some(">=1.0.0".into())).unwrap();
522 assert_eq!(package_req.to_string(), "foo>=1.0.0");
523 let package_req = Req::new("foo".into(), Some(">1.0.0".into())).unwrap();
524 assert_eq!(package_req.to_string(), "foo>1.0.0");
525 let package_req = Req::new("foo".into(), Some("<=1.0.0".into())).unwrap();
526 assert_eq!(package_req.to_string(), "foo<=1.0.0");
527 let package_req = Req::new("foo".into(), Some("<1.0.0".into())).unwrap();
528 assert_eq!(package_req.to_string(), "foo<1.0.0");
529 let package_req = Req::new("foo".into(), Some("~>1.0".into())).unwrap();
530 assert_eq!(package_req.to_string(), "foo>=1.0.0, <1.1.0");
531 let package_req = Req::new("foo".into(), Some(">=1.0.0, <1.1.0".into())).unwrap();
532 assert_eq!(package_req.to_string(), "foo>=1.0.0, <1.1.0");
533 let package_req = Req::new("foo".into(), None).unwrap();
534 assert_eq!(package_req.to_string(), "foo");
535 let package_req = Req::new("foo".into(), Some("*".into())).unwrap();
536 assert_eq!(package_req.to_string(), "foo");
537 }
538
539 #[test]
540 pub fn remote_package_type_priorities() {
541 let rock_types = vec![
542 RemotePackageType::Binary,
543 RemotePackageType::Src,
544 RemotePackageType::Rockspec,
545 ];
546 assert_eq!(
547 rock_types.into_iter().sorted().collect_vec(),
548 vec![
549 RemotePackageType::Src,
550 RemotePackageType::Rockspec,
551 RemotePackageType::Binary,
552 ]
553 );
554 }
555
556 #[test]
557 fn package_spec_req_roundtrips() {
558 let spec = Spec::new("foo".into(), "1.0.0-1".parse().unwrap());
559 let req: Req = spec.clone().into();
560 assert_eq!(spec, req.try_into().unwrap());
561
562 let spec = Spec::new("foo".into(), "1.0.0".parse().unwrap());
563 let req: Req = spec.clone().into();
564 assert_eq!(spec, req.try_into().unwrap());
565 }
566
567 #[test]
568 fn package_req_spec_roundtrips() {
569 let req: Req = "foo@1.0.0".parse().unwrap();
570 let spec: Spec = req.clone().try_into().unwrap();
571 assert_eq!(req, spec.into());
572
573 let req: Req = "foo@1.0.0-1".parse().unwrap();
574 let spec: Spec = req.clone().try_into().unwrap();
575 assert_eq!(req, spec.into());
576 }
577
578 #[test]
579 fn package_spec_parses_same_as_name_version() {
580 let req: Req = "foo@1.0.0".parse().unwrap();
581 let spec: Spec = req.clone().try_into().unwrap();
582 let name: Name = "foo".into();
583 let version: Version = "1.0.0".parse().unwrap();
584 assert_eq!(spec, Spec::new(name, version));
585
586 let req: Req = "foo@1.0.0-1".parse().unwrap();
587 let spec: Spec = req.clone().try_into().unwrap();
588 let name: Name = "foo".into();
589 let version: Version = "1.0.0-1".parse().unwrap();
590 assert_eq!(spec, Spec::new(name, version));
591 }
592}