Skip to content

Commit

Permalink
Use separate arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Sep 27, 2024
1 parent 4cffabe commit b8c39c3
Show file tree
Hide file tree
Showing 11 changed files with 1,943 additions and 438 deletions.
61 changes: 58 additions & 3 deletions crates/distribution-types/src/index_source.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::IndexUrl;
use std::str::FromStr;
use thiserror::Error;

use crate::{IndexUrl, IndexUrlError};

#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
Expand Down Expand Up @@ -69,8 +72,19 @@ pub struct IndexSource {
// Flat,
// }

impl From<IndexUrl> for IndexSource {
fn from(url: IndexUrl) -> Self {
impl IndexSource {
/// Initialize an [`IndexSource`] from a pip-style `--index-url`.
pub fn from_index_url(url: IndexUrl) -> Self {
Self {
url,
name: None,
explicit: false,
default: true,
}
}

/// Initialize an [`IndexSource`] from a pip-style `--extra-index-url`.
pub fn from_extra_index_url(url: IndexUrl) -> Self {
Self {
url,
name: None,
Expand All @@ -79,3 +93,44 @@ impl From<IndexUrl> for IndexSource {
}
}
}

impl FromStr for IndexSource {
type Err = IndexSourceError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
// Determine whether the source is prefixed with a name, as in `name=https://pypi.org/simple`.
if let Some((name, url)) = s.split_once('=') {
if name.is_empty() {
return Err(IndexSourceError::EmptyName);
}

if name.chars().all(char::is_alphanumeric) {
let url = IndexUrl::from_str(url)?;
return Ok(Self {
name: Some(name.to_string()),
url,
explicit: false,
default: false,
});
}
}

// Otherwise, assume the source is a URL.
let url = IndexUrl::from_str(s)?;
Ok(Self {
name: None,
url,
explicit: false,
default: false,
})
}
}

/// An error that can occur when parsing an [`IndexSource`].
#[derive(Error, Debug)]
pub enum IndexSourceError {
#[error(transparent)]
Url(#[from] IndexUrlError),
#[error("Index included a name, but the name was empty")]
EmptyName,
}
77 changes: 42 additions & 35 deletions crates/distribution-types/src/index_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,23 +300,20 @@ impl From<VerbatimUrl> for FlatIndexLocation {
#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct IndexLocations {
index: Option<IndexSource>,
extra_index: Vec<IndexSource>,
indexes: Vec<IndexSource>,
flat_index: Vec<FlatIndexLocation>,
no_index: bool,
}

impl IndexLocations {
/// Determine the index URLs to use for fetching packages.
pub fn new(
index: Option<IndexSource>,
extra_index: Vec<IndexSource>,
indexes: Vec<IndexSource>,
flat_index: Vec<FlatIndexLocation>,
no_index: bool,
) -> Self {
Self {
index,
extra_index,
indexes,
flat_index,
no_index,
}
Expand All @@ -331,14 +328,12 @@ impl IndexLocations {
#[must_use]
pub fn combine(
self,
index: Option<IndexSource>,
extra_index: Vec<IndexSource>,
indexes: Vec<IndexSource>,
flat_index: Vec<FlatIndexLocation>,
no_index: bool,
) -> Self {
Self {
index: self.index.or(index),
extra_index: self.extra_index.into_iter().chain(extra_index).collect(),
indexes: self.indexes.into_iter().chain(indexes).collect(),
flat_index: self.flat_index.into_iter().chain(flat_index).collect(),
no_index: self.no_index || no_index,
}
Expand All @@ -361,10 +356,16 @@ impl<'a> IndexLocations {
if self.no_index {
None
} else {
match self.index.as_ref() {
Some(index) => Some(&index.url),
None => Some(&DEFAULT_INDEX_URL),
}
self.indexes
.iter()
.find_map(|index| {
if !index.default || index.explicit {
None
} else {
Some(&index.url)
}
})
.or_else(|| Some(&DEFAULT_INDEX_URL))
}
}

Expand All @@ -373,7 +374,13 @@ impl<'a> IndexLocations {
if self.no_index {
Either::Left(std::iter::empty())
} else {
Either::Right(self.extra_index.iter().map(|index| &index.url))
Either::Right(self.indexes.iter().filter_map(|index| {
if index.default || index.explicit {
None
} else {
Some(&index.url)
}
}))
}
}

Expand All @@ -400,8 +407,7 @@ impl<'a> IndexLocations {
/// Clone the index locations into a [`IndexUrls`] instance.
pub fn index_urls(&'a self) -> IndexUrls {
IndexUrls {
index: self.index.clone(),
extra_index: self.extra_index.clone(),
indexes: self.indexes.clone(),
no_index: self.no_index,
}
}
Expand All @@ -419,13 +425,12 @@ impl<'a> IndexLocations {
/// From a pip perspective, this type merges `--index-url` and `--extra-index-url`.
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct IndexUrls {
index: Option<IndexSource>,
extra_index: Vec<IndexSource>,
indexes: Vec<IndexSource>,
no_index: bool,
}

impl<'a> IndexUrls {
/// Return the fallback [`IndexUrl`] entry.
/// Return the primary [`IndexUrl`] entry.
///
/// If `--no-index` is set, return `None`.
///
Expand All @@ -434,10 +439,16 @@ impl<'a> IndexUrls {
if self.no_index {
None
} else {
match self.index.as_ref() {
Some(index) => Some(&index.url),
None => Some(&DEFAULT_INDEX_URL),
}
self.indexes
.iter()
.find_map(|index| {
if !index.default || index.explicit {
None
} else {
Some(&index.url)
}
})
.or_else(|| Some(&DEFAULT_INDEX_URL))
}
}

Expand All @@ -446,7 +457,13 @@ impl<'a> IndexUrls {
if self.no_index {
Either::Left(std::iter::empty())
} else {
Either::Right(self.extra_index.iter().map(|index| &index.url))
Either::Right(self.indexes.iter().filter_map(|index| {
if index.default || index.explicit {
None
} else {
Some(&index.url)
}
}))
}
}

Expand All @@ -462,16 +479,6 @@ impl<'a> IndexUrls {
}
}

impl From<IndexLocations> for IndexUrls {
fn from(locations: IndexLocations) -> Self {
Self {
index: locations.index,
extra_index: locations.extra_index,
no_index: locations.no_index,
}
}
}

/// A map of [`IndexUrl`]s to their capabilities.
///
/// For now, we only support a single capability (range requests), and we only store an index if
Expand Down
53 changes: 52 additions & 1 deletion crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use anyhow::{anyhow, Result};
use clap::builder::styling::{AnsiColor, Effects, Style};
use clap::builder::Styles;
use clap::{Args, Parser, Subcommand};
use distribution_types::{FlatIndexLocation, IndexUrl};
use distribution_types::{FlatIndexLocation, IndexSource, IndexUrl};
use pep508_rs::Requirement;
use pypi_types::VerbatimParsedUrl;
use url::Url;
Expand Down Expand Up @@ -779,6 +779,36 @@ fn parse_index_url(input: &str) -> Result<Maybe<IndexUrl>, String> {
}
}

/// Parse a string into an [`IndexSource`], mapping the empty string to `None`.
fn parse_index_source(input: &str) -> Result<Maybe<IndexSource>, String> {
if input.is_empty() {
Ok(Maybe::None)
} else {
match IndexSource::from_str(input) {
Ok(index) => Ok(Maybe::Some(IndexSource {
default: false,
..index
})),
Err(err) => Err(err.to_string()),
}
}
}

/// Parse a string into an [`IndexSource`], mapping the empty string to `None`.
fn parse_default_index_source(input: &str) -> Result<Maybe<IndexSource>, String> {
if input.is_empty() {
Ok(Maybe::None)
} else {
match IndexSource::from_str(input) {
Ok(index) => Ok(Maybe::Some(IndexSource {
default: true,
..index
})),
Err(err) => Err(err.to_string()),
}
}
}

/// Parse a string into an [`Url`], mapping the empty string to `None`.
fn parse_insecure_host(input: &str) -> Result<Maybe<TrustedHost>, String> {
if input.is_empty() {
Expand Down Expand Up @@ -3688,6 +3718,27 @@ pub struct GenerateShellCompletionArgs {
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub struct IndexArgs {
/// The URL of the Python package index (by default: <https://pypi.org/simple>).
///
/// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local
/// directory laid out in the same format.
///
/// The index given by this flag is given lower priority than all other
/// indexes specified via the `--extra-index-url` flag.
#[arg(long, env = "UV_DEFAULT_INDEX", value_parser = parse_default_index_source, help_heading = "Index options")]
pub default_index: Option<Maybe<IndexSource>>,

/// Extra URLs of package indexes to use, in addition to `--index-url`.
///
/// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local
/// directory laid out in the same format.
///
/// All indexes provided via this flag take priority over the index specified by
/// `--index-url` (which defaults to PyPI). When multiple `--extra-index-url` flags are
/// provided, earlier values take priority.
#[arg(long, env = "UV_INDEX", value_delimiter = ' ', value_parser = parse_index_source, help_heading = "Index options")]
pub index: Option<Vec<Maybe<IndexSource>>>,

/// The URL of the Python package index (by default: <https://pypi.org/simple>).
///
/// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local
Expand Down
30 changes: 27 additions & 3 deletions crates/uv-cli/src/options.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use uv_cache::Refresh;
use uv_configuration::ConfigSettings;
use uv_resolver::PrereleaseMode;
use uv_settings::{PipOptions, ResolverInstallerOptions, ResolverOptions};
use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};

use crate::{
BuildOptionsArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs,
Expand Down Expand Up @@ -186,13 +186,21 @@ impl From<ResolverInstallerArgs> for PipOptions {
impl From<IndexArgs> for PipOptions {
fn from(args: IndexArgs) -> Self {
let IndexArgs {
default_index,
index,
index_url,
extra_index_url,
no_index,
find_links,
} = args;

Self {
index: default_index
.and_then(Maybe::into_option)
.map(|default_index| vec![default_index])
.combine(
index.map(|index| index.into_iter().filter_map(Maybe::into_option).collect()),
),
index_url: index_url.and_then(Maybe::into_option),
extra_index_url: extra_index_url.map(|extra_index_urls| {
extra_index_urls
Expand Down Expand Up @@ -242,7 +250,15 @@ pub fn resolver_options(
} = build_args;

ResolverOptions {
index: None,
index: index_args
.default_index
.and_then(Maybe::into_option)
.map(|default_index| vec![default_index])
.combine(
index_args
.index
.map(|index| index.into_iter().filter_map(Maybe::into_option).collect()),
),
index_url: index_args.index_url.and_then(Maybe::into_option),
extra_index_url: index_args.extra_index_url.map(|extra_index_urls| {
extra_index_urls
Expand Down Expand Up @@ -326,8 +342,16 @@ pub fn resolver_installer_options(
no_binary_package,
} = build_args;

let default_index = index_args
.default_index
.and_then(Maybe::into_option)
.map(|default_index| vec![default_index]);
let index = index_args
.index
.map(|index| index.into_iter().filter_map(Maybe::into_option).collect());

ResolverInstallerOptions {
index: None,
index: default_index.combine(index),
index_url: index_args.index_url.and_then(Maybe::into_option),
extra_index_url: index_args.extra_index_url.map(|extra_index_urls| {
extra_index_urls
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/pip/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,10 @@ pub(crate) async fn pip_compile(

// Incorporate any index locations from the provided sources.
let index_locations = index_locations.combine(
index_url.map(IndexSource::from),
extra_index_urls
.into_iter()
.map(IndexSource::from)
.map(IndexSource::from_extra_index_url)
.chain(index_url.map(IndexSource::from_index_url))
.collect(),
find_links,
no_index,
Expand Down
Loading

0 comments on commit b8c39c3

Please sign in to comment.