diff options
Diffstat (limited to 'source')
-rw-r--r-- | source/package_manifest.py | 82 | ||||
-rw-r--r-- | source/release_notes.py | 181 |
2 files changed, 263 insertions, 0 deletions
diff --git a/source/package_manifest.py b/source/package_manifest.py new file mode 100644 index 0000000..47c5bef --- /dev/null +++ b/source/package_manifest.py @@ -0,0 +1,82 @@ +import json +from collections import OrderedDict +from urllib.request import urlopen +from pprint import pprint + +ARCHITECTURE = [ 'linux-64', 'osx-64'] +METAPACKAGES = [('stsci', '1.0.0'), + ('stsci-data-analysis', '1.0.0'), + ('stsci-hst', '1.0.0'), + ('stsci-jwst', '1.0.0')] +REPODATA_URL='http://ssb.stsci.edu/astroconda/{arch}/repodata.json' +#REPODATA_URL='http://ssb.stsci.edu/conda-dev/{arch}/repodata.json' + +MESSAGE = """ +Packaging reference key: + +:: + + [package]-[version]-[glob]_[build_number] + ^Name ^Version ^ ^Conda package revision + | + npXXpyYY + ^ ^ + | | + | Compiled for Python version + | + Compiled for NumPY version + + +""" + +def get_repodata(architecture): + """ Retrieves repository data but strips the info key (there's never anything there) + """ + url = REPODATA_URL.format(arch=architecture) + with urlopen(url) as response: + data = OrderedDict(json.loads(response.read().decode())['packages']) + + return data + + +if __name__ == '__main__': + + python_versions = dict( + py27='2.7', + py34='3.4', + py35='3.5' + ) + + with open('package_manifest.rst', 'w+') as pfile: + print('Packages', file=pfile) + print('========\n\n', file=pfile) + print('{0}\n\n'.format(MESSAGE), file=pfile) + + repo_data = OrderedDict() + for arch in ARCHITECTURE: + repo_data[arch] = get_repodata(arch) + + metapackages = [] + for mpkg, mpkg_version in METAPACKAGES: + for key, value in repo_data[arch].items(): + if mpkg == repo_data[arch][key]['name']: + if mpkg_version == repo_data[arch][key]['version']: + metapackages.append(('-'.join([value['name'], value['version']]), value['build'], value['depends'])) + + print('{arch} metapackages'.format(arch=arch), file=pfile) + print('------------------------\n\n', file=pfile) + + metapackages = sorted(metapackages, key=lambda k: k[0]) + for name, build, dependencies in metapackages: + print('- **{name} ({python})**\n'.format(name=name, python=build), file=pfile) + for pkg in dependencies: + print(' - {:<20s}\n'.format(pkg), file=pfile) + + print('{arch} packages'.format(arch=arch), file=pfile) + print('------------------------\n\n', file=pfile) + + _packages = sorted(repo_data[arch].values(), key=lambda k: k['name']) + packages = set(['-'.join([d['name'], d['version']]) for d in _packages]) + + for record in sorted(packages): + print('- {name}\n'.format(name=record, header='-' * len(record)), file=pfile) diff --git a/source/release_notes.py b/source/release_notes.py new file mode 100644 index 0000000..3c1b397 --- /dev/null +++ b/source/release_notes.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python +from __future__ import print_function + +import os +import math +from collections import OrderedDict + +try: + from github3 import login + from github3 import GitHubError +except ImportError: + print('github3.py required.') + print('pip install https+git://github.com/sigmavirus24/github3.py') + exit(1) + +try: + import pypandoc +except ImportError: + print('pypandoc required.') + print('conda install -c https://conda.anaconda.org/janschulz pypandoc') + exit(1) + + +header = """ +# Release Notes +""" +title = """ +## {name} +""" +message = """ +### {title} + +*{date}* + +{body} + +""" + + +def explode_newlines(s): + """Normalize newlines and double them up for MD->RST conversion. + """ + tmp = '' + for line in s.splitlines(): + line = line.replace('\r\n', '\n') + line = line + '\n\n' + tmp += line + + return tmp + +def transform_headings(s): + """Trust no one. + Header levels less than or equal to 3 are up-converted. + + 1 = 4 + 2 = 4 + 3 = 5 + 4 = 6 + 5 = 6 + 6 = 6 + + Oh well, sucks right? + """ + delim = '#' + headings = OrderedDict( + h6=delim * 6, + h5=delim * 5, + h4=delim * 4, + h3=delim * 3, + h2=delim * 2, + h1=delim * 1 + ) + output = '' + + for line in s.splitlines(): + length = len(line) + stop = len(headings.keys()) + 1 + + if length < stop: + stop = length + + block = line[:stop] + depth = 0 + for i, ch in enumerate(block, 1): + if ch != '#': break + depth = i + + if depth == 0 or depth > 3: + output += explode_newlines(line) + continue + + #print('length={:<10d} stop={:<10d} depth={:<10d} block={:>20s}'.format(length, stop, depth, block)) + + current = headings['h'+str(depth)] + try: + required = headings['h'+str(math.ceil(depth + depth / depth + 1))] + except KeyError: + required = headings['h'+str(len(headings) - 1)] + + if depth > 0 and depth <= 3: + print("Heading levels 1, 2, and 3 are allocated by this script...") + print("Offending line: '{0}'".format(line)) + print("Automatically converted to: '{0}'".format(required)) + print("(FIX YOUR RELEASE NOTES)\n\n") + + #print('Transformed "{0}" -> "{1}"'.format(current, required)) + line = line.replace(current, required) + output += explode_newlines(line) + + return output + +def read_token(filename): + """For the sake of security, paste your github auth token in a file and reference it using this function + + Expected file format (one-liner): + GITHUB_USER_AUTH_TOKEN_HERE + """ + with open(filename, 'r') as f: + # Obtain the first line in the token file + token = f.readline().rstrip() + + if not token: + return None + + for i, ch in enumerate(token): + if not ch.isalnum(): + raise ValueError('Invalid token: Non-alphanumeric character detected (Value: "{0}", ASCII: {1}, Index: {2}) '.format(ch, ord(ch), i)) + + return token + + +def pull_release_notes(tmpfile, outfile): + with open(tmpfile, 'w+') as mdout: + # Write header (main title) + print(header, file=mdout) + + # Sort repositories inline alphabetically, because otherwise its seemingly random order + for repository in sorted(list(org.repositories()), key=lambda k: k.as_dict()['name']): + repo = repository.as_dict() + repo_name = repo['name'].upper() + + # Ignore repositories without release notes + releases = list(repository.releases()) + if not releases: + continue + + # Write repository title + print(title.format(name=repo_name), file=mdout) + + for note in repository.releases(): + # RST is EXTREMELY particular about newlines. + body = explode_newlines(note.body) + body = transform_headings(note.body) + # Write release notes structure + print(message.format(title=note.name, + date=note.published_at, + body=body), file=mdout) + + +if __name__ == '__main__': + owner = 'spacetelescope' + tmpfile = 'release_notes.md' + outfile = 'release_notes.rst' + + gh = login(token=read_token('TOKEN')) + org = gh.organization(owner) + + try: + pull_release_notes(tmpfile, outfile) + except GitHubError as e: + print(e) + exit(1) + + if os.path.exists(outfile): + os.unlink(outfile) + + if os.path.exists(tmpfile): + pypandoc.convert(source=tmpfile, + to='rst', + outputfile=outfile) + os.unlink(tmpfile) |