cmd: add v bump (#12798)

pull/12802/head
Subhomoy Haldar 2021-12-12 01:47:01 +05:30 committed by GitHub
parent 2ced182816
commit f0969698e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 313 additions and 0 deletions

188
cmd/tools/vbump.v 100644
View File

@ -0,0 +1,188 @@
// Copyright (c) 2019-2021 Subhomoy Haldar. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module main
import flag
import os
import regex
import semver
const (
tool_name = os.file_name(os.executable())
tool_version = '0.0.1'
tool_description = '\n Bump the semantic version of the v.mod and/or specified files.
The first instance of a version number is replaced with the new version.
Additionally, the line affected must contain the word "version" in any
form of capitalization. For instance, the following lines will be
recognized by the heuristic:
tool_version = \'1.2.1\'
version: \'0.2.42\'
VERSION = "1.23.8"
Examples:
Bump the patch version in v.mod if it exists
v bump --patch
Bump the major version in v.mod and vls.v
v bump --major v.mod vls.v
Upgrade the minor version in sample.v only
v bump --minor sample.v
'
semver_query = r'((0)|([1-9]\d*)\.){2}(0)|([1-9]\d*)(\-[\w\d\.\-_]+)?(\+[\w\d\.\-_]+)?'
)
struct Options {
show_help bool
major bool
minor bool
patch bool
}
type ReplacementFunction = fn (re regex.RE, input string, start int, end int) string
fn replace_with_increased_patch_version(re regex.RE, input string, start int, end int) string {
version := semver.from(input[start..end]) or { return input }
return version.increment(.patch).str()
}
fn replace_with_increased_minor_version(re regex.RE, input string, start int, end int) string {
version := semver.from(input[start..end]) or { return input }
return version.increment(.minor).str()
}
fn replace_with_increased_major_version(re regex.RE, input string, start int, end int) string {
version := semver.from(input[start..end]) or { return input }
return version.increment(.major).str()
}
fn get_replacement_function(options Options) ReplacementFunction {
if options.patch {
return replace_with_increased_patch_version
} else if options.minor {
return replace_with_increased_minor_version
} else if options.major {
return replace_with_increased_major_version
}
return replace_with_increased_patch_version
}
fn process_file(input_file string, options Options) {
lines := os.read_lines(input_file) or { panic('Failed to read file: $input_file') }
mut re := regex.regex_opt(semver_query) or { panic('Could not create a RegEx parser.') }
repl_fn := get_replacement_function(options)
mut new_lines := []string{cap: lines.len}
mut replacement_complete := false
for line in lines {
// Copy over the remaining lines normally if the replacement is complete
if replacement_complete {
new_lines << line
continue
}
// Check if replacement is necessary
updated_line := if line.to_lower().contains('version') {
replacement_complete = true
re.replace_by_fn(line, repl_fn)
} else {
line
}
new_lines << updated_line
}
// Add a trailing newline
new_lines << ''
backup_file := input_file + '.cache'
// Remove the backup file if it exists.
os.rm(backup_file) or {}
// Rename the original to the backup.
os.mv(input_file, backup_file) or { panic('Failed to copy file: $input_file') }
// Process the old file and write it back to the original.
os.write_file(input_file, new_lines.join_lines()) or {
panic('Failed to write file: $input_file')
}
// Remove the backup file.
os.rm(backup_file) or {}
if replacement_complete {
println('Bumped version in $input_file')
} else {
println('No changes made in $input_file')
}
}
fn main() {
if os.args.len < 2 {
println('Usage: $tool_name [options] [file1 file2 ...]
$tool_description
Try $tool_name -h for more help...')
exit(1)
}
mut fp := flag.new_flag_parser(os.args)
fp.application(tool_name)
fp.version(tool_version)
fp.description(tool_description)
fp.arguments_description('[file1 file2 ...]')
fp.skip_executable()
options := Options{
show_help: fp.bool('help', `h`, false, 'Show this help text.')
patch: fp.bool('patch', `p`, false, 'Bump the patch version.')
minor: fp.bool('minor', `n`, false, 'Bump the minor version.')
major: fp.bool('major', `m`, false, 'Bump the major version.')
}
if options.show_help {
println(fp.usage())
exit(0)
}
validate_options(options) or { panic(err) }
files := os.args[3..]
if files.len == 0 {
if !os.exists('v.mod') {
println('v.mod does not exist. You can create one using "v init".')
exit(1)
}
process_file('v.mod', options)
}
for input_file in files {
if !os.exists(input_file) {
println('File not found: $input_file')
exit(1)
}
process_file(input_file, options)
}
}
fn validate_options(options Options) ? {
if options.patch && options.major {
return error('Cannot specify both --patch and --major.')
}
if options.patch && options.minor {
return error('Cannot specify both --patch and --minor.')
}
if options.major && options.minor {
return error('Cannot specify both --major and --minor.')
}
if !(options.patch || options.major || options.minor) {
return error('Must specify one of --patch, --major, or --minor.')
}
}

View File

@ -0,0 +1,95 @@
import os
struct BumpTestCase {
file_name string
contents string
line int
expected_patch string
expected_minor string
expected_major string
}
const test_cases = [
BumpTestCase{
file_name: 'v.mod'
contents: "Module {
name: 'Sample'
description: 'Sample project'
version: '1.2.6'
license: 'MIT'
dependencies: []
}
"
line: 3
expected_patch: " version: '1.2.7'"
expected_minor: " version: '1.3.0'"
expected_major: " version: '2.0.0'"
},
BumpTestCase{
file_name: 'random_versions.vv'
contents: "
1.1.2
1.2.5
3.21.73
version = '1.5.1'
"
line: 4
expected_patch: "version = '1.5.2'"
expected_minor: "version = '1.6.0'"
expected_major: "version = '2.0.0'"
},
BumpTestCase{
file_name: 'sample_tool.v'
contents: "// Module comment and copyright information
import os
import flag
const (
tool_name = os.file_name(os.executable())
tool_version = '0.1.33'
)
fn main() {
// stuff
}
"
line: 6
expected_patch: " tool_version = '0.1.34'"
expected_minor: " tool_version = '0.2.0'"
expected_major: " tool_version = '1.0.0'"
},
]
fn run_individual_test(case BumpTestCase) ? {
vexe := @VEXE
temp_dir := os.temp_dir()
test_file := os.join_path_single(temp_dir, case.file_name)
os.rm(test_file) or {}
os.write_file(test_file, case.contents) ?
{
os.execute_or_exit('$vexe bump --patch $test_file')
patch_lines := os.read_lines(test_file) ?
assert patch_lines[case.line] == case.expected_patch
}
{
os.execute_or_exit('$vexe bump --minor $test_file')
minor_lines := os.read_lines(test_file) ?
assert minor_lines[case.line] == case.expected_minor
}
{
os.execute_or_exit('$vexe bump --major $test_file')
major_lines := os.read_lines(test_file) ?
assert major_lines[case.line] == case.expected_major
}
os.rm(test_file) ?
}
fn test_all_bump_cases() {
for case in test_cases {
run_individual_test(case) or { panic(err) }
}
}

View File

@ -59,6 +59,7 @@ const (
'build-examples',
'build-tools',
'build-vbinaries',
'bump',
'check-md',
'complete',
'compress',

View File

@ -0,0 +1,28 @@
Usage: v bump [options] [file1 file2 ...]
Description:
Bump the semantic version of the v.mod and/or specified files.
The first instance of a version number is replaced with the new version.
Additionally, the line affected must contain the word "version" in any
form of capitalization. For instance, the following lines will be
recognized by the heuristic:
tool_version = '1.2.1'
version: '0.2.42'
VERSION = "1.23.8"
Examples:
Bump the patch version in v.mod if it exists
v bump --patch
Bump the major version in v.mod and vls.v
v bump --major v.mod vls.v
Upgrade the minor version in sample.v only
v bump --minor sample.v
Options:
-h, --help Show this help text.
-m, --major Bump the major version.
-n, --minor Bump the minor version.
-p, --patch Bump the patch version.

View File

@ -19,6 +19,7 @@ const (
'build-examples',
'build-tools',
'build-vbinaries',
'bump',
'check-md',
'complete',
'compress',