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