1use std::borrow::Cow;
36use std::fmt::Display;
37use std::path;
38use std::path::Path;
39use std::str::FromStr;
40
41use url::Url;
42
43use uv_distribution_filename::{
44 DistExtension, SourceDistExtension, SourceDistFilename, WheelFilename,
45};
46use uv_fs::normalize_absolute_path;
47use uv_git_types::GitUrl;
48use uv_normalize::PackageName;
49use uv_pep440::Version;
50use uv_pep508::{Pep508Url, VerbatimUrl};
51use uv_pypi_types::{
52 ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, VerbatimParsedUrl,
53};
54use uv_redacted::DisplaySafeUrl;
55
56pub use crate::annotation::*;
57pub use crate::any::*;
58pub use crate::build_info::*;
59pub use crate::build_requires::*;
60pub use crate::buildable::*;
61pub use crate::cached::*;
62pub use crate::config_settings::*;
63pub use crate::dependency_metadata::*;
64pub use crate::diagnostic::*;
65pub use crate::dist_error::*;
66pub use crate::error::*;
67pub use crate::exclude_newer::*;
68pub use crate::file::*;
69pub use crate::hash::*;
70pub use crate::id::*;
71pub use crate::index::*;
72pub use crate::index_name::*;
73pub use crate::index_url::*;
74pub use crate::installed::*;
75pub use crate::known_platform::*;
76pub use crate::origin::*;
77pub use crate::pip_index::*;
78pub use crate::prioritized_distribution::*;
79pub use crate::requested::*;
80pub use crate::requirement::*;
81pub use crate::requires_python::*;
82pub use crate::resolution::*;
83pub use crate::resolved::*;
84pub use crate::specified_requirement::*;
85pub use crate::status_code_strategy::*;
86pub use crate::traits::*;
87
88mod annotation;
89mod any;
90mod build_info;
91mod build_requires;
92mod buildable;
93mod cached;
94mod config_settings;
95mod dependency_metadata;
96mod diagnostic;
97mod dist_error;
98mod error;
99mod exclude_newer;
100mod file;
101mod hash;
102mod id;
103mod index;
104mod index_name;
105mod index_url;
106mod installed;
107mod known_platform;
108mod origin;
109mod pip_index;
110mod prioritized_distribution;
111mod requested;
112mod requirement;
113mod requires_python;
114mod resolution;
115mod resolved;
116mod specified_requirement;
117mod status_code_strategy;
118mod traits;
119
120#[derive(Debug, Clone)]
121pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> {
122 Version(&'a Version),
124 Url(&'a T),
126}
127
128impl<'a, T: Pep508Url> VersionOrUrlRef<'a, T> {
129 pub fn url(&self) -> Option<&'a T> {
131 match self {
132 Self::Version(_) => None,
133 Self::Url(url) => Some(url),
134 }
135 }
136}
137
138impl Verbatim for VersionOrUrlRef<'_> {
139 fn verbatim(&self) -> Cow<'_, str> {
140 match self {
141 Self::Version(version) => Cow::Owned(format!("=={version}")),
142 Self::Url(url) => Cow::Owned(format!(" @ {}", url.verbatim())),
143 }
144 }
145}
146
147impl std::fmt::Display for VersionOrUrlRef<'_> {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 match self {
150 Self::Version(version) => write!(f, "=={version}"),
151 Self::Url(url) => write!(f, " @ {url}"),
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
157pub enum InstalledVersion<'a> {
158 Version(&'a Version),
160 Url(&'a DisplaySafeUrl, &'a Version),
163}
164
165impl<'a> InstalledVersion<'a> {
166 pub fn url(&self) -> Option<&'a DisplaySafeUrl> {
168 match self {
169 Self::Version(_) => None,
170 Self::Url(url, _) => Some(url),
171 }
172 }
173
174 pub fn version(&self) -> &'a Version {
176 match self {
177 Self::Version(version) => version,
178 Self::Url(_, version) => version,
179 }
180 }
181}
182
183impl std::fmt::Display for InstalledVersion<'_> {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 match self {
186 Self::Version(version) => write!(f, "=={version}"),
187 Self::Url(url, version) => write!(f, "=={version} (from {url})"),
188 }
189 }
190}
191
192#[derive(Debug, Clone, Hash, PartialEq, Eq)]
196pub enum Dist {
197 Built(BuiltDist),
198 Source(SourceDist),
199}
200
201#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
203pub enum DistRef<'a> {
204 Built(&'a BuiltDist),
205 Source(&'a SourceDist),
206}
207
208impl Display for DistRef<'_> {
209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210 match self {
211 Self::Built(built_dist) => Display::fmt(&built_dist, f),
212 Self::Source(source_dist) => Display::fmt(&source_dist, f),
213 }
214 }
215}
216
217#[derive(Debug, Clone, Hash, PartialEq, Eq)]
219pub enum BuiltDist {
220 Registry(RegistryBuiltDist),
221 DirectUrl(DirectUrlBuiltDist),
222 Path(PathBuiltDist),
223}
224
225#[derive(Debug, Clone, Hash, PartialEq, Eq)]
227pub enum SourceDist {
228 Registry(RegistrySourceDist),
229 DirectUrl(DirectUrlSourceDist),
230 Git(GitSourceDist),
231 Path(PathSourceDist),
232 Directory(DirectorySourceDist),
233}
234
235#[derive(Debug, Clone, Hash, PartialEq, Eq)]
237pub struct RegistryBuiltWheel {
238 pub filename: WheelFilename,
239 pub file: Box<File>,
240 pub index: IndexUrl,
241}
242
243#[derive(Debug, Clone, Hash, PartialEq, Eq)]
245pub struct RegistryBuiltDist {
246 pub wheels: Vec<RegistryBuiltWheel>,
249 pub best_wheel_index: usize,
254 pub sdist: Option<RegistrySourceDist>,
262 }
272
273#[derive(Debug, Clone, Hash, PartialEq, Eq)]
275pub struct DirectUrlBuiltDist {
276 pub filename: WheelFilename,
279 pub location: Box<DisplaySafeUrl>,
281 pub url: VerbatimUrl,
283}
284
285#[derive(Debug, Clone, Hash, PartialEq, Eq)]
287pub struct PathBuiltDist {
288 pub filename: WheelFilename,
289 pub install_path: Box<Path>,
291 pub url: VerbatimUrl,
293}
294
295#[derive(Debug, Clone, Hash, PartialEq, Eq)]
297pub struct RegistrySourceDist {
298 pub name: PackageName,
299 pub version: Version,
300 pub file: Box<File>,
301 pub ext: SourceDistExtension,
303 pub index: IndexUrl,
304 pub wheels: Vec<RegistryBuiltWheel>,
312}
313
314#[derive(Debug, Clone, Hash, PartialEq, Eq)]
316pub struct DirectUrlSourceDist {
317 pub name: PackageName,
320 pub location: Box<DisplaySafeUrl>,
322 pub subdirectory: Option<Box<Path>>,
324 pub ext: SourceDistExtension,
326 pub url: VerbatimUrl,
328}
329
330#[derive(Debug, Clone, Hash, PartialEq, Eq)]
332pub struct GitSourceDist {
333 pub name: PackageName,
334 pub git: Box<GitUrl>,
336 pub subdirectory: Option<Box<Path>>,
338 pub url: VerbatimUrl,
340}
341
342#[derive(Debug, Clone, Hash, PartialEq, Eq)]
344pub struct PathSourceDist {
345 pub name: PackageName,
346 pub version: Option<Version>,
347 pub install_path: Box<Path>,
349 pub ext: SourceDistExtension,
351 pub url: VerbatimUrl,
353}
354
355#[derive(Debug, Clone, Hash, PartialEq, Eq)]
357pub struct DirectorySourceDist {
358 pub name: PackageName,
359 pub install_path: Box<Path>,
361 pub editable: Option<bool>,
363 pub r#virtual: Option<bool>,
365 pub url: VerbatimUrl,
367}
368
369impl Dist {
370 pub fn from_http_url(
373 name: PackageName,
374 url: VerbatimUrl,
375 location: DisplaySafeUrl,
376 subdirectory: Option<Box<Path>>,
377 ext: DistExtension,
378 ) -> Result<Self, Error> {
379 match ext {
380 DistExtension::Wheel => {
381 let filename = WheelFilename::from_str(&url.filename()?)?;
383 if filename.name != name {
384 return Err(Error::PackageNameMismatch(
385 name,
386 filename.name,
387 url.verbatim().to_string(),
388 ));
389 }
390
391 Ok(Self::Built(BuiltDist::DirectUrl(DirectUrlBuiltDist {
392 filename,
393 location: Box::new(location),
394 url,
395 })))
396 }
397 DistExtension::Source(ext) => {
398 Ok(Self::Source(SourceDist::DirectUrl(DirectUrlSourceDist {
399 name,
400 location: Box::new(location),
401 subdirectory,
402 ext,
403 url,
404 })))
405 }
406 }
407 }
408
409 pub fn from_file_url(
411 name: PackageName,
412 url: VerbatimUrl,
413 install_path: &Path,
414 ext: DistExtension,
415 ) -> Result<Self, Error> {
416 let install_path = path::absolute(install_path)?;
418
419 let install_path = normalize_absolute_path(&install_path)?;
421
422 if !install_path.exists() {
424 return Err(Error::NotFound(url.to_url()));
425 }
426
427 match ext {
429 DistExtension::Wheel => {
430 let filename = WheelFilename::from_str(&url.filename()?)?;
432 if filename.name != name {
433 return Err(Error::PackageNameMismatch(
434 name,
435 filename.name,
436 url.verbatim().to_string(),
437 ));
438 }
439 Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
440 filename,
441 install_path: install_path.into_boxed_path(),
442 url,
443 })))
444 }
445 DistExtension::Source(ext) => {
446 let version = url
448 .filename()
449 .ok()
450 .and_then(|filename| {
451 SourceDistFilename::parse(filename.as_ref(), ext, &name).ok()
452 })
453 .map(|filename| filename.version);
454
455 Ok(Self::Source(SourceDist::Path(PathSourceDist {
456 name,
457 version,
458 install_path: install_path.into_boxed_path(),
459 ext,
460 url,
461 })))
462 }
463 }
464 }
465
466 pub fn from_directory_url(
468 name: PackageName,
469 url: VerbatimUrl,
470 install_path: &Path,
471 editable: Option<bool>,
472 r#virtual: Option<bool>,
473 ) -> Result<Self, Error> {
474 let install_path = path::absolute(install_path)?;
476
477 let install_path = normalize_absolute_path(&install_path)?;
479
480 if !install_path.exists() {
482 return Err(Error::NotFound(url.to_url()));
483 }
484
485 Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
487 name,
488 install_path: install_path.into_boxed_path(),
489 editable,
490 r#virtual,
491 url,
492 })))
493 }
494
495 pub fn from_git_url(
497 name: PackageName,
498 url: VerbatimUrl,
499 git: GitUrl,
500 subdirectory: Option<Box<Path>>,
501 ) -> Result<Self, Error> {
502 Ok(Self::Source(SourceDist::Git(GitSourceDist {
503 name,
504 git: Box::new(git),
505 subdirectory,
506 url,
507 })))
508 }
509
510 pub fn from_url(name: PackageName, url: VerbatimParsedUrl) -> Result<Self, Error> {
512 match url.parsed_url {
513 ParsedUrl::Archive(archive) => Self::from_http_url(
514 name,
515 url.verbatim,
516 archive.url,
517 archive.subdirectory,
518 archive.ext,
519 ),
520 ParsedUrl::Path(file) => {
521 Self::from_file_url(name, url.verbatim, &file.install_path, file.ext)
522 }
523 ParsedUrl::Directory(directory) => Self::from_directory_url(
524 name,
525 url.verbatim,
526 &directory.install_path,
527 directory.editable,
528 directory.r#virtual,
529 ),
530 ParsedUrl::Git(git) => {
531 Self::from_git_url(name, url.verbatim, git.url, git.subdirectory)
532 }
533 }
534 }
535
536 pub fn is_editable(&self) -> bool {
538 match self {
539 Self::Source(dist) => dist.is_editable(),
540 Self::Built(_) => false,
541 }
542 }
543
544 pub fn is_local(&self) -> bool {
546 match self {
547 Self::Source(dist) => dist.is_local(),
548 Self::Built(dist) => dist.is_local(),
549 }
550 }
551
552 pub fn index(&self) -> Option<&IndexUrl> {
554 match self {
555 Self::Built(dist) => dist.index(),
556 Self::Source(dist) => dist.index(),
557 }
558 }
559
560 pub fn file(&self) -> Option<&File> {
562 match self {
563 Self::Built(built) => built.file(),
564 Self::Source(source) => source.file(),
565 }
566 }
567
568 pub fn source_tree(&self) -> Option<&Path> {
570 match self {
571 Self::Built { .. } => None,
572 Self::Source(source) => source.source_tree(),
573 }
574 }
575
576 pub fn version(&self) -> Option<&Version> {
578 match self {
579 Self::Built(wheel) => Some(wheel.version()),
580 Self::Source(source_dist) => source_dist.version(),
581 }
582 }
583
584 pub fn as_ref(&self) -> DistRef<'_> {
586 match self {
587 Self::Built(dist) => DistRef::Built(dist),
588 Self::Source(dist) => DistRef::Source(dist),
589 }
590 }
591}
592
593impl<'a> From<&'a Dist> for DistRef<'a> {
594 fn from(dist: &'a Dist) -> Self {
595 match dist {
596 Dist::Built(built) => DistRef::Built(built),
597 Dist::Source(source) => DistRef::Source(source),
598 }
599 }
600}
601
602impl<'a> From<&'a SourceDist> for DistRef<'a> {
603 fn from(dist: &'a SourceDist) -> Self {
604 DistRef::Source(dist)
605 }
606}
607
608impl<'a> From<&'a BuiltDist> for DistRef<'a> {
609 fn from(dist: &'a BuiltDist) -> Self {
610 DistRef::Built(dist)
611 }
612}
613
614impl BuiltDist {
615 pub fn is_local(&self) -> bool {
617 matches!(self, Self::Path(_))
618 }
619
620 pub fn index(&self) -> Option<&IndexUrl> {
622 match self {
623 Self::Registry(registry) => Some(®istry.best_wheel().index),
624 Self::DirectUrl(_) => None,
625 Self::Path(_) => None,
626 }
627 }
628
629 pub fn file(&self) -> Option<&File> {
631 match self {
632 Self::Registry(registry) => Some(®istry.best_wheel().file),
633 Self::DirectUrl(_) | Self::Path(_) => None,
634 }
635 }
636
637 pub fn version(&self) -> &Version {
638 match self {
639 Self::Registry(wheels) => &wheels.best_wheel().filename.version,
640 Self::DirectUrl(wheel) => &wheel.filename.version,
641 Self::Path(wheel) => &wheel.filename.version,
642 }
643 }
644}
645
646impl SourceDist {
647 pub fn extension(&self) -> Option<SourceDistExtension> {
649 match self {
650 Self::Registry(source_dist) => Some(source_dist.ext),
651 Self::DirectUrl(source_dist) => Some(source_dist.ext),
652 Self::Path(source_dist) => Some(source_dist.ext),
653 Self::Git(_) | Self::Directory(_) => None,
654 }
655 }
656
657 pub fn index(&self) -> Option<&IndexUrl> {
659 match self {
660 Self::Registry(registry) => Some(®istry.index),
661 Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
662 }
663 }
664
665 pub fn file(&self) -> Option<&File> {
667 match self {
668 Self::Registry(registry) => Some(®istry.file),
669 Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
670 }
671 }
672
673 pub fn version(&self) -> Option<&Version> {
675 match self {
676 Self::Registry(source_dist) => Some(&source_dist.version),
677 Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
678 }
679 }
680
681 pub fn is_editable(&self) -> bool {
683 match self {
684 Self::Directory(DirectorySourceDist { editable, .. }) => editable.unwrap_or(false),
685 _ => false,
686 }
687 }
688
689 pub fn is_virtual(&self) -> bool {
691 match self {
692 Self::Directory(DirectorySourceDist { r#virtual, .. }) => r#virtual.unwrap_or(false),
693 _ => false,
694 }
695 }
696
697 pub fn is_local(&self) -> bool {
699 matches!(self, Self::Directory(_) | Self::Path(_))
700 }
701
702 pub fn as_path(&self) -> Option<&Path> {
704 match self {
705 Self::Path(dist) => Some(&dist.install_path),
706 Self::Directory(dist) => Some(&dist.install_path),
707 _ => None,
708 }
709 }
710
711 pub fn source_tree(&self) -> Option<&Path> {
713 match self {
714 Self::Directory(dist) => Some(&dist.install_path),
715 _ => None,
716 }
717 }
718}
719
720impl RegistryBuiltDist {
721 pub fn best_wheel(&self) -> &RegistryBuiltWheel {
723 &self.wheels[self.best_wheel_index]
724 }
725}
726
727impl DirectUrlBuiltDist {
728 pub fn parsed_url(&self) -> ParsedUrl {
730 ParsedUrl::Archive(ParsedArchiveUrl::from_source(
731 (*self.location).clone(),
732 None,
733 DistExtension::Wheel,
734 ))
735 }
736}
737
738impl PathBuiltDist {
739 pub fn parsed_url(&self) -> ParsedUrl {
741 ParsedUrl::Path(ParsedPathUrl::from_source(
742 self.install_path.clone(),
743 DistExtension::Wheel,
744 self.url.to_url(),
745 ))
746 }
747}
748
749impl PathSourceDist {
750 pub fn parsed_url(&self) -> ParsedUrl {
752 ParsedUrl::Path(ParsedPathUrl::from_source(
753 self.install_path.clone(),
754 DistExtension::Source(self.ext),
755 self.url.to_url(),
756 ))
757 }
758}
759
760impl DirectUrlSourceDist {
761 pub fn parsed_url(&self) -> ParsedUrl {
763 ParsedUrl::Archive(ParsedArchiveUrl::from_source(
764 (*self.location).clone(),
765 self.subdirectory.clone(),
766 DistExtension::Source(self.ext),
767 ))
768 }
769}
770
771impl GitSourceDist {
772 pub fn parsed_url(&self) -> ParsedUrl {
774 ParsedUrl::Git(ParsedGitUrl::from_source(
775 (*self.git).clone(),
776 self.subdirectory.clone(),
777 ))
778 }
779}
780
781impl DirectorySourceDist {
782 pub fn parsed_url(&self) -> ParsedUrl {
784 ParsedUrl::Directory(ParsedDirectoryUrl::from_source(
785 self.install_path.clone(),
786 self.editable,
787 self.r#virtual,
788 self.url.to_url(),
789 ))
790 }
791}
792
793impl Name for RegistryBuiltWheel {
794 fn name(&self) -> &PackageName {
795 &self.filename.name
796 }
797}
798
799impl Name for RegistryBuiltDist {
800 fn name(&self) -> &PackageName {
801 self.best_wheel().name()
802 }
803}
804
805impl Name for DirectUrlBuiltDist {
806 fn name(&self) -> &PackageName {
807 &self.filename.name
808 }
809}
810
811impl Name for PathBuiltDist {
812 fn name(&self) -> &PackageName {
813 &self.filename.name
814 }
815}
816
817impl Name for RegistrySourceDist {
818 fn name(&self) -> &PackageName {
819 &self.name
820 }
821}
822
823impl Name for DirectUrlSourceDist {
824 fn name(&self) -> &PackageName {
825 &self.name
826 }
827}
828
829impl Name for GitSourceDist {
830 fn name(&self) -> &PackageName {
831 &self.name
832 }
833}
834
835impl Name for PathSourceDist {
836 fn name(&self) -> &PackageName {
837 &self.name
838 }
839}
840
841impl Name for DirectorySourceDist {
842 fn name(&self) -> &PackageName {
843 &self.name
844 }
845}
846
847impl Name for SourceDist {
848 fn name(&self) -> &PackageName {
849 match self {
850 Self::Registry(dist) => dist.name(),
851 Self::DirectUrl(dist) => dist.name(),
852 Self::Git(dist) => dist.name(),
853 Self::Path(dist) => dist.name(),
854 Self::Directory(dist) => dist.name(),
855 }
856 }
857}
858
859impl Name for BuiltDist {
860 fn name(&self) -> &PackageName {
861 match self {
862 Self::Registry(dist) => dist.name(),
863 Self::DirectUrl(dist) => dist.name(),
864 Self::Path(dist) => dist.name(),
865 }
866 }
867}
868
869impl Name for Dist {
870 fn name(&self) -> &PackageName {
871 match self {
872 Self::Built(dist) => dist.name(),
873 Self::Source(dist) => dist.name(),
874 }
875 }
876}
877
878impl Name for CompatibleDist<'_> {
879 fn name(&self) -> &PackageName {
880 match self {
881 Self::InstalledDist(dist) => dist.name(),
882 Self::SourceDist {
883 sdist,
884 prioritized: _,
885 } => sdist.name(),
886 Self::CompatibleWheel {
887 wheel,
888 priority: _,
889 prioritized: _,
890 } => wheel.name(),
891 Self::IncompatibleWheel {
892 sdist,
893 wheel: _,
894 prioritized: _,
895 } => sdist.name(),
896 }
897 }
898}
899
900impl DistributionMetadata for RegistryBuiltWheel {
901 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
902 VersionOrUrlRef::Version(&self.filename.version)
903 }
904}
905
906impl DistributionMetadata for RegistryBuiltDist {
907 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
908 self.best_wheel().version_or_url()
909 }
910}
911
912impl DistributionMetadata for DirectUrlBuiltDist {
913 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
914 VersionOrUrlRef::Url(&self.url)
915 }
916
917 fn version_id(&self) -> VersionId {
918 VersionId::from_archive(self.location.as_ref(), None)
919 }
920}
921
922impl DistributionMetadata for PathBuiltDist {
923 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
924 VersionOrUrlRef::Url(&self.url)
925 }
926
927 fn version_id(&self) -> VersionId {
928 VersionId::from_path(self.install_path.as_ref())
929 }
930}
931
932impl DistributionMetadata for RegistrySourceDist {
933 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
934 VersionOrUrlRef::Version(&self.version)
935 }
936}
937
938impl DistributionMetadata for DirectUrlSourceDist {
939 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
940 VersionOrUrlRef::Url(&self.url)
941 }
942
943 fn version_id(&self) -> VersionId {
944 VersionId::from_archive(self.location.as_ref(), self.subdirectory.as_deref())
945 }
946}
947
948impl DistributionMetadata for GitSourceDist {
949 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
950 VersionOrUrlRef::Url(&self.url)
951 }
952
953 fn version_id(&self) -> VersionId {
954 VersionId::from_git(self.git.as_ref(), self.subdirectory.as_deref())
955 }
956}
957
958impl DistributionMetadata for PathSourceDist {
959 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
960 VersionOrUrlRef::Url(&self.url)
961 }
962
963 fn version_id(&self) -> VersionId {
964 VersionId::from_path(self.install_path.as_ref())
965 }
966}
967
968impl DistributionMetadata for DirectorySourceDist {
969 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
970 VersionOrUrlRef::Url(&self.url)
971 }
972
973 fn version_id(&self) -> VersionId {
974 VersionId::from_directory(self.install_path.as_ref())
975 }
976}
977
978impl DistributionMetadata for SourceDist {
979 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
980 match self {
981 Self::Registry(dist) => dist.version_or_url(),
982 Self::DirectUrl(dist) => dist.version_or_url(),
983 Self::Git(dist) => dist.version_or_url(),
984 Self::Path(dist) => dist.version_or_url(),
985 Self::Directory(dist) => dist.version_or_url(),
986 }
987 }
988
989 fn version_id(&self) -> VersionId {
990 match self {
991 Self::Registry(dist) => dist.version_id(),
992 Self::DirectUrl(dist) => dist.version_id(),
993 Self::Git(dist) => dist.version_id(),
994 Self::Path(dist) => dist.version_id(),
995 Self::Directory(dist) => dist.version_id(),
996 }
997 }
998}
999
1000impl DistributionMetadata for BuiltDist {
1001 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1002 match self {
1003 Self::Registry(dist) => dist.version_or_url(),
1004 Self::DirectUrl(dist) => dist.version_or_url(),
1005 Self::Path(dist) => dist.version_or_url(),
1006 }
1007 }
1008
1009 fn version_id(&self) -> VersionId {
1010 match self {
1011 Self::Registry(dist) => dist.version_id(),
1012 Self::DirectUrl(dist) => dist.version_id(),
1013 Self::Path(dist) => dist.version_id(),
1014 }
1015 }
1016}
1017
1018impl DistributionMetadata for Dist {
1019 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1020 match self {
1021 Self::Built(dist) => dist.version_or_url(),
1022 Self::Source(dist) => dist.version_or_url(),
1023 }
1024 }
1025
1026 fn version_id(&self) -> VersionId {
1027 match self {
1028 Self::Built(dist) => dist.version_id(),
1029 Self::Source(dist) => dist.version_id(),
1030 }
1031 }
1032}
1033
1034impl RemoteSource for File {
1035 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1036 Ok(Cow::Borrowed(&self.filename))
1037 }
1038
1039 fn size(&self) -> Option<u64> {
1040 self.size
1041 }
1042}
1043
1044impl RemoteSource for Url {
1045 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1046 let mut path_segments = self
1048 .path_segments()
1049 .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1050
1051 let last = path_segments
1053 .next_back()
1054 .expect("path segments is non-empty");
1055
1056 let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1058
1059 Ok(filename)
1060 }
1061
1062 fn size(&self) -> Option<u64> {
1063 None
1064 }
1065}
1066
1067impl RemoteSource for UrlString {
1068 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1069 let last = self
1071 .base_str()
1072 .split('/')
1073 .next_back()
1074 .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1075
1076 let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1078
1079 Ok(filename)
1080 }
1081
1082 fn size(&self) -> Option<u64> {
1083 None
1084 }
1085}
1086
1087impl RemoteSource for RegistryBuiltWheel {
1088 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1089 self.file.filename()
1090 }
1091
1092 fn size(&self) -> Option<u64> {
1093 self.file.size()
1094 }
1095}
1096
1097impl RemoteSource for RegistryBuiltDist {
1098 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1099 self.best_wheel().filename()
1100 }
1101
1102 fn size(&self) -> Option<u64> {
1103 self.best_wheel().size()
1104 }
1105}
1106
1107impl RemoteSource for RegistrySourceDist {
1108 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1109 self.file.filename()
1110 }
1111
1112 fn size(&self) -> Option<u64> {
1113 self.file.size()
1114 }
1115}
1116
1117impl RemoteSource for DirectUrlBuiltDist {
1118 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1119 self.url.filename()
1120 }
1121
1122 fn size(&self) -> Option<u64> {
1123 self.url.size()
1124 }
1125}
1126
1127impl RemoteSource for DirectUrlSourceDist {
1128 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1129 self.url.filename()
1130 }
1131
1132 fn size(&self) -> Option<u64> {
1133 self.url.size()
1134 }
1135}
1136
1137impl RemoteSource for GitSourceDist {
1138 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1139 match self.url.filename()? {
1141 Cow::Borrowed(filename) => {
1142 if let Some((_, filename)) = filename.rsplit_once('@') {
1143 Ok(Cow::Borrowed(filename))
1144 } else {
1145 Ok(Cow::Borrowed(filename))
1146 }
1147 }
1148 Cow::Owned(filename) => {
1149 if let Some((_, filename)) = filename.rsplit_once('@') {
1150 Ok(Cow::Owned(filename.to_owned()))
1151 } else {
1152 Ok(Cow::Owned(filename))
1153 }
1154 }
1155 }
1156 }
1157
1158 fn size(&self) -> Option<u64> {
1159 self.url.size()
1160 }
1161}
1162
1163impl RemoteSource for PathBuiltDist {
1164 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1165 self.url.filename()
1166 }
1167
1168 fn size(&self) -> Option<u64> {
1169 self.url.size()
1170 }
1171}
1172
1173impl RemoteSource for PathSourceDist {
1174 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1175 self.url.filename()
1176 }
1177
1178 fn size(&self) -> Option<u64> {
1179 self.url.size()
1180 }
1181}
1182
1183impl RemoteSource for DirectorySourceDist {
1184 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1185 self.url.filename()
1186 }
1187
1188 fn size(&self) -> Option<u64> {
1189 self.url.size()
1190 }
1191}
1192
1193impl RemoteSource for SourceDist {
1194 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1195 match self {
1196 Self::Registry(dist) => dist.filename(),
1197 Self::DirectUrl(dist) => dist.filename(),
1198 Self::Git(dist) => dist.filename(),
1199 Self::Path(dist) => dist.filename(),
1200 Self::Directory(dist) => dist.filename(),
1201 }
1202 }
1203
1204 fn size(&self) -> Option<u64> {
1205 match self {
1206 Self::Registry(dist) => dist.size(),
1207 Self::DirectUrl(dist) => dist.size(),
1208 Self::Git(dist) => dist.size(),
1209 Self::Path(dist) => dist.size(),
1210 Self::Directory(dist) => dist.size(),
1211 }
1212 }
1213}
1214
1215impl RemoteSource for BuiltDist {
1216 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1217 match self {
1218 Self::Registry(dist) => dist.filename(),
1219 Self::DirectUrl(dist) => dist.filename(),
1220 Self::Path(dist) => dist.filename(),
1221 }
1222 }
1223
1224 fn size(&self) -> Option<u64> {
1225 match self {
1226 Self::Registry(dist) => dist.size(),
1227 Self::DirectUrl(dist) => dist.size(),
1228 Self::Path(dist) => dist.size(),
1229 }
1230 }
1231}
1232
1233impl RemoteSource for Dist {
1234 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1235 match self {
1236 Self::Built(dist) => dist.filename(),
1237 Self::Source(dist) => dist.filename(),
1238 }
1239 }
1240
1241 fn size(&self) -> Option<u64> {
1242 match self {
1243 Self::Built(dist) => dist.size(),
1244 Self::Source(dist) => dist.size(),
1245 }
1246 }
1247}
1248
1249impl Identifier for DisplaySafeUrl {
1250 fn distribution_id(&self) -> DistributionId {
1251 DistributionId::Url(uv_cache_key::CanonicalUrl::new(self))
1252 }
1253
1254 fn resource_id(&self) -> ResourceId {
1255 ResourceId::Url(uv_cache_key::RepositoryUrl::new(self))
1256 }
1257}
1258
1259impl Identifier for File {
1260 fn distribution_id(&self) -> DistributionId {
1261 self.hashes
1262 .first()
1263 .cloned()
1264 .map(DistributionId::Digest)
1265 .unwrap_or_else(|| self.url.distribution_id())
1266 }
1267
1268 fn resource_id(&self) -> ResourceId {
1269 self.hashes
1270 .first()
1271 .cloned()
1272 .map(ResourceId::Digest)
1273 .unwrap_or_else(|| self.url.resource_id())
1274 }
1275}
1276
1277impl Identifier for Path {
1278 fn distribution_id(&self) -> DistributionId {
1279 DistributionId::PathBuf(self.to_path_buf())
1280 }
1281
1282 fn resource_id(&self) -> ResourceId {
1283 ResourceId::PathBuf(self.to_path_buf())
1284 }
1285}
1286
1287impl Identifier for FileLocation {
1288 fn distribution_id(&self) -> DistributionId {
1289 match self {
1290 Self::RelativeUrl(base, url) => {
1291 DistributionId::RelativeUrl(base.to_string(), url.to_string())
1292 }
1293 Self::AbsoluteUrl(url) => DistributionId::AbsoluteUrl(url.to_string()),
1294 }
1295 }
1296
1297 fn resource_id(&self) -> ResourceId {
1298 match self {
1299 Self::RelativeUrl(base, url) => {
1300 ResourceId::RelativeUrl(base.to_string(), url.to_string())
1301 }
1302 Self::AbsoluteUrl(url) => ResourceId::AbsoluteUrl(url.to_string()),
1303 }
1304 }
1305}
1306
1307impl Identifier for RegistryBuiltWheel {
1308 fn distribution_id(&self) -> DistributionId {
1309 self.file.distribution_id()
1310 }
1311
1312 fn resource_id(&self) -> ResourceId {
1313 self.file.resource_id()
1314 }
1315}
1316
1317impl Identifier for RegistryBuiltDist {
1318 fn distribution_id(&self) -> DistributionId {
1319 self.best_wheel().distribution_id()
1320 }
1321
1322 fn resource_id(&self) -> ResourceId {
1323 self.best_wheel().resource_id()
1324 }
1325}
1326
1327impl Identifier for RegistrySourceDist {
1328 fn distribution_id(&self) -> DistributionId {
1329 self.file.distribution_id()
1330 }
1331
1332 fn resource_id(&self) -> ResourceId {
1333 self.file.resource_id()
1334 }
1335}
1336
1337impl Identifier for DirectUrlBuiltDist {
1338 fn distribution_id(&self) -> DistributionId {
1339 self.url.distribution_id()
1340 }
1341
1342 fn resource_id(&self) -> ResourceId {
1343 self.url.resource_id()
1344 }
1345}
1346
1347impl Identifier for DirectUrlSourceDist {
1348 fn distribution_id(&self) -> DistributionId {
1349 self.url.distribution_id()
1350 }
1351
1352 fn resource_id(&self) -> ResourceId {
1353 self.url.resource_id()
1354 }
1355}
1356
1357impl Identifier for PathBuiltDist {
1358 fn distribution_id(&self) -> DistributionId {
1359 self.url.distribution_id()
1360 }
1361
1362 fn resource_id(&self) -> ResourceId {
1363 self.url.resource_id()
1364 }
1365}
1366
1367impl Identifier for PathSourceDist {
1368 fn distribution_id(&self) -> DistributionId {
1369 self.url.distribution_id()
1370 }
1371
1372 fn resource_id(&self) -> ResourceId {
1373 self.url.resource_id()
1374 }
1375}
1376
1377impl Identifier for DirectorySourceDist {
1378 fn distribution_id(&self) -> DistributionId {
1379 self.url.distribution_id()
1380 }
1381
1382 fn resource_id(&self) -> ResourceId {
1383 self.url.resource_id()
1384 }
1385}
1386
1387impl Identifier for GitSourceDist {
1388 fn distribution_id(&self) -> DistributionId {
1389 self.url.distribution_id()
1390 }
1391
1392 fn resource_id(&self) -> ResourceId {
1393 self.url.resource_id()
1394 }
1395}
1396
1397impl Identifier for SourceDist {
1398 fn distribution_id(&self) -> DistributionId {
1399 match self {
1400 Self::Registry(dist) => dist.distribution_id(),
1401 Self::DirectUrl(dist) => dist.distribution_id(),
1402 Self::Git(dist) => dist.distribution_id(),
1403 Self::Path(dist) => dist.distribution_id(),
1404 Self::Directory(dist) => dist.distribution_id(),
1405 }
1406 }
1407
1408 fn resource_id(&self) -> ResourceId {
1409 match self {
1410 Self::Registry(dist) => dist.resource_id(),
1411 Self::DirectUrl(dist) => dist.resource_id(),
1412 Self::Git(dist) => dist.resource_id(),
1413 Self::Path(dist) => dist.resource_id(),
1414 Self::Directory(dist) => dist.resource_id(),
1415 }
1416 }
1417}
1418
1419impl Identifier for BuiltDist {
1420 fn distribution_id(&self) -> DistributionId {
1421 match self {
1422 Self::Registry(dist) => dist.distribution_id(),
1423 Self::DirectUrl(dist) => dist.distribution_id(),
1424 Self::Path(dist) => dist.distribution_id(),
1425 }
1426 }
1427
1428 fn resource_id(&self) -> ResourceId {
1429 match self {
1430 Self::Registry(dist) => dist.resource_id(),
1431 Self::DirectUrl(dist) => dist.resource_id(),
1432 Self::Path(dist) => dist.resource_id(),
1433 }
1434 }
1435}
1436
1437impl Identifier for InstalledDist {
1438 fn distribution_id(&self) -> DistributionId {
1439 self.install_path().distribution_id()
1440 }
1441
1442 fn resource_id(&self) -> ResourceId {
1443 self.install_path().resource_id()
1444 }
1445}
1446
1447impl Identifier for Dist {
1448 fn distribution_id(&self) -> DistributionId {
1449 match self {
1450 Self::Built(dist) => dist.distribution_id(),
1451 Self::Source(dist) => dist.distribution_id(),
1452 }
1453 }
1454
1455 fn resource_id(&self) -> ResourceId {
1456 match self {
1457 Self::Built(dist) => dist.resource_id(),
1458 Self::Source(dist) => dist.resource_id(),
1459 }
1460 }
1461}
1462
1463impl Identifier for DirectSourceUrl<'_> {
1464 fn distribution_id(&self) -> DistributionId {
1465 self.url.distribution_id()
1466 }
1467
1468 fn resource_id(&self) -> ResourceId {
1469 self.url.resource_id()
1470 }
1471}
1472
1473impl Identifier for GitSourceUrl<'_> {
1474 fn distribution_id(&self) -> DistributionId {
1475 self.url.distribution_id()
1476 }
1477
1478 fn resource_id(&self) -> ResourceId {
1479 self.url.resource_id()
1480 }
1481}
1482
1483impl Identifier for PathSourceUrl<'_> {
1484 fn distribution_id(&self) -> DistributionId {
1485 self.url.distribution_id()
1486 }
1487
1488 fn resource_id(&self) -> ResourceId {
1489 self.url.resource_id()
1490 }
1491}
1492
1493impl Identifier for DirectorySourceUrl<'_> {
1494 fn distribution_id(&self) -> DistributionId {
1495 self.url.distribution_id()
1496 }
1497
1498 fn resource_id(&self) -> ResourceId {
1499 self.url.resource_id()
1500 }
1501}
1502
1503impl Identifier for SourceUrl<'_> {
1504 fn distribution_id(&self) -> DistributionId {
1505 match self {
1506 Self::Direct(url) => url.distribution_id(),
1507 Self::Git(url) => url.distribution_id(),
1508 Self::Path(url) => url.distribution_id(),
1509 Self::Directory(url) => url.distribution_id(),
1510 }
1511 }
1512
1513 fn resource_id(&self) -> ResourceId {
1514 match self {
1515 Self::Direct(url) => url.resource_id(),
1516 Self::Git(url) => url.resource_id(),
1517 Self::Path(url) => url.resource_id(),
1518 Self::Directory(url) => url.resource_id(),
1519 }
1520 }
1521}
1522
1523impl Identifier for BuildableSource<'_> {
1524 fn distribution_id(&self) -> DistributionId {
1525 match self {
1526 Self::Dist(source) => source.distribution_id(),
1527 Self::Url(source) => source.distribution_id(),
1528 }
1529 }
1530
1531 fn resource_id(&self) -> ResourceId {
1532 match self {
1533 Self::Dist(source) => source.resource_id(),
1534 Self::Url(source) => source.resource_id(),
1535 }
1536 }
1537}
1538
1539#[cfg(test)]
1540mod test {
1541 use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString};
1542 use uv_redacted::DisplaySafeUrl;
1543
1544 #[test]
1546 fn dist_size() {
1547 assert!(size_of::<Dist>() <= 200, "{}", size_of::<Dist>());
1548 assert!(size_of::<BuiltDist>() <= 200, "{}", size_of::<BuiltDist>());
1549 assert!(
1550 size_of::<SourceDist>() <= 176,
1551 "{}",
1552 size_of::<SourceDist>()
1553 );
1554 }
1555
1556 #[test]
1557 fn remote_source() {
1558 for url in [
1559 "https://example.com/foo-0.1.0.tar.gz",
1560 "https://example.com/foo-0.1.0.tar.gz#fragment",
1561 "https://example.com/foo-0.1.0.tar.gz?query",
1562 "https://example.com/foo-0.1.0.tar.gz?query#fragment",
1563 "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment",
1564 "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment/3",
1565 ] {
1566 let url = DisplaySafeUrl::parse(url).unwrap();
1567 assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1568 let url = UrlString::from(url.clone());
1569 assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1570 }
1571 }
1572}