feat(cli): Version updates (#4179)

- `version` on its own displays the current version
- `version --bump patch` bumps the patch version
- `version --bump minor` bumps the minor version
- `version --bump major` bumps the major version
- `version 1.2.3` bumps the version directly (existing behaviour)

All flavours of version bump displays a string in the form:
`Bumping version 1.2.3 to 4.5.6`
This commit is contained in:
John-Philip Taylor 2025-08-22 19:50:45 +02:00 committed by Will Lillis
parent fee50ad0ce
commit 10f5a42fd7
2 changed files with 86 additions and 15 deletions

View file

@ -26,7 +26,9 @@ use tree_sitter_cli::{
playground, query,
tags::{self, TagsOptions},
test::{self, TestOptions, TestStats},
test_highlight, test_tags, util, version, wasm,
test_highlight, test_tags, util, version,
version::BumpLevel,
wasm,
};
use tree_sitter_config::Config;
use tree_sitter_highlight::Highlighter;
@ -53,7 +55,7 @@ enum Commands {
Parse(Parse),
/// Run a parser's tests
Test(Test),
/// Increment the version of a grammar
/// Display or increment the version of a grammar
Version(Version),
/// Fuzz a parser
Fuzz(Fuzz),
@ -327,14 +329,26 @@ struct Test {
#[derive(Args)]
#[command(alias = "publish")]
/// Increment the version of a grammar
/// Display or increment the version of a grammar
struct Version {
#[arg(num_args = 1)]
/// The version to bump to
pub version: SemverVersion,
#[arg(
conflicts_with = "bump",
long_help = "\
The version to bump to\n\
\n\
Examples:\n \
tree-sitter version: display the current version\n \
tree-sitter version <version>: bump to specified version\n \
tree-sitter version --bump <level>: automatic bump"
)]
pub version: Option<SemverVersion>,
/// The path to the tree-sitter grammar directory
#[arg(long, short = 'p')]
pub grammar_path: Option<PathBuf>,
/// Automatically bump from the current version
#[arg(long, value_enum, conflicts_with = "version")]
pub bump: Option<BumpLevel>,
}
#[derive(Args)]
@ -1314,7 +1328,7 @@ impl Test {
impl Version {
fn run(self, current_dir: PathBuf) -> Result<()> {
version::Version::new(self.version.to_string(), current_dir).run()
version::Version::new(self.version, current_dir, self.bump).run()
}
}

View file

@ -1,29 +1,86 @@
use std::{fs, path::PathBuf, process::Command};
use anyhow::{anyhow, Context, Result};
use clap::ValueEnum;
use regex::Regex;
use semver::Version as SemverVersion;
use std::cmp::Ordering;
use tree_sitter_loader::TreeSitterJSON;
#[derive(Clone, Copy, Default, ValueEnum)]
pub enum BumpLevel {
#[default]
Patch,
Minor,
Major,
}
pub struct Version {
pub version: String,
pub version: Option<SemverVersion>,
pub current_dir: PathBuf,
pub bump: Option<BumpLevel>,
}
impl Version {
#[must_use]
pub const fn new(version: String, current_dir: PathBuf) -> Self {
pub const fn new(
version: Option<SemverVersion>,
current_dir: PathBuf,
bump: Option<BumpLevel>,
) -> Self {
Self {
version,
current_dir,
bump,
}
}
pub fn run(self) -> Result<()> {
pub fn run(mut self) -> Result<()> {
let tree_sitter_json = self.current_dir.join("tree-sitter.json");
let tree_sitter_json =
serde_json::from_str::<TreeSitterJSON>(&fs::read_to_string(tree_sitter_json)?)?;
let current_version = tree_sitter_json.metadata.version;
self.version = match (self.version.is_some(), self.bump) {
(false, None) => {
println!("Current version: {current_version}");
return Ok(());
}
(true, None) => self.version,
(false, Some(bump)) => {
let mut v = current_version.clone();
match bump {
BumpLevel::Patch => v.patch += 1,
BumpLevel::Minor => {
v.minor += 1;
v.patch = 0;
}
BumpLevel::Major => {
v.major += 1;
v.minor = 0;
v.patch = 0;
}
}
Some(v)
}
(true, Some(_)) => unreachable!(),
};
let new_version = self.version.as_ref().unwrap();
match new_version.cmp(&current_version) {
Ordering::Less => {
eprintln!("Warning: new version is lower than current!");
println!("Reverting version {current_version} to {new_version}");
}
Ordering::Greater => {
println!("Bumping version {current_version} to {new_version}");
}
Ordering::Equal => {
println!("Keeping version {current_version}");
}
}
let is_multigrammar = tree_sitter_json.grammars.len() > 1;
self.update_treesitter_json().with_context(|| {
@ -80,7 +137,7 @@ impl Version {
format!(
"{}{}{}",
&line[..start_quote],
self.version,
self.version.as_ref().unwrap(),
&line[end_quote..]
)
} else {
@ -107,7 +164,7 @@ impl Version {
.lines()
.map(|line| {
if line.starts_with("version =") {
format!("version = \"{}\"", self.version)
format!("version = \"{}\"", self.version.as_ref().unwrap())
} else {
line.to_string()
}
@ -157,7 +214,7 @@ impl Version {
format!(
"{}{}{}",
&line[..start_quote],
self.version,
self.version.as_ref().unwrap(),
&line[end_quote..]
)
} else {
@ -208,7 +265,7 @@ impl Version {
.lines()
.map(|line| {
if line.starts_with("VERSION") {
format!("VERSION := {}", self.version)
format!("VERSION := {}", self.version.as_ref().unwrap())
} else {
line.to_string()
}
@ -230,7 +287,7 @@ impl Version {
let cmake = fs::read_to_string(self.current_dir.join("CMakeLists.txt"))?;
let re = Regex::new(r#"(\s*VERSION\s+)"[0-9]+\.[0-9]+\.[0-9]+""#)?;
let cmake = re.replace(&cmake, format!(r#"$1"{}""#, self.version));
let cmake = re.replace(&cmake, format!(r#"$1"{}""#, self.version.as_ref().unwrap()));
fs::write(self.current_dir.join("CMakeLists.txt"), cmake.as_bytes())?;
@ -248,7 +305,7 @@ impl Version {
.lines()
.map(|line| {
if line.starts_with("version =") {
format!("version = \"{}\"", self.version)
format!("version = \"{}\"", self.version.as_ref().unwrap())
} else {
line.to_string()
}