Skip to content

Commit 63ab247

Browse files
authored
Allow explicit values with uv version --bump (#16555)
Resolves #16427 This PR updates `uv version --bump` so you can pin the exact number you’re targeting, i.e. `--bump patch=10` or `--bump dev=42`. The command-line interface now parses those `component=value` flags, and the bump logic actually sets the version to the number you asked for.
1 parent 3ccad58 commit 63ab247

File tree

7 files changed

+786
-90
lines changed

7 files changed

+786
-90
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use std::ffi::OsString;
2+
use std::fmt::{self, Display, Formatter};
23
use std::ops::{Deref, DerefMut};
34
use std::path::PathBuf;
45
use std::str::FromStr;
56

67
use anyhow::{Result, anyhow};
7-
use clap::builder::Styles;
8+
use clap::ValueEnum;
89
use clap::builder::styling::{AnsiColor, Effects, Style};
10+
use clap::builder::{PossibleValue, Styles, TypedValueParser, ValueParserFactory};
11+
use clap::error::ErrorKind;
912
use clap::{Args, Parser, Subcommand};
1013

1114
use uv_auth::Service;
@@ -587,8 +590,8 @@ pub struct VersionArgs {
587590
/// Update the project version using the given semantics
588591
///
589592
/// This flag can be passed multiple times.
590-
#[arg(group = "operation", long)]
591-
pub bump: Vec<VersionBump>,
593+
#[arg(group = "operation", long, value_name = "BUMP[=VALUE]")]
594+
pub bump: Vec<VersionBumpSpec>,
592595

593596
/// Don't write a new version to the `pyproject.toml`
594597
///
@@ -698,8 +701,8 @@ pub enum VersionBump {
698701
Dev,
699702
}
700703

701-
impl std::fmt::Display for VersionBump {
702-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
704+
impl Display for VersionBump {
705+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
703706
let string = match self {
704707
Self::Major => "major",
705708
Self::Minor => "minor",
@@ -715,6 +718,110 @@ impl std::fmt::Display for VersionBump {
715718
}
716719
}
717720

721+
impl FromStr for VersionBump {
722+
type Err = String;
723+
724+
fn from_str(value: &str) -> Result<Self, Self::Err> {
725+
match value {
726+
"major" => Ok(Self::Major),
727+
"minor" => Ok(Self::Minor),
728+
"patch" => Ok(Self::Patch),
729+
"stable" => Ok(Self::Stable),
730+
"alpha" => Ok(Self::Alpha),
731+
"beta" => Ok(Self::Beta),
732+
"rc" => Ok(Self::Rc),
733+
"post" => Ok(Self::Post),
734+
"dev" => Ok(Self::Dev),
735+
_ => Err(format!("invalid bump component `{value}`")),
736+
}
737+
}
738+
}
739+
740+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
741+
pub struct VersionBumpSpec {
742+
pub bump: VersionBump,
743+
pub value: Option<u64>,
744+
}
745+
746+
impl Display for VersionBumpSpec {
747+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
748+
match self.value {
749+
Some(value) => write!(f, "{}={value}", self.bump),
750+
None => self.bump.fmt(f),
751+
}
752+
}
753+
}
754+
755+
impl FromStr for VersionBumpSpec {
756+
type Err = String;
757+
758+
fn from_str(input: &str) -> Result<Self, Self::Err> {
759+
let (name, value) = match input.split_once('=') {
760+
Some((name, value)) => (name, Some(value)),
761+
None => (input, None),
762+
};
763+
764+
let bump = name.parse::<VersionBump>()?;
765+
766+
if bump == VersionBump::Stable && value.is_some() {
767+
return Err("`--bump stable` does not accept a value".to_string());
768+
}
769+
770+
let value = match value {
771+
Some("") => {
772+
return Err("`--bump` values cannot be empty".to_string());
773+
}
774+
Some(raw) => Some(
775+
raw.parse::<u64>()
776+
.map_err(|_| format!("invalid numeric value `{raw}` for `--bump {name}`"))?,
777+
),
778+
None => None,
779+
};
780+
781+
Ok(Self { bump, value })
782+
}
783+
}
784+
785+
impl ValueParserFactory for VersionBumpSpec {
786+
type Parser = VersionBumpSpecValueParser;
787+
788+
fn value_parser() -> Self::Parser {
789+
VersionBumpSpecValueParser
790+
}
791+
}
792+
793+
#[derive(Clone, Debug)]
794+
pub struct VersionBumpSpecValueParser;
795+
796+
impl TypedValueParser for VersionBumpSpecValueParser {
797+
type Value = VersionBumpSpec;
798+
799+
fn parse_ref(
800+
&self,
801+
_cmd: &clap::Command,
802+
_arg: Option<&clap::Arg>,
803+
value: &std::ffi::OsStr,
804+
) -> Result<Self::Value, clap::Error> {
805+
let raw = value.to_str().ok_or_else(|| {
806+
clap::Error::raw(
807+
ErrorKind::InvalidUtf8,
808+
"`--bump` values must be valid UTF-8",
809+
)
810+
})?;
811+
812+
VersionBumpSpec::from_str(raw)
813+
.map_err(|message| clap::Error::raw(ErrorKind::InvalidValue, message))
814+
}
815+
816+
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
817+
Some(Box::new(
818+
VersionBump::value_variants()
819+
.iter()
820+
.filter_map(ValueEnum::to_possible_value),
821+
))
822+
}
823+
}
824+
718825
#[derive(Args)]
719826
pub struct SelfNamespace {
720827
#[command(subcommand)]

0 commit comments

Comments
 (0)