1717assert CHANGELOGS_DIR .is_dir ()
1818
1919class Package :
20- # SourceForge Wiki syntax:
21- PATTERN = r"\[([a-zA-Z\ -\:\/\.\_0-9]* )\]\(([^\]\ ]* )\) \| ([^\|]*) \| ([^\|]*)"
22- # Google Code Wiki syntax:
23- PATTERN_OLD = r"\[([a-zA-Z\-\:\/\.\_0-9]*) ([^\]\ ]*)\] \| ([^\|]*) \| ([^\|]*)"
20+ PATTERNS = [
21+ r"\[([\w\ -\:\/\.\_]+ )\]\(([^)]+ )\) \| ([^\|]*) \| ([^\|]*)" , # SourceForge
22+ r"\[([\w\-\:\/\.\_]+) ([^\]\ ]+)\] \| ([^\|]*) \| ([^\|]*)" # Google Code
23+ ]
2424
25- def __init__ (self ):
26- self .name = self .version = self .description = self .url = None
27-
28- def __str__ (self ):
29- return f"{ self .name } { self .version } \r \n { self .description } \r \n Website: { self .url } "
25+ def __init__ (self , text = None ):
26+ self .name = self .url = self .version = self .description = None
27+ if text :
28+ self .from_text (text )
3029
3130 def from_text (self , text ):
32- match = re .match (self .PATTERN_OLD , text ) or re .match (self .PATTERN , text )
33- if not match :
34- raise ValueError ("Text does not match expected pattern: " + text )
35- self .name , self .url , self .version , self .description = match .groups ()
31+ for pattern in self .PATTERNS :
32+ match = re .match (pattern , text )
33+ if match :
34+ self .name , self .url , self .version , self .description = match .groups ()
35+ return
36+ raise ValueError (f"Unrecognized package line format: { text } " )
3637
3738 def to_wiki (self ):
3839 return f" * [{ self .name } ]({ self .url } ) { self .version } ({ self .description } )\r \n "
3940
4041 def upgrade_wiki (self , other ):
41- assert self .name .replace ("-" , "_" ).lower () == other .name .replace ("-" , "_" ).lower ()
4242 return f" * [{ self .name } ]({ self .url } ) { other .version } → { self .version } ({ self .description } )\r \n "
4343
4444class PackageIndex :
45- WINPYTHON_PATTERN = r"\#\# WinPython\-*[0-9b-t]* ([0-9\.a-zA-Z]*)"
46- TOOLS_LINE = "### Tools"
47- PYTHON_PACKAGES_LINE = "### Python packages"
48- WHEELHOUSE_PACKAGES_LINE = "### WheelHouse packages"
49- HEADER_LINE1 = "Name | Version | Description"
50- HEADER_LINE2 = "-----|---------|------------"
45+ HEADERS = {"tools" : "### Tools" , "python" : "### Python packages" , "wheelhouse" : "### WheelHouse packages" }
46+ BLANKS = ["Name | Version | Description" , "-----|---------|------------" , "" , "<details>" , "</details>" ]
5147
5248 def __init__ (self , version , basedir = None , flavor = "" , architecture = 64 ):
5349 self .version = version
5450 self .flavor = flavor
5551 self .basedir = basedir
5652 self .architecture = architecture
57- self .other_packages = {}
58- self .python_packages = {}
59- self .wheelhouse_packages = {}
60- self .from_file (basedir )
61-
62- def from_file (self , basedir ):
63- fname = CHANGELOGS_DIR / f"WinPython{ self .flavor } -{ self .architecture } bit-{ self .version } .md"
64- if not fname .exists ():
65- raise FileNotFoundError (f"Changelog file not found: { fname } " )
66- with open (fname , "r" , encoding = utils .guess_encoding (fname )[0 ]) as fdesc :
67- self .from_text (fdesc .read ())
53+ self .packages = {"tools" : {}, "python" : {}, "wheelhouse" : {}}
54+ self ._load_index ()
6855
69- def from_text (self , text ):
70- version = re .match (self .WINPYTHON_PATTERN + self .flavor , text ).groups ()[0 ]
71- assert version == self .version
72- tools_flag = python_flag = wheelhouse_flag = False
56+ def _load_index (self ):
57+ filename = CHANGELOGS_DIR / f"WinPython{ self .flavor } -{ self .architecture } bit-{ self .version } .md"
58+ if not filename .exists ():
59+ raise FileNotFoundError (f"Changelog not found: { filename } " )
60+
61+ with open (filename , "r" , encoding = utils .guess_encoding (filename )[0 ]) as f :
62+ self ._parse_index (f .read ())
63+
64+ def _parse_index (self , text ):
65+ current = None
7366 for line in text .splitlines ():
74- if line :
75- if line == self .TOOLS_LINE :
76- tools_flag , python_flag , wheelhouse_flag = True , False , False
77- continue
78- elif line == self .PYTHON_PACKAGES_LINE :
79- tools_flag , python_flag , wheelhouse_flag = False , True , False
80- continue
81- elif line == self .WHEELHOUSE_PACKAGES_LINE :
82- tools_flag , python_flag , wheelhouse_flag = False , False , True
83- continue
84- elif line in (self .HEADER_LINE1 , self .HEADER_LINE2 , "<details>" , "</details>" ):
85- continue
86- if tools_flag or python_flag or wheelhouse_flag :
87- package = Package ()
88- package .from_text (line )
89- if tools_flag :
90- self .other_packages [package .name ] = package
91- elif python_flag :
92- self .python_packages [package .name ] = package
93- else :
94- self .wheelhouse_packages [package .name ] = package
95-
96- def diff_package_dicts (old_packages , new_packages ):
67+ if line in self .HEADERS .values ():
68+ current = [k for k , v in self .HEADERS .items () if v == line ][0 ]
69+ continue
70+ if line .strip () in self .BLANKS :
71+ continue
72+ if current :
73+ pkg = Package (line )
74+ self .packages [current ][pkg .name ] = pkg
75+
76+ def compare_packages (old , new ):
9777 """Return difference between package old and package new"""
9878
9979 # wheel replace '-' per '_' in key
100- old = {k .replace ("-" , "_" ).lower (): v for k , v in old_packages .items ()}
101- new = {k .replace ("-" , "_" ).lower (): v for k , v in new_packages .items ()}
102- text = ""
103-
104- if new_keys := sorted (set (new ) - set (old )):
105- text += "New packages:\r \n \r \n " + "" .join (new [k ].to_wiki () for k in new_keys ) + "\r \n "
106-
107- if upgraded := [new [k ].upgrade_wiki (old [k ]) for k in sorted (set (old ) & set (new )) if old [k ].version != new [k ].version ]:
108- text += "Upgraded packages:\r \n \r \n " + f"{ '' .join (upgraded )} " + "\r \n "
109-
110- if removed_keys := sorted (set (old ) - set (new )):
111- text += "Removed packages:\r \n \r \n " + "" .join (old [k ].to_wiki () for k in removed_keys ) + "\r \n "
112- return text
113-
114- def find_closer_version (version1 , basedir = None , flavor = "" , architecture = 64 ):
80+ def normalize (d ): return {k .replace ("-" , "_" ).lower (): v for k , v in d .items ()}
81+ old , new = normalize (old ), normalize (new )
82+ output = ""
83+
84+ added = [new [k ].to_wiki () for k in new if k not in old ]
85+ upgraded = [new [k ].upgrade_wiki (old [k ]) for k in new if k in old and new [k ].version != old [k ].version ]
86+ removed = [old [k ].to_wiki () for k in old if k not in new ]
87+
88+ if added :
89+ output += "New packages:\r \n \r \n " + "" .join (added ) + "\r \n "
90+ if upgraded :
91+ output += "Upgraded packages:\r \n \r \n " + "" .join (upgraded ) + "\r \n "
92+ if removed :
93+ output += "Removed packages:\r \n \r \n " + "" .join (removed ) + "\r \n "
94+ return output
95+
96+ def find_previous_version (target_version , basedir = None , flavor = "" , architecture = 64 ):
11597 """Find version which is the closest to `version`"""
116- builddir = Path (basedir ) / f"bu{ flavor } "
117- pattern = re .compile (rf"WinPython{ flavor } -{ architecture } bit-([0-9\.]*)\.(txt|md)" )
118- versions = [pattern .match (name ).groups ()[0 ] for name in os .listdir (builddir ) if pattern .match (name )]
119-
120- if version1 not in versions :
121- raise ValueError (f"Unknown version { version1 } " )
122-
123- version_below = '0.0.0.0'
124- for v in versions :
125- if version .parse (version_below ) < version .parse (v ) and version .parse (v ) < version .parse (version1 ):
126- version_below = v
98+ build_dir = Path (basedir ) / f"bu{ flavor } "
99+ pattern = re .compile (rf"WinPython{ flavor } -{ architecture } bit-([0-9\.]+)\.(txt|md)" )
100+ versions = [pattern .match (f ).group (1 ) for f in os .listdir (build_dir ) if pattern .match (f )]
101+ versions = [v for v in versions if version .parse (v ) < version .parse (target_version )]
102+ return max (versions , key = version .parse , default = target_version )
127103
128- return version_below if version_below != '0.0.0.0' else version1
104+ def compare_package_indexes (version2 , version1 = None , basedir = None , flavor = "" , flavor1 = None , architecture = 64 ):
105+ version1 = version1 or find_previous_version (version2 , basedir , flavor , architecture )
106+ flavor1 = flavor1 or flavor
129107
130- def compare_package_indexes (version2 , version1 = None , basedir = None , flavor = "" , flavor1 = None ,architecture = 64 ):
131- """Compare two package index Wiki pages"""
132- version1 = version1 if version1 else find_closer_version (version2 , basedir , flavor , architecture )
133- flavor1 = flavor1 if flavor1 else flavor
134108 pi1 = PackageIndex (version1 , basedir , flavor1 , architecture )
135109 pi2 = PackageIndex (version2 , basedir , flavor , architecture )
136110
@@ -140,37 +114,29 @@ def compare_package_indexes(version2, version1=None, basedir=None, flavor="", fl
140114 "<details>\r \n \r \n "
141115 )
142116
143- tools_text = diff_package_dicts (pi1 .other_packages , pi2 .other_packages )
144- if tools_text :
145- text += PackageIndex .TOOLS_LINE + "\r \n \r \n " + tools_text
117+ for key in PackageIndex .HEADERS :
118+ diff = compare_packages (pi1 .packages [key ], pi2 .packages [key ])
119+ if diff :
120+ text += f"{ PackageIndex .HEADERS [key ]} \r \n \r \n { diff } "
146121
147- py_text = diff_package_dicts (pi1 .python_packages , pi2 .python_packages )
148- if py_text :
149- text += PackageIndex .PYTHON_PACKAGES_LINE + "\r \n \r \n " + py_text
122+ return text + "\r \n </details>\r \n * * *\r \n "
150123
151- py_text = diff_package_dicts (pi1 .wheelhouse_packages , pi2 .wheelhouse_packages )
152- if py_text :
153- text += PackageIndex .WHEELHOUSE_PACKAGES_LINE + "\r \n \r \n " + py_text
154-
155- text += "\r \n </details>\r \n * * *\r \n "
156- return text
157-
158- def _copy_all_changelogs (version , basedir , flavor = "" , architecture = 64 ):
124+ def copy_changelogs (version , basedir , flavor = "" , architecture = 64 ):
159125 basever = "." .join (version .split ("." )[:2 ])
160- pattern = re .compile (rf"WinPython{ flavor } -{ architecture } bit-{ basever } ([0-9\.]*)\.(txt|md)" )
161- for name in os .listdir (CHANGELOGS_DIR ):
162- if pattern .match (name ):
163- shutil .copyfile (CHANGELOGS_DIR / name , Path (basedir ) / f"bu{ flavor } " / name )
126+ pattern = re .compile (rf"WinPython{ flavor } -{ architecture } bit-{ basever } [0-9\.]*\.(txt|md)" )
127+ dest = Path (basedir ) / f"bu{ flavor } "
128+ for fname in os .listdir (CHANGELOGS_DIR ):
129+ if pattern .match (fname ):
130+ shutil .copyfile (CHANGELOGS_DIR / fname , dest / fname )
164131
165132def write_changelog (version2 , version1 = None , basedir = None , flavor = "" , architecture = 64 ):
166133 """Write changelog between version1 and version2 of WinPython"""
167- _copy_all_changelogs (version2 , basedir , flavor , architecture )
134+ copy_changelogs (version2 , basedir , flavor , architecture )
168135 print ("comparing_package_indexes" , version2 , basedir , flavor , architecture )
169- changelog_text = compare_package_indexes (version2 , version1 , basedir , flavor , architecture = architecture )
136+ changelog = compare_package_indexes (version2 , version1 , basedir , flavor , architecture = architecture )
170137 output_file = Path (basedir ) / f"bu{ flavor } " / f"WinPython{ flavor } -{ architecture } bit-{ version2 } _History.md"
171-
172- with open (output_file , "w" , encoding = "utf-8" ) as fdesc :
173- fdesc .write (changelog_text )
138+ with open (output_file , "w" , encoding = "utf-8" ) as f :
139+ f .write (changelog )
174140 # Copy to winpython/changelogs
175141 shutil .copyfile (output_file , CHANGELOGS_DIR / output_file .name )
176142
0 commit comments