From 10f5a42fd79a219cea4b8e6b5732856eadb2fbe5 Mon Sep 17 00:00:00 2001 From: John-Philip Taylor Date: Fri, 22 Aug 2025 19:50:45 +0200 Subject: [PATCH] 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` --- crates/cli/src/main.rs | 26 ++++++++++---- crates/cli/src/version.rs | 75 ++++++++++++++++++++++++++++++++++----- 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index b0931148..2f816ad4 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -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 : bump to specified version\n \ + tree-sitter version --bump : automatic bump" + )] + pub version: Option, /// The path to the tree-sitter grammar directory #[arg(long, short = 'p')] pub grammar_path: Option, + /// Automatically bump from the current version + #[arg(long, value_enum, conflicts_with = "version")] + pub bump: Option, } #[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() } } diff --git a/crates/cli/src/version.rs b/crates/cli/src/version.rs index 11871265..3064ead7 100644 --- a/crates/cli/src/version.rs +++ b/crates/cli/src/version.rs @@ -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, pub current_dir: PathBuf, + pub bump: Option, } impl Version { #[must_use] - pub const fn new(version: String, current_dir: PathBuf) -> Self { + pub const fn new( + version: Option, + current_dir: PathBuf, + bump: Option, + ) -> 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::(&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(¤t_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() }