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<T: Pep508Url> VersionOrUrlRef<'_, T> {
126 pub fn url(&self) -> Option<&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 InstalledVersion<'_> {
163 pub fn url(&self) -> Option<&DisplaySafeUrl> {
165 match self {
166 Self::Version(_) => None,
167 Self::Url(url, _) => Some(url),
168 }
169 }
170
171 pub fn version(&self) -> &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)]
207#[allow(clippy::large_enum_variant)]
208pub enum BuiltDist {
209 Registry(RegistryBuiltDist),
210 DirectUrl(DirectUrlBuiltDist),
211 Path(PathBuiltDist),
212}
213
214#[derive(Debug, Clone, Hash, PartialEq, Eq)]
216#[allow(clippy::large_enum_variant)]
217pub enum SourceDist {
218 Registry(RegistrySourceDist),
219 DirectUrl(DirectUrlSourceDist),
220 Git(GitSourceDist),
221 Path(PathSourceDist),
222 Directory(DirectorySourceDist),
223}
224
225#[derive(Debug, Clone, Hash, PartialEq, Eq)]
227pub struct RegistryBuiltWheel {
228 pub filename: WheelFilename,
229 pub file: Box<File>,
230 pub index: IndexUrl,
231}
232
233#[derive(Debug, Clone, Hash, PartialEq, Eq)]
235pub struct RegistryBuiltDist {
236 pub wheels: Vec<RegistryBuiltWheel>,
239 pub best_wheel_index: usize,
244 pub sdist: Option<RegistrySourceDist>,
252 }
262
263#[derive(Debug, Clone, Hash, PartialEq, Eq)]
265pub struct DirectUrlBuiltDist {
266 pub filename: WheelFilename,
269 pub location: Box<DisplaySafeUrl>,
271 pub url: VerbatimUrl,
273}
274
275#[derive(Debug, Clone, Hash, PartialEq, Eq)]
277pub struct PathBuiltDist {
278 pub filename: WheelFilename,
279 pub install_path: Box<Path>,
281 pub url: VerbatimUrl,
283}
284
285#[derive(Debug, Clone, Hash, PartialEq, Eq)]
287pub struct RegistrySourceDist {
288 pub name: PackageName,
289 pub version: Version,
290 pub file: Box<File>,
291 pub ext: SourceDistExtension,
293 pub index: IndexUrl,
294 pub wheels: Vec<RegistryBuiltWheel>,
302}
303
304#[derive(Debug, Clone, Hash, PartialEq, Eq)]
306pub struct DirectUrlSourceDist {
307 pub name: PackageName,
310 pub location: Box<DisplaySafeUrl>,
312 pub subdirectory: Option<Box<Path>>,
314 pub ext: SourceDistExtension,
316 pub url: VerbatimUrl,
318}
319
320#[derive(Debug, Clone, Hash, PartialEq, Eq)]
322pub struct GitSourceDist {
323 pub name: PackageName,
324 pub git: Box<GitUrl>,
326 pub subdirectory: Option<Box<Path>>,
328 pub url: VerbatimUrl,
330}
331
332#[derive(Debug, Clone, Hash, PartialEq, Eq)]
334pub struct PathSourceDist {
335 pub name: PackageName,
336 pub version: Option<Version>,
337 pub install_path: Box<Path>,
339 pub ext: SourceDistExtension,
341 pub url: VerbatimUrl,
343}
344
345#[derive(Debug, Clone, Hash, PartialEq, Eq)]
347pub struct DirectorySourceDist {
348 pub name: PackageName,
349 pub install_path: Box<Path>,
351 pub editable: Option<bool>,
353 pub r#virtual: Option<bool>,
355 pub url: VerbatimUrl,
357}
358
359impl Dist {
360 pub fn from_http_url(
363 name: PackageName,
364 url: VerbatimUrl,
365 location: DisplaySafeUrl,
366 subdirectory: Option<Box<Path>>,
367 ext: DistExtension,
368 ) -> Result<Self, Error> {
369 match ext {
370 DistExtension::Wheel => {
371 let filename = WheelFilename::from_str(&url.filename()?)?;
373 if filename.name != name {
374 return Err(Error::PackageNameMismatch(
375 name,
376 filename.name,
377 url.verbatim().to_string(),
378 ));
379 }
380
381 Ok(Self::Built(BuiltDist::DirectUrl(DirectUrlBuiltDist {
382 filename,
383 location: Box::new(location),
384 url,
385 })))
386 }
387 DistExtension::Source(ext) => {
388 Ok(Self::Source(SourceDist::DirectUrl(DirectUrlSourceDist {
389 name,
390 location: Box::new(location),
391 subdirectory,
392 ext,
393 url,
394 })))
395 }
396 }
397 }
398
399 pub fn from_file_url(
401 name: PackageName,
402 url: VerbatimUrl,
403 install_path: &Path,
404 ext: DistExtension,
405 ) -> Result<Self, Error> {
406 let install_path = path::absolute(install_path)?;
408
409 let install_path = normalize_absolute_path(&install_path)?;
411
412 if !install_path.exists() {
414 return Err(Error::NotFound(url.to_url()));
415 }
416
417 match ext {
419 DistExtension::Wheel => {
420 let filename = WheelFilename::from_str(&url.filename()?)?;
422 if filename.name != name {
423 return Err(Error::PackageNameMismatch(
424 name,
425 filename.name,
426 url.verbatim().to_string(),
427 ));
428 }
429 Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
430 filename,
431 install_path: install_path.into_boxed_path(),
432 url,
433 })))
434 }
435 DistExtension::Source(ext) => {
436 let version = url
438 .filename()
439 .ok()
440 .and_then(|filename| {
441 SourceDistFilename::parse(filename.as_ref(), ext, &name).ok()
442 })
443 .map(|filename| filename.version);
444
445 Ok(Self::Source(SourceDist::Path(PathSourceDist {
446 name,
447 version,
448 install_path: install_path.into_boxed_path(),
449 ext,
450 url,
451 })))
452 }
453 }
454 }
455
456 pub fn from_directory_url(
458 name: PackageName,
459 url: VerbatimUrl,
460 install_path: &Path,
461 editable: Option<bool>,
462 r#virtual: Option<bool>,
463 ) -> Result<Self, Error> {
464 let install_path = path::absolute(install_path)?;
466
467 let install_path = normalize_absolute_path(&install_path)?;
469
470 if !install_path.exists() {
472 return Err(Error::NotFound(url.to_url()));
473 }
474
475 Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
477 name,
478 install_path: install_path.into_boxed_path(),
479 editable,
480 r#virtual,
481 url,
482 })))
483 }
484
485 pub fn from_git_url(
487 name: PackageName,
488 url: VerbatimUrl,
489 git: GitUrl,
490 subdirectory: Option<Box<Path>>,
491 ) -> Result<Self, Error> {
492 Ok(Self::Source(SourceDist::Git(GitSourceDist {
493 name,
494 git: Box::new(git),
495 subdirectory,
496 url,
497 })))
498 }
499
500 pub fn from_url(name: PackageName, url: VerbatimParsedUrl) -> Result<Self, Error> {
502 match url.parsed_url {
503 ParsedUrl::Archive(archive) => Self::from_http_url(
504 name,
505 url.verbatim,
506 archive.url,
507 archive.subdirectory,
508 archive.ext,
509 ),
510 ParsedUrl::Path(file) => {
511 Self::from_file_url(name, url.verbatim, &file.install_path, file.ext)
512 }
513 ParsedUrl::Directory(directory) => Self::from_directory_url(
514 name,
515 url.verbatim,
516 &directory.install_path,
517 directory.editable,
518 directory.r#virtual,
519 ),
520 ParsedUrl::Git(git) => {
521 Self::from_git_url(name, url.verbatim, git.url, git.subdirectory)
522 }
523 }
524 }
525
526 pub fn is_editable(&self) -> bool {
528 match self {
529 Self::Source(dist) => dist.is_editable(),
530 Self::Built(_) => false,
531 }
532 }
533
534 pub fn is_local(&self) -> bool {
536 match self {
537 Self::Source(dist) => dist.is_local(),
538 Self::Built(dist) => dist.is_local(),
539 }
540 }
541
542 pub fn index(&self) -> Option<&IndexUrl> {
544 match self {
545 Self::Built(dist) => dist.index(),
546 Self::Source(dist) => dist.index(),
547 }
548 }
549
550 pub fn file(&self) -> Option<&File> {
552 match self {
553 Self::Built(built) => built.file(),
554 Self::Source(source) => source.file(),
555 }
556 }
557
558 pub fn source_tree(&self) -> Option<&Path> {
560 match self {
561 Self::Built { .. } => None,
562 Self::Source(source) => source.source_tree(),
563 }
564 }
565
566 pub fn version(&self) -> Option<&Version> {
568 match self {
569 Self::Built(wheel) => Some(wheel.version()),
570 Self::Source(source_dist) => source_dist.version(),
571 }
572 }
573
574 pub fn as_ref(&self) -> DistRef<'_> {
576 match self {
577 Self::Built(dist) => DistRef::Built(dist),
578 Self::Source(dist) => DistRef::Source(dist),
579 }
580 }
581}
582
583impl<'a> From<&'a Dist> for DistRef<'a> {
584 fn from(dist: &'a Dist) -> Self {
585 match dist {
586 Dist::Built(built) => DistRef::Built(built),
587 Dist::Source(source) => DistRef::Source(source),
588 }
589 }
590}
591
592impl<'a> From<&'a SourceDist> for DistRef<'a> {
593 fn from(dist: &'a SourceDist) -> Self {
594 DistRef::Source(dist)
595 }
596}
597
598impl<'a> From<&'a BuiltDist> for DistRef<'a> {
599 fn from(dist: &'a BuiltDist) -> Self {
600 DistRef::Built(dist)
601 }
602}
603
604impl BuiltDist {
605 pub fn is_local(&self) -> bool {
607 matches!(self, Self::Path(_))
608 }
609
610 pub fn index(&self) -> Option<&IndexUrl> {
612 match self {
613 Self::Registry(registry) => Some(®istry.best_wheel().index),
614 Self::DirectUrl(_) => None,
615 Self::Path(_) => None,
616 }
617 }
618
619 pub fn file(&self) -> Option<&File> {
621 match self {
622 Self::Registry(registry) => Some(®istry.best_wheel().file),
623 Self::DirectUrl(_) | Self::Path(_) => None,
624 }
625 }
626
627 pub fn version(&self) -> &Version {
628 match self {
629 Self::Registry(wheels) => &wheels.best_wheel().filename.version,
630 Self::DirectUrl(wheel) => &wheel.filename.version,
631 Self::Path(wheel) => &wheel.filename.version,
632 }
633 }
634}
635
636impl SourceDist {
637 pub fn index(&self) -> Option<&IndexUrl> {
639 match self {
640 Self::Registry(registry) => Some(®istry.index),
641 Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
642 }
643 }
644
645 pub fn file(&self) -> Option<&File> {
647 match self {
648 Self::Registry(registry) => Some(®istry.file),
649 Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
650 }
651 }
652
653 pub fn version(&self) -> Option<&Version> {
655 match self {
656 Self::Registry(source_dist) => Some(&source_dist.version),
657 Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
658 }
659 }
660
661 pub fn is_editable(&self) -> bool {
663 match self {
664 Self::Directory(DirectorySourceDist { editable, .. }) => editable.unwrap_or(false),
665 _ => false,
666 }
667 }
668
669 pub fn is_virtual(&self) -> bool {
671 match self {
672 Self::Directory(DirectorySourceDist { r#virtual, .. }) => r#virtual.unwrap_or(false),
673 _ => false,
674 }
675 }
676
677 pub fn is_local(&self) -> bool {
679 matches!(self, Self::Directory(_) | Self::Path(_))
680 }
681
682 pub fn as_path(&self) -> Option<&Path> {
684 match self {
685 Self::Path(dist) => Some(&dist.install_path),
686 Self::Directory(dist) => Some(&dist.install_path),
687 _ => None,
688 }
689 }
690
691 pub fn source_tree(&self) -> Option<&Path> {
693 match self {
694 Self::Directory(dist) => Some(&dist.install_path),
695 _ => None,
696 }
697 }
698}
699
700impl RegistryBuiltDist {
701 pub fn best_wheel(&self) -> &RegistryBuiltWheel {
703 &self.wheels[self.best_wheel_index]
704 }
705}
706
707impl DirectUrlBuiltDist {
708 pub fn parsed_url(&self) -> ParsedUrl {
710 ParsedUrl::Archive(ParsedArchiveUrl::from_source(
711 (*self.location).clone(),
712 None,
713 DistExtension::Wheel,
714 ))
715 }
716}
717
718impl PathBuiltDist {
719 pub fn parsed_url(&self) -> ParsedUrl {
721 ParsedUrl::Path(ParsedPathUrl::from_source(
722 self.install_path.clone(),
723 DistExtension::Wheel,
724 self.url.to_url(),
725 ))
726 }
727}
728
729impl PathSourceDist {
730 pub fn parsed_url(&self) -> ParsedUrl {
732 ParsedUrl::Path(ParsedPathUrl::from_source(
733 self.install_path.clone(),
734 DistExtension::Source(self.ext),
735 self.url.to_url(),
736 ))
737 }
738}
739
740impl DirectUrlSourceDist {
741 pub fn parsed_url(&self) -> ParsedUrl {
743 ParsedUrl::Archive(ParsedArchiveUrl::from_source(
744 (*self.location).clone(),
745 self.subdirectory.clone(),
746 DistExtension::Source(self.ext),
747 ))
748 }
749}
750
751impl GitSourceDist {
752 pub fn parsed_url(&self) -> ParsedUrl {
754 ParsedUrl::Git(ParsedGitUrl::from_source(
755 (*self.git).clone(),
756 self.subdirectory.clone(),
757 ))
758 }
759}
760
761impl DirectorySourceDist {
762 pub fn parsed_url(&self) -> ParsedUrl {
764 ParsedUrl::Directory(ParsedDirectoryUrl::from_source(
765 self.install_path.clone(),
766 self.editable,
767 self.r#virtual,
768 self.url.to_url(),
769 ))
770 }
771}
772
773impl Name for RegistryBuiltWheel {
774 fn name(&self) -> &PackageName {
775 &self.filename.name
776 }
777}
778
779impl Name for RegistryBuiltDist {
780 fn name(&self) -> &PackageName {
781 self.best_wheel().name()
782 }
783}
784
785impl Name for DirectUrlBuiltDist {
786 fn name(&self) -> &PackageName {
787 &self.filename.name
788 }
789}
790
791impl Name for PathBuiltDist {
792 fn name(&self) -> &PackageName {
793 &self.filename.name
794 }
795}
796
797impl Name for RegistrySourceDist {
798 fn name(&self) -> &PackageName {
799 &self.name
800 }
801}
802
803impl Name for DirectUrlSourceDist {
804 fn name(&self) -> &PackageName {
805 &self.name
806 }
807}
808
809impl Name for GitSourceDist {
810 fn name(&self) -> &PackageName {
811 &self.name
812 }
813}
814
815impl Name for PathSourceDist {
816 fn name(&self) -> &PackageName {
817 &self.name
818 }
819}
820
821impl Name for DirectorySourceDist {
822 fn name(&self) -> &PackageName {
823 &self.name
824 }
825}
826
827impl Name for SourceDist {
828 fn name(&self) -> &PackageName {
829 match self {
830 Self::Registry(dist) => dist.name(),
831 Self::DirectUrl(dist) => dist.name(),
832 Self::Git(dist) => dist.name(),
833 Self::Path(dist) => dist.name(),
834 Self::Directory(dist) => dist.name(),
835 }
836 }
837}
838
839impl Name for BuiltDist {
840 fn name(&self) -> &PackageName {
841 match self {
842 Self::Registry(dist) => dist.name(),
843 Self::DirectUrl(dist) => dist.name(),
844 Self::Path(dist) => dist.name(),
845 }
846 }
847}
848
849impl Name for Dist {
850 fn name(&self) -> &PackageName {
851 match self {
852 Self::Built(dist) => dist.name(),
853 Self::Source(dist) => dist.name(),
854 }
855 }
856}
857
858impl Name for CompatibleDist<'_> {
859 fn name(&self) -> &PackageName {
860 match self {
861 Self::InstalledDist(dist) => dist.name(),
862 Self::SourceDist {
863 sdist,
864 prioritized: _,
865 } => sdist.name(),
866 Self::CompatibleWheel {
867 wheel,
868 priority: _,
869 prioritized: _,
870 } => wheel.name(),
871 Self::IncompatibleWheel {
872 sdist,
873 wheel: _,
874 prioritized: _,
875 } => sdist.name(),
876 }
877 }
878}
879
880impl DistributionMetadata for RegistryBuiltWheel {
881 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
882 VersionOrUrlRef::Version(&self.filename.version)
883 }
884}
885
886impl DistributionMetadata for RegistryBuiltDist {
887 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
888 self.best_wheel().version_or_url()
889 }
890}
891
892impl DistributionMetadata for DirectUrlBuiltDist {
893 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
894 VersionOrUrlRef::Url(&self.url)
895 }
896}
897
898impl DistributionMetadata for PathBuiltDist {
899 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
900 VersionOrUrlRef::Url(&self.url)
901 }
902}
903
904impl DistributionMetadata for RegistrySourceDist {
905 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
906 VersionOrUrlRef::Version(&self.version)
907 }
908}
909
910impl DistributionMetadata for DirectUrlSourceDist {
911 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
912 VersionOrUrlRef::Url(&self.url)
913 }
914}
915
916impl DistributionMetadata for GitSourceDist {
917 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
918 VersionOrUrlRef::Url(&self.url)
919 }
920}
921
922impl DistributionMetadata for PathSourceDist {
923 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
924 VersionOrUrlRef::Url(&self.url)
925 }
926}
927
928impl DistributionMetadata for DirectorySourceDist {
929 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
930 VersionOrUrlRef::Url(&self.url)
931 }
932}
933
934impl DistributionMetadata for SourceDist {
935 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
936 match self {
937 Self::Registry(dist) => dist.version_or_url(),
938 Self::DirectUrl(dist) => dist.version_or_url(),
939 Self::Git(dist) => dist.version_or_url(),
940 Self::Path(dist) => dist.version_or_url(),
941 Self::Directory(dist) => dist.version_or_url(),
942 }
943 }
944}
945
946impl DistributionMetadata for BuiltDist {
947 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
948 match self {
949 Self::Registry(dist) => dist.version_or_url(),
950 Self::DirectUrl(dist) => dist.version_or_url(),
951 Self::Path(dist) => dist.version_or_url(),
952 }
953 }
954}
955
956impl DistributionMetadata for Dist {
957 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
958 match self {
959 Self::Built(dist) => dist.version_or_url(),
960 Self::Source(dist) => dist.version_or_url(),
961 }
962 }
963}
964
965impl RemoteSource for File {
966 fn filename(&self) -> Result<Cow<'_, str>, Error> {
967 Ok(Cow::Borrowed(&self.filename))
968 }
969
970 fn size(&self) -> Option<u64> {
971 self.size
972 }
973}
974
975impl RemoteSource for Url {
976 fn filename(&self) -> Result<Cow<'_, str>, Error> {
977 let mut path_segments = self
979 .path_segments()
980 .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
981
982 let last = path_segments
984 .next_back()
985 .expect("path segments is non-empty");
986
987 let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
989
990 Ok(filename)
991 }
992
993 fn size(&self) -> Option<u64> {
994 None
995 }
996}
997
998impl RemoteSource for UrlString {
999 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1000 let last = self
1002 .base_str()
1003 .split('/')
1004 .next_back()
1005 .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1006
1007 let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1009
1010 Ok(filename)
1011 }
1012
1013 fn size(&self) -> Option<u64> {
1014 None
1015 }
1016}
1017
1018impl RemoteSource for RegistryBuiltWheel {
1019 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1020 self.file.filename()
1021 }
1022
1023 fn size(&self) -> Option<u64> {
1024 self.file.size()
1025 }
1026}
1027
1028impl RemoteSource for RegistryBuiltDist {
1029 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1030 self.best_wheel().filename()
1031 }
1032
1033 fn size(&self) -> Option<u64> {
1034 self.best_wheel().size()
1035 }
1036}
1037
1038impl RemoteSource for RegistrySourceDist {
1039 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1040 self.file.filename()
1041 }
1042
1043 fn size(&self) -> Option<u64> {
1044 self.file.size()
1045 }
1046}
1047
1048impl RemoteSource for DirectUrlBuiltDist {
1049 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1050 self.url.filename()
1051 }
1052
1053 fn size(&self) -> Option<u64> {
1054 self.url.size()
1055 }
1056}
1057
1058impl RemoteSource for DirectUrlSourceDist {
1059 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1060 self.url.filename()
1061 }
1062
1063 fn size(&self) -> Option<u64> {
1064 self.url.size()
1065 }
1066}
1067
1068impl RemoteSource for GitSourceDist {
1069 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1070 match self.url.filename()? {
1072 Cow::Borrowed(filename) => {
1073 if let Some((_, filename)) = filename.rsplit_once('@') {
1074 Ok(Cow::Borrowed(filename))
1075 } else {
1076 Ok(Cow::Borrowed(filename))
1077 }
1078 }
1079 Cow::Owned(filename) => {
1080 if let Some((_, filename)) = filename.rsplit_once('@') {
1081 Ok(Cow::Owned(filename.to_owned()))
1082 } else {
1083 Ok(Cow::Owned(filename))
1084 }
1085 }
1086 }
1087 }
1088
1089 fn size(&self) -> Option<u64> {
1090 self.url.size()
1091 }
1092}
1093
1094impl RemoteSource for PathBuiltDist {
1095 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1096 self.url.filename()
1097 }
1098
1099 fn size(&self) -> Option<u64> {
1100 self.url.size()
1101 }
1102}
1103
1104impl RemoteSource for PathSourceDist {
1105 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1106 self.url.filename()
1107 }
1108
1109 fn size(&self) -> Option<u64> {
1110 self.url.size()
1111 }
1112}
1113
1114impl RemoteSource for DirectorySourceDist {
1115 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1116 self.url.filename()
1117 }
1118
1119 fn size(&self) -> Option<u64> {
1120 self.url.size()
1121 }
1122}
1123
1124impl RemoteSource for SourceDist {
1125 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1126 match self {
1127 Self::Registry(dist) => dist.filename(),
1128 Self::DirectUrl(dist) => dist.filename(),
1129 Self::Git(dist) => dist.filename(),
1130 Self::Path(dist) => dist.filename(),
1131 Self::Directory(dist) => dist.filename(),
1132 }
1133 }
1134
1135 fn size(&self) -> Option<u64> {
1136 match self {
1137 Self::Registry(dist) => dist.size(),
1138 Self::DirectUrl(dist) => dist.size(),
1139 Self::Git(dist) => dist.size(),
1140 Self::Path(dist) => dist.size(),
1141 Self::Directory(dist) => dist.size(),
1142 }
1143 }
1144}
1145
1146impl RemoteSource for BuiltDist {
1147 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1148 match self {
1149 Self::Registry(dist) => dist.filename(),
1150 Self::DirectUrl(dist) => dist.filename(),
1151 Self::Path(dist) => dist.filename(),
1152 }
1153 }
1154
1155 fn size(&self) -> Option<u64> {
1156 match self {
1157 Self::Registry(dist) => dist.size(),
1158 Self::DirectUrl(dist) => dist.size(),
1159 Self::Path(dist) => dist.size(),
1160 }
1161 }
1162}
1163
1164impl RemoteSource for Dist {
1165 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1166 match self {
1167 Self::Built(dist) => dist.filename(),
1168 Self::Source(dist) => dist.filename(),
1169 }
1170 }
1171
1172 fn size(&self) -> Option<u64> {
1173 match self {
1174 Self::Built(dist) => dist.size(),
1175 Self::Source(dist) => dist.size(),
1176 }
1177 }
1178}
1179
1180impl Identifier for DisplaySafeUrl {
1181 fn distribution_id(&self) -> DistributionId {
1182 DistributionId::Url(uv_cache_key::CanonicalUrl::new(self))
1183 }
1184
1185 fn resource_id(&self) -> ResourceId {
1186 ResourceId::Url(uv_cache_key::RepositoryUrl::new(self))
1187 }
1188}
1189
1190impl Identifier for File {
1191 fn distribution_id(&self) -> DistributionId {
1192 self.hashes
1193 .first()
1194 .cloned()
1195 .map(DistributionId::Digest)
1196 .unwrap_or_else(|| self.url.distribution_id())
1197 }
1198
1199 fn resource_id(&self) -> ResourceId {
1200 self.hashes
1201 .first()
1202 .cloned()
1203 .map(ResourceId::Digest)
1204 .unwrap_or_else(|| self.url.resource_id())
1205 }
1206}
1207
1208impl Identifier for Path {
1209 fn distribution_id(&self) -> DistributionId {
1210 DistributionId::PathBuf(self.to_path_buf())
1211 }
1212
1213 fn resource_id(&self) -> ResourceId {
1214 ResourceId::PathBuf(self.to_path_buf())
1215 }
1216}
1217
1218impl Identifier for FileLocation {
1219 fn distribution_id(&self) -> DistributionId {
1220 match self {
1221 Self::RelativeUrl(base, url) => {
1222 DistributionId::RelativeUrl(base.to_string(), url.to_string())
1223 }
1224 Self::AbsoluteUrl(url) => DistributionId::AbsoluteUrl(url.to_string()),
1225 }
1226 }
1227
1228 fn resource_id(&self) -> ResourceId {
1229 match self {
1230 Self::RelativeUrl(base, url) => {
1231 ResourceId::RelativeUrl(base.to_string(), url.to_string())
1232 }
1233 Self::AbsoluteUrl(url) => ResourceId::AbsoluteUrl(url.to_string()),
1234 }
1235 }
1236}
1237
1238impl Identifier for RegistryBuiltWheel {
1239 fn distribution_id(&self) -> DistributionId {
1240 self.file.distribution_id()
1241 }
1242
1243 fn resource_id(&self) -> ResourceId {
1244 self.file.resource_id()
1245 }
1246}
1247
1248impl Identifier for RegistryBuiltDist {
1249 fn distribution_id(&self) -> DistributionId {
1250 self.best_wheel().distribution_id()
1251 }
1252
1253 fn resource_id(&self) -> ResourceId {
1254 self.best_wheel().resource_id()
1255 }
1256}
1257
1258impl Identifier for RegistrySourceDist {
1259 fn distribution_id(&self) -> DistributionId {
1260 self.file.distribution_id()
1261 }
1262
1263 fn resource_id(&self) -> ResourceId {
1264 self.file.resource_id()
1265 }
1266}
1267
1268impl Identifier for DirectUrlBuiltDist {
1269 fn distribution_id(&self) -> DistributionId {
1270 self.url.distribution_id()
1271 }
1272
1273 fn resource_id(&self) -> ResourceId {
1274 self.url.resource_id()
1275 }
1276}
1277
1278impl Identifier for DirectUrlSourceDist {
1279 fn distribution_id(&self) -> DistributionId {
1280 self.url.distribution_id()
1281 }
1282
1283 fn resource_id(&self) -> ResourceId {
1284 self.url.resource_id()
1285 }
1286}
1287
1288impl Identifier for PathBuiltDist {
1289 fn distribution_id(&self) -> DistributionId {
1290 self.url.distribution_id()
1291 }
1292
1293 fn resource_id(&self) -> ResourceId {
1294 self.url.resource_id()
1295 }
1296}
1297
1298impl Identifier for PathSourceDist {
1299 fn distribution_id(&self) -> DistributionId {
1300 self.url.distribution_id()
1301 }
1302
1303 fn resource_id(&self) -> ResourceId {
1304 self.url.resource_id()
1305 }
1306}
1307
1308impl Identifier for DirectorySourceDist {
1309 fn distribution_id(&self) -> DistributionId {
1310 self.url.distribution_id()
1311 }
1312
1313 fn resource_id(&self) -> ResourceId {
1314 self.url.resource_id()
1315 }
1316}
1317
1318impl Identifier for GitSourceDist {
1319 fn distribution_id(&self) -> DistributionId {
1320 self.url.distribution_id()
1321 }
1322
1323 fn resource_id(&self) -> ResourceId {
1324 self.url.resource_id()
1325 }
1326}
1327
1328impl Identifier for SourceDist {
1329 fn distribution_id(&self) -> DistributionId {
1330 match self {
1331 Self::Registry(dist) => dist.distribution_id(),
1332 Self::DirectUrl(dist) => dist.distribution_id(),
1333 Self::Git(dist) => dist.distribution_id(),
1334 Self::Path(dist) => dist.distribution_id(),
1335 Self::Directory(dist) => dist.distribution_id(),
1336 }
1337 }
1338
1339 fn resource_id(&self) -> ResourceId {
1340 match self {
1341 Self::Registry(dist) => dist.resource_id(),
1342 Self::DirectUrl(dist) => dist.resource_id(),
1343 Self::Git(dist) => dist.resource_id(),
1344 Self::Path(dist) => dist.resource_id(),
1345 Self::Directory(dist) => dist.resource_id(),
1346 }
1347 }
1348}
1349
1350impl Identifier for BuiltDist {
1351 fn distribution_id(&self) -> DistributionId {
1352 match self {
1353 Self::Registry(dist) => dist.distribution_id(),
1354 Self::DirectUrl(dist) => dist.distribution_id(),
1355 Self::Path(dist) => dist.distribution_id(),
1356 }
1357 }
1358
1359 fn resource_id(&self) -> ResourceId {
1360 match self {
1361 Self::Registry(dist) => dist.resource_id(),
1362 Self::DirectUrl(dist) => dist.resource_id(),
1363 Self::Path(dist) => dist.resource_id(),
1364 }
1365 }
1366}
1367
1368impl Identifier for InstalledDist {
1369 fn distribution_id(&self) -> DistributionId {
1370 self.install_path().distribution_id()
1371 }
1372
1373 fn resource_id(&self) -> ResourceId {
1374 self.install_path().resource_id()
1375 }
1376}
1377
1378impl Identifier for Dist {
1379 fn distribution_id(&self) -> DistributionId {
1380 match self {
1381 Self::Built(dist) => dist.distribution_id(),
1382 Self::Source(dist) => dist.distribution_id(),
1383 }
1384 }
1385
1386 fn resource_id(&self) -> ResourceId {
1387 match self {
1388 Self::Built(dist) => dist.resource_id(),
1389 Self::Source(dist) => dist.resource_id(),
1390 }
1391 }
1392}
1393
1394impl Identifier for DirectSourceUrl<'_> {
1395 fn distribution_id(&self) -> DistributionId {
1396 self.url.distribution_id()
1397 }
1398
1399 fn resource_id(&self) -> ResourceId {
1400 self.url.resource_id()
1401 }
1402}
1403
1404impl Identifier for GitSourceUrl<'_> {
1405 fn distribution_id(&self) -> DistributionId {
1406 self.url.distribution_id()
1407 }
1408
1409 fn resource_id(&self) -> ResourceId {
1410 self.url.resource_id()
1411 }
1412}
1413
1414impl Identifier for PathSourceUrl<'_> {
1415 fn distribution_id(&self) -> DistributionId {
1416 self.url.distribution_id()
1417 }
1418
1419 fn resource_id(&self) -> ResourceId {
1420 self.url.resource_id()
1421 }
1422}
1423
1424impl Identifier for DirectorySourceUrl<'_> {
1425 fn distribution_id(&self) -> DistributionId {
1426 self.url.distribution_id()
1427 }
1428
1429 fn resource_id(&self) -> ResourceId {
1430 self.url.resource_id()
1431 }
1432}
1433
1434impl Identifier for SourceUrl<'_> {
1435 fn distribution_id(&self) -> DistributionId {
1436 match self {
1437 Self::Direct(url) => url.distribution_id(),
1438 Self::Git(url) => url.distribution_id(),
1439 Self::Path(url) => url.distribution_id(),
1440 Self::Directory(url) => url.distribution_id(),
1441 }
1442 }
1443
1444 fn resource_id(&self) -> ResourceId {
1445 match self {
1446 Self::Direct(url) => url.resource_id(),
1447 Self::Git(url) => url.resource_id(),
1448 Self::Path(url) => url.resource_id(),
1449 Self::Directory(url) => url.resource_id(),
1450 }
1451 }
1452}
1453
1454impl Identifier for BuildableSource<'_> {
1455 fn distribution_id(&self) -> DistributionId {
1456 match self {
1457 Self::Dist(source) => source.distribution_id(),
1458 Self::Url(source) => source.distribution_id(),
1459 }
1460 }
1461
1462 fn resource_id(&self) -> ResourceId {
1463 match self {
1464 Self::Dist(source) => source.resource_id(),
1465 Self::Url(source) => source.resource_id(),
1466 }
1467 }
1468}
1469
1470#[cfg(test)]
1471mod test {
1472 use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString};
1473 use uv_redacted::DisplaySafeUrl;
1474
1475 #[test]
1477 fn dist_size() {
1478 assert!(size_of::<Dist>() <= 200, "{}", size_of::<Dist>());
1479 assert!(size_of::<BuiltDist>() <= 200, "{}", size_of::<BuiltDist>());
1480 assert!(
1481 size_of::<SourceDist>() <= 176,
1482 "{}",
1483 size_of::<SourceDist>()
1484 );
1485 }
1486
1487 #[test]
1488 fn remote_source() {
1489 for url in [
1490 "https://example.com/foo-0.1.0.tar.gz",
1491 "https://example.com/foo-0.1.0.tar.gz#fragment",
1492 "https://example.com/foo-0.1.0.tar.gz?query",
1493 "https://example.com/foo-0.1.0.tar.gz?query#fragment",
1494 "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment",
1495 "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment/3",
1496 ] {
1497 let url = DisplaySafeUrl::parse(url).unwrap();
1498 assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1499 let url = UrlString::from(url.clone());
1500 assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1501 }
1502 }
1503}