66from typing import List , Optional , Tuple , Dict
77
88
9+ def get_version_from_changelog (file_path = "CHANGELOG.md" ):
10+ """Extract the most recent version from the changelog file."""
11+ try :
12+ with open (file_path , 'r' ) as file :
13+ for line in file :
14+ match = re .search (r'## \[(.*?)\]' , line )
15+ if match :
16+ return match .group (1 )
17+ except FileNotFoundError :
18+ pass
19+ return None
20+
21+
22+ def add_version (current_version : str , increment_type : str = "patch" ) -> str :
23+ """
24+ Increment the version number according to semantic versioning.
25+
26+ Args:
27+ current_version: The current version string (e.g., "1.2.3")
28+ increment_type: The part of the version to increment ("major", "minor", or "patch")
29+
30+ Returns:
31+ The new version string
32+ """
33+ if not current_version :
34+ return "0.1.0" # Default starting version
35+
36+ # Parse version components
37+ match = re .match (r'^(\d+)\.(\d+)\.(\d+)(-([a-zA-Z0-9.-]+))?(\+([a-zA-Z0-9.-]+))?$' , current_version )
38+ if not match :
39+ raise ValueError (f"Invalid version format: { current_version } . Expected format: X.Y.Z[-prerelease][+build]" )
40+
41+ major , minor , patch = int (match .group (1 )), int (match .group (2 )), int (match .group (3 ))
42+ prerelease = match .group (5 ) if match .group (4 ) else None
43+ build = match .group (7 ) if match .group (6 ) else None
44+
45+ # Increment appropriate component
46+ if increment_type == "major" :
47+ major += 1
48+ minor = 0
49+ patch = 0
50+ prerelease = None # Clear prerelease on major version bump
51+ elif increment_type == "minor" :
52+ minor += 1
53+ patch = 0
54+ prerelease = None # Clear prerelease on minor version bump
55+ elif increment_type == "patch" :
56+ patch += 1
57+ prerelease = None # Clear prerelease on patch version bump
58+ elif increment_type .startswith ("pre" ):
59+ # Handle prerelease versions
60+ if prerelease :
61+ # If it's already a prerelease, try to increment its number
62+ pre_parts = prerelease .split ('.' )
63+ if len (pre_parts ) > 1 and pre_parts [- 1 ].isdigit ():
64+ pre_parts [- 1 ] = str (int (pre_parts [- 1 ]) + 1 )
65+ prerelease = '.' .join (pre_parts )
66+ else :
67+ prerelease = f"{ prerelease } .1"
68+ else :
69+ # Start a new prerelease version
70+ prerelease_type = increment_type [3 :] or "alpha" # Extract alpha/beta/rc or default to alpha
71+ prerelease = f"{ prerelease_type } .1"
72+ else :
73+ raise ValueError (
74+ f"Invalid increment type: { increment_type } . Expected 'major', 'minor', 'patch', or 'pre[type]'" )
75+
76+ # Construct new version
77+ new_version = f"{ major } .{ minor } .{ patch } "
78+ if prerelease :
79+ new_version += f"-{ prerelease } "
80+ if build :
81+ new_version += f"+{ build } "
82+
83+ return new_version
84+
85+
986class ChangelogGenerator :
1087 def __init__ (self ):
11- self .version : str = self ._get_latest_version ()
88+ version = get_version_from_changelog () or "0.1.0"
89+ # print(version)
90+ self .version : str = version
1291 self .changes : Dict [str , List [str ]] = {
1392 "Added" : [],
1493 "Changed" : [],
@@ -18,46 +97,16 @@ def __init__(self):
1897 "Security" : []
1998 }
2099
21- def _get_latest_version (self ) -> str :
22- """Extract the latest version from the changelog file."""
23- try :
24- if not os .path .exists ("CHANGELOG.md" ):
25- return "0.1.0"
26-
27- with open ("CHANGELOG.md" , 'r' , encoding = 'utf-8' ) as f :
28- content = f .read ()
29-
30- # Look for version numbers in the format [X.Y.Z]
31- version_pattern = r'\[(\d+\.\d+\.\d+)\]'
32- versions = re .findall (version_pattern , content )
33-
34- if not versions :
35- return "0.1.0"
36-
37- return versions [0 ] # First match is the latest version
38- except Exception :
39- return "0.1.0"
40-
41- def _increment_version (self , change_types : List [str ]) -> str :
42- """
43- Increment version number based on change types.
44- - Major (1.0.0): Breaking changes (Removed)
45- - Minor (0.1.0): New features (Added)
46- - Patch (0.0.1): Fixes and minor changes
100+ def increment_version (self , increment_type : str = "patch" ):
47101 """
48- major , minor , patch = map (int , self .version .split ('.' ))
49-
50- if "Removed" in change_types :
51- major += 1
52- minor = 0
53- patch = 0
54- elif "Added" in change_types or "Deprecated" in change_types :
55- minor += 1
56- patch = 0
57- else :
58- patch += 1
102+ Increment the version number according to semantic versioning.
59103
60- return f"{ major } .{ minor } .{ patch } "
104+ Args:
105+ increment_type: The part of the version to increment
106+ ("major", "minor", "patch", or "pre[type]")
107+ """
108+ self .version = add_version (self .version , increment_type )
109+ return self .version
61110
62111 def get_git_diff (self , file_path : str , staged : bool = False ) -> str :
63112 """Get git diff for a file."""
@@ -137,10 +186,6 @@ def generate_changelog(self, staged: bool = False) -> str:
137186 change_type = self .analyze_file_changes (file , staged )
138187 self .add_change (change_type , f"Changes in { file } " )
139188
140- # Calculate new version based on changes
141- change_types = [ct for ct , changes in self .changes .items () if changes ]
142- self .version = self ._increment_version (change_types )
143-
144189 # Generate markdown
145190 today = datetime .now ().strftime ("%Y-%m-%d" )
146191 changelog = f"## [{ self .version } ] - { today } \n \n "
@@ -158,8 +203,21 @@ def generate_changelog(self, staged: bool = False) -> str:
158203 print (f"Error executing git command: { e } " )
159204 return ""
160205
161- def update_changelog_file (self , output_file : str = "CHANGELOG.md" , staged : bool = False ):
162- """Update the CHANGELOG.md file."""
206+ def update_changelog_file (self , output_file : str = "CHANGELOG.md" , staged : bool = False ,
207+ increment_type : str = None ):
208+ """
209+ Update the CHANGELOG.md file.
210+
211+ Args:
212+ output_file: Path to the changelog file
213+ staged: Whether to include staged changes only
214+ increment_type: If provided, increment the version before updating
215+ ("major", "minor", "patch", or None to keep current version)
216+ """
217+ # Increment version if requested
218+ if increment_type :
219+ self .increment_version (increment_type )
220+
163221 new_changes = self .generate_changelog (staged )
164222 if not new_changes :
165223 return
@@ -191,7 +249,15 @@ def update_changelog_file(self, output_file: str = "CHANGELOG.md", staged: bool
191249
192250def main ():
193251 generator = ChangelogGenerator ()
194- generator .update_changelog_file (staged = True )
252+
253+ # Check for command-line arguments to determine increment type
254+ import sys
255+ increment_type = "patch" # Default increment
256+ if len (sys .argv ) > 1 :
257+ increment_type = sys .argv [1 ] # Use provided increment type
258+
259+ generator .update_changelog_file (staged = True , increment_type = increment_type )
260+ print (f"Updated changelog to version { generator .version } " )
195261
196262
197263if __name__ == "__main__" :
0 commit comments