fix_version.py
1 # SPDX-License-Identifier: MIT 2 # 3 # Copyright (c) 2021 The Anvil Extras project team members listed at 4 # https://github.com/anvilistas/anvil-extras/graphs/contributors 5 # 6 # This software is published at https://github.com/anvilistas/anvil-extras 7 8 import argparse 9 import logging 10 import pathlib 11 import re 12 from configparser import ConfigParser 13 14 logging.basicConfig( 15 level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" 16 ) 17 18 19 def read_bumpversion_config(config_file): 20 config = ConfigParser() 21 config.read(config_file) 22 current_version = config.get("bumpversion", "current_version") 23 24 patterns = [] 25 for section in config.sections(): 26 if section.startswith("bumpversion:glob:"): 27 pattern = section[len("bumpversion:glob:") :] 28 patterns.append(pattern) 29 30 glob_patterns = patterns or ["**/*.py"] 31 return current_version, glob_patterns 32 33 34 def update_version_in_file(file_path, current_version): 35 with open(file_path, "r") as file: 36 content = file.read() 37 38 logging.info(f" Updating version in file: {file_path}") 39 40 version_line = f'__version__ = "{current_version}"' 41 42 if re.search(r'^__version__\s*=\s*"[^"]+"', content, flags=re.MULTILINE): 43 logging.info(" Version found") 44 updated_content = re.sub( 45 r'^__version__\s*=\s*"[^"]+"', version_line, content, flags=re.MULTILINE 46 ) 47 48 else: 49 logging.info(" No version found") 50 lines = content.splitlines() 51 insert_position = 0 52 53 for i, line in enumerate(lines): 54 if ( 55 not line 56 or line.startswith("#") 57 or line.startswith("import") 58 or line.startswith("from") 59 ): 60 insert_position = i + 1 61 else: 62 break 63 64 lines.insert(insert_position, version_line + "\n") 65 updated_content = "\n".join(lines) 66 67 if content != updated_content: 68 with open(file_path, "w") as file: 69 file.write(updated_content) 70 logging.info(f"Updated version in {file_path}") 71 72 73 def process_files_or_directory(path, current_version, all_files): 74 path = pathlib.Path(path).resolve() 75 76 if path.is_file(): 77 logging.info(f"Processing file: {path}") 78 if str(path) in all_files: 79 update_version_in_file(str(path), current_version) 80 elif path.is_dir(): 81 logging.info(f"Processing dir: {path}") 82 for file_path in path.glob("**/*.py"): 83 if str(file_path) not in all_files: 84 continue 85 update_version_in_file(str(file_path), current_version) 86 else: 87 logging.info(f"Error: {path} is not a file or directory") 88 89 90 if __name__ == "__main__": 91 parser = argparse.ArgumentParser() 92 parser.add_argument("paths", nargs="*", help="Files or directories to process") 93 parser.add_argument( 94 "--config", 95 default=".bumpversion.cfg", 96 help="Path to the bumpversion configuration file", 97 ) 98 args = parser.parse_args() 99 100 config_file = args.config 101 current_version, glob_patterns = read_bumpversion_config(config_file) 102 # Use the current working directory as the root directory 103 root_dir = pathlib.Path.cwd() 104 105 all_files = [str(p) for pattern in glob_patterns for p in root_dir.glob(pattern)] 106 107 for path in args.paths: 108 process_files_or_directory( 109 path, current_version=current_version, all_files=all_files 110 )