# -*- coding: utf-8 -*- # # Copyright © 2013 Pierre Raybaut # Licensed under the terms of the MIT License # (see winpython/__init__.py for details) """ WinPython diff script Created on Tue Jan 29 11:56:54 2013 """ from __future__ import print_function, with_statement import os import os.path as osp import re import shutil # Local imports from winpython import utils # pep503 defines normalized package names: www.python.org/dev/peps/pep-0503 def normalize(name): return re.sub(r"[-_.]+", "-", name).lower() CHANGELOGS_DIR = osp.join( osp.dirname(__file__), 'changelogs' ) assert osp.isdir(CHANGELOGS_DIR) class Package(object): # SourceForge Wiki syntax: PATTERN = r'\[([a-zA-Z\-\:\/\.\_0-9]*)\]\(([^\]\ ]*)\) \| ([^\|]*) \| ([^\|]*)' # Google Code Wiki syntax: PATTERN_OLD = r'\[([a-zA-Z\-\:\/\.\_0-9]*) ([^\]\ ]*)\] \| ([^\|]*) \| ([^\|]*)' def __init__(self): self.name = None self.version = None self.description = None self.url = None def __str__(self): text = "%s %s" % (self.name, self.version) text += "\r\n%s\r\nWebsite: %s" % ( self.description, self.url, ) return text def from_text(self, text): try: self.url, self.name, self.version, self.description = re.match( self.PATTERN_OLD, text ).groups() except AttributeError: self.name, self.url, self.version, self.description = re.match( self.PATTERN, text ).groups() def to_wiki(self): return " * [%s](%s) %s (%s)\r\n" % ( self.name, self.url, self.version, self.description, ) def upgrade_wiki(self, other): # wheel replace '-' per '_' in package name assert ( self.name.replace('-', '_').lower() == other.name.replace('-', '_').lower() ) return " * [%s](%s) %s → %s (%s)\r\n" % ( self.name, self.url, other.version, self.version, self.description, ) class PackageIndex(object): WINPYTHON_PATTERN = ( r'\#\# WinPython\-*[0-9b-t]* ([0-9\.a-zA-Z]*)' ) TOOLS_LINE = '### Tools' PYTHON_PACKAGES_LINE = '### Python packages' HEADER_LINE1 = 'Name | Version | Description' HEADER_LINE2 = '-----|---------|------------' def __init__( self, version, basedir=None, flavor='', architecture=64, ): self.version = version self.other_packages = {} self.python_packages = {} self.flavor = flavor self.basedir = basedir self.architecture = architecture self.from_file(basedir) def from_file(self, basedir): # fname = osp.join(basedir, 'build%s' % self.flavor, fname = osp.join( CHANGELOGS_DIR, 'WinPython%s-%sbit-%s.md' % ( self.flavor, self.architecture, self.version, ), ) with open( fname, 'r' ) as fdesc: # python3 doesn't like 'rb' text = fdesc.read() self.from_text(text) def from_text(self, text): version = re.match( self.WINPYTHON_PATTERN + self.flavor, text ).groups()[0] assert version == self.version tools_flag = False python_flag = False for line in text.splitlines(): if line: if line == self.TOOLS_LINE: tools_flag = True continue elif line == self.PYTHON_PACKAGES_LINE: tools_flag = False python_flag = True continue elif line in ( self.HEADER_LINE1, self.HEADER_LINE2, ): continue if tools_flag or python_flag: package = Package() package.from_text(line) if tools_flag: self.other_packages[ package.name ] = package else: self.python_packages[ package.name ] = package def diff_package_dicts(dict1_in, dict2_in): """Return difference between package dict1 and package dict2""" text = "" # wheel replace '-' per '_' in key dict1 = {} dict2 = {} for key in dict1_in: dict1[key.replace('-', '_').lower()] = dict1_in[key] for key in dict2_in: dict2[key.replace('-', '_').lower()] = dict2_in[key] set1, set2 = set(dict1.keys()), set(dict2.keys()) # New packages new = sorted(set2 - set1) if new: text += "New packages:\r\n\r\n" for name in new: package = dict2[name] text += package.to_wiki() text += '\r\n' # Upgraded packages upgraded_list = [] for name in sorted(set1 & set2): package1 = dict1[name] package2 = dict2[name] if package1.version != package2.version: upgraded_list.append( package2.upgrade_wiki(package1) ) if upgraded_list: text += ( "Upgraded packages:\r\n\r\n%s\r\n" % "".join(upgraded_list) ) # Removed packages removed = sorted(set1 - set2) if removed: text += "Removed packages:\r\n\r\n" for name in removed: package = dict1[name] text += package.to_wiki() text += '\r\n' return text def find_closer_version( version1, basedir=None, flavor='', architecture=64 ): """Find version which is the closest to `version`""" builddir = osp.join(basedir, 'bu%s' % flavor) func = lambda name: re.match( r'WinPython%s-%sbit-([0-9\.]*)\.(txt|md)' % (flavor, architecture), name, ) versions = [ func(name).groups()[0] for name in os.listdir(builddir) if func(name) ] try: index = versions.index(version1) except ValueError: raise ValueError("Unknown version %s" % version1) if index == 0: print("No version prior to %s" % version1) index += 1 # we don't want to fail on this return versions[index - 1] def compare_package_indexes( version2, version1=None, basedir=None, flavor='', flavor1=None, architecture=64, ): """Compare two package index Wiki pages""" if version1 is None: version1 = find_closer_version( version2, basedir=basedir, flavor=flavor, architecture=architecture, ) flavor1 = flavor1 if flavor1 is not None else flavor text = '\r\n'.join( [ "## History of changes for WinPython-%sbit %s" % (architecture, version2 + flavor), "", "The following changes were made to WinPython-%sbit" " distribution since version %s." % (architecture, version1 + flavor1), "", "", ] ) pi1 = PackageIndex( version1, basedir=basedir, flavor=flavor1, architecture=architecture, ) pi2 = PackageIndex( version2, basedir=basedir, flavor=flavor, architecture=architecture, ) tools_text = diff_package_dicts( pi1.other_packages, pi2.other_packages ) if tools_text: text += ( PackageIndex.TOOLS_LINE + '\r\n\r\n' + tools_text ) py_text = diff_package_dicts( pi1.python_packages, pi2.python_packages ) if py_text: text += ( PackageIndex.PYTHON_PACKAGES_LINE + '\r\n\r\n' + py_text ) text += '* * *\r\n' return text def _copy_all_changelogs( version, basedir, flavor='', architecture=64 ): basever = '.'.join(version.split('.')[:2]) for name in os.listdir(CHANGELOGS_DIR): if re.match( r'WinPython%s-%sbit-%s([0-9\.]*)\.(txt|md)' % (flavor, architecture, basever), name, ): shutil.copyfile( osp.join(CHANGELOGS_DIR, name), osp.join(basedir, 'bu%s' % flavor, name), ) def write_changelog( version2, version1=None, basedir=None, flavor='', release_level='', architecture=64, ): """Write changelog between version1 and version2 of WinPython""" _copy_all_changelogs( version2, basedir, flavor=flavor, architecture=architecture, ) print( 'comparing_package_indexes', version2, basedir, flavor, architecture, ) text = compare_package_indexes( version2, version1, basedir=basedir, flavor=flavor, architecture=architecture, ) fname = osp.join( basedir, 'bu%s' % flavor, 'WinPython%s-%sbit-%s_History.md' % (flavor, architecture, version2), ) with open( fname, 'w', encoding='utf-8-sig' ) as fdesc: # python 3 need fdesc.write(text) # Copy to winpython/changelogs shutil.copyfile( fname, osp.join(CHANGELOGS_DIR, osp.basename(fname)) ) def test_parse_package_index_wiki( version, basedir=None, flavor='', architecture=64 ): """Parse the package index Wiki page""" pi = PackageIndex( version, basedir=basedir, flavor=flavor, architecture=architecture, ) utils.print_box("WinPython %s:" % pi.version) utils.print_box("Tools:") for package in pi.other_packages.values(): print(package) print('') utils.print_box("Python packages:") for package in pi.python_packages.values(): print(package) print('') def test_compare( basedir, version2, version1, architecture=64 ): print( compare_package_indexes( basedir, version2, version1, architecture=architecture, ) ) if __name__ == '__main__': print( compare_package_indexes( version2='3.7.4.0', version1='3.7.2.0', basedir=r'C:\WinP\bd37', flavor='Zero', flavor1='Zero', architecture=32 )) write_changelog( version2='3.7.4.0', version1='3.7.2.0', basedir=r'C:\WinP\bd37', flavor='Ps2', architecture=64 ) # test_parse_package_index_wiki('2.7.3.3') # print(compare_package_indexes('2.7.3.3', '2.7.3.1')) # write_changelog('2.7.4.1', '2.7.4.0') # write_changelog('3.3.0.0beta2', '3.3.0.0beta1')