189 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			189 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			V
		
	
	
| // Copyright (c) 2019-2022 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.')
 | |
| 	}
 | |
| }
 |