blob: 53928498abaeefb8ac05a27ca6d5d6b17f525307 [file] [log] [blame]
#!/usr/bin/env python
# Provided an index URL and a few project hints page this
# will spit out a shiny HTML 5 W3C compliant releases page.
# Copyright (C) 2012-2013 Luis R. Rodriguez <mcgrof@do-not-panic.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from HTMLParser import HTMLParser
import urllib
import ConfigParser
import re, sys
import os, getopt
from operator import itemgetter
def rel_html_license_verbose():
print '-----------------------------------------------------------------------'
print 'Copyright (C) 2012-2013 Luis R. Rodriguez <mcgrof@do-not-panic.com>'
print ''
print 'This program is free software: you can redistribute it and/or modify'
print 'it under the terms of the GNU Affero General Public License as'
print 'published by the Free Software Foundation, either version 3 of the'
print 'License, or (at your option) any later version.'
print ''
print 'This program is distributed in the hope that it will be useful,'
print 'but WITHOUT ANY WARRANTY; without even the implied warranty of'
print 'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the'
print 'GNU Affero General Public License for more details.'
print ''
print 'You should have received a copy of the GNU Affero General Public License'
print 'along with this program. If not, see <http://www.gnu.org/licenses/>.'
print '-----------------------------------------------------------------------'
def rel_html_license():
return "AGPL"
def rel_html_href():
return '<a href="https://github.com/mcgrof/rel-html">rel-html</a>'
class index_parser(HTMLParser):
"HTML index parser for software releases class."
def parse(self, html):
"Parse the given string 's'."
self.feed(html)
self.close()
def __init__(self, config_file):
HTMLParser.__init__(self)
self.config = ConfigParser.SafeConfigParser()
self.config.read(config_file)
self.rel_html_proj = self.config.get("project", "rel_html_proj")
stable_vers = self.config.get("project", "rel_html_stable_vers").split()
self.rel_html_release_urls = []
urls = self.config.get("project", "rel_html_url_releases").split()
for url in urls:
self.rel_html_release_urls.append(url.strip())
self.rel_html_rels = []
if (self.config.has_option("project", "ignore_signatures")):
self.ignore_signatures = self.config.get("project", "ignore_signatures")
else:
self.ignore_signatures = False
if (self.config.has_option("project", "ignore_changelogs")):
self.ignore_changelogs = self.config.get("project", "ignore_changelogs")
else:
self.ignore_changelogs = False
ver_testing = self.config.get("project", "rel_html_testing_ver")
rel_name_testing = self.rel_html_proj + '-' + ver_testing
tar_testing = rel_name_testing + ".tar.bz2"
s_tarball_testing = rel_name_testing + ".tar.sign"
tmp_changelog_testing = 'ChangeLog-' + ver_testing
tmp_changelog_signed_testing = 'ChangeLog-' + ver_testing + ".sign"
rel_testing = dict(version=self.config.get("project", "rel_html_testing_ver"),
rel=rel_name_testing,
url='',
maintained = True,
longterm = False,
next_rel = False,
tarball = tar_testing,
tarball_exists = False,
ignore_signature = self.ignore_signatures,
signed_tarball = s_tarball_testing,
signed_tarball_exists = False,
changelog = tmp_changelog_testing,
changelog_url = '',
changelog_exists = False,
changelog_required = False,
signed_changelog = tmp_changelog_signed_testing,
signed_changelog_exists = False,
verified = False)
self.rel_html_rels.append(rel_testing)
for ver in stable_vers:
maint = True
if "EOL" in ver:
maint = False
ver = ver.strip(":EOL")
rel_name = self.rel_html_proj + '-' + ver
tar = rel_name + ".tar.bz2"
s_tarball = rel_name + ".tar.sign"
tmp_changelog = 'ChangeLog-' + ver
tmp_changelog_signed = 'ChangeLog-' + ver + ".sign"
# A release is only verified if the file exist,
# its signed, has a respective ChangeLog and
# the ChangeLog is signed
rel = dict(version=ver,
rel=rel_name,
url='',
maintained = maint,
longterm = False,
next_rel = False,
tarball = tar,
tarball_exists = False,
ignore_signature = self.ignore_signatures,
signed_tarball = s_tarball,
signed_tarball_exists = False,
changelog = tmp_changelog,
changelog_url = '',
changelog_exists = False,
changelog_required = not self.ignore_changelogs,
signed_changelog = tmp_changelog_signed,
signed_changelog_exists = False,
verified = False)
self.rel_html_rels.append(rel)
self.next_rel_day = 0
self.next_rel_month = 0
self.next_rel_url = ''
self.next_rel_date = ''
self.rel_license = self.config.get("project", "rel_license")
self.html_title = self.config.get("html", "title")
self.html_nav_dict = ({ 'url': self.config.get("html", "nav_01_url"),
'txt': self.config.get("html", "nav_01_txt") },
{ 'url': self.config.get("html", "nav_02_url"),
'txt': self.config.get("html", "nav_02_txt") },
{ 'url': self.config.get("html", "nav_03_url"),
'txt': self.config.get("html", "nav_03_txt") })
self.html_release_title = self.config.get("html", "release_title")
if (self.config.has_option("html", "release_title_next")):
self.html_release_title_next = self.config.get("html", "release_title_next")
else:
self.html_release_title_next = ''
self.html_about_title = self.config.get("html", "about_title")
self.html_about = self.config.get("html", "about")
self.hyperlinks = []
self.rels = []
self.signed = False
self.changelog = ''
self.signed_changelog = False
def handle_starttag(self, tag, attributes):
"Process a tags and its 'attributes'."
if tag != 'a': pass
for name, value in attributes:
if name != 'href': pass
if (self.next_rel_date != '' and
self.next_rel_date in value and
'tar.bz2' in value):
m = re.match(r'' + self.rel_html_proj + '+' \
+ '\-(?P<DATE_VERSION>' + self.next_rel_date + '+)' \
+ '\-*(?P<EXTRAVERSION>\d*)' \
+ '\-*(?P<RELMOD>\w*)',
value)
rel_specifics = m.groupdict()
rel_name_next = self.rel_html_proj + '-' + rel_specifics['DATE_VERSION']
next_version = rel_specifics['DATE_VERSION']
if (rel_specifics['EXTRAVERSION'] != ''):
rel_name_next = rel_name_next + '-' + rel_specifics['EXTRAVERSION']
next_version = next_version + '-' + rel_specifics['EXTRAVERSION']
if (rel_specifics['RELMOD'] != ''):
rel_name_next = rel_name_next + '-' + rel_specifics['RELMOD']
next_version = next_version + '-' + rel_specifics['RELMOD']
tar_next = rel_name_next + ".tar.bz2"
s_tarball_next = rel_name_next + ".tar.sign"
rel_next = dict(version=next_version,
rel=rel_name_next,
url='',
maintained = True,
longterm = False,
next_rel = True,
tarball = tar_next,
tarball_exists = True,
ignore_signature = self.ignore_signatures,
signed_tarball = s_tarball_next,
signed_tarball_exists = False,
changelog = '',
changelog_url = '',
changelog_exists = False,
changelog_required = False,
signed_changelog = '',
signed_changelog_exists = False,
verified = False)
self.rel_html_rels.append(rel_next)
# Stable release mods
for r in self.rel_html_rels:
if (self.next_rel_date != '' and
self.next_rel_date not in value and
'tar.bz2.sign' not in value and
'tar.bz2' in value and
r.get('version') in value):
m = re.match(r'' + self.rel_html_proj + '-+' \
"v*(?P<VERSION>\w+.)" \
"(?P<PATCHLEVEL>\w+.*)" \
"(?P<SUBLEVEL>\w*)" \
"(?P<EXTRAVERSION>[.-]\w*)" \
"(?P<RELMOD>[-][usnpc]+)", \
value)
if not m:
continue
rel_specifics = m.groupdict()
rel_mod = rel_specifics['RELMOD']
if (rel_mod == ''):
continue
rel_name = r.get('rel') + rel_mod
rel_s = dict(version=r.get('version') + rel_mod,
rel=rel_name,
url='',
maintained = True,
longterm = False,
next_rel = False,
tarball = rel_name + '.tar.bz2',
tarball_exists = True,
ignore_signature = self.ignore_signatures,
signed_tarball = rel_name + '.tar.sign',
signed_tarball_exists = False,
changelog = '',
changelog_url = '',
changelog_exists = False,
changelog_required = False,
signed_changelog = '',
signed_changelog_exists = False,
verified = False)
idx = map(itemgetter('version'), self.rel_html_rels).index(r.get('version'))
self.rel_html_rels.insert(idx+1, rel_s)
break
for r in self.rel_html_rels:
# sys.stdout.write('%s<br>\n' % value)
if r.get('version') in value:
if r.get('tarball') in value:
r['tarball_exists'] = True
r['url'] = value
if "longerm" in value:
r['longterm'] = True
elif r.get('signed_tarball') in value:
r['signed_tarball_exists'] = True
elif (r.get('changelog') == value):
r['changelog_url'] = value
r['changelog_exists'] = True
elif (r.get('signed_changelog') == value):
r['signed_changelog_exists'] = True
def handle_endtag(self, tag):
pass
def handle_data(self, data):
pass
def releases_verified(self):
"Verify releases"
all_verified = True
for r in self.rel_html_rels:
if (not r['tarball_exists']):
all_verified = False
sys.stdout.write('No tarball: %s<br>\n' % r['tarball'])
break
if (not r['ignore_signature']):
if (not r['signed_tarball_exists']):
all_verified = False
sys.stdout.write('No signed tarball: %s<br>\n' % r['signed_tarball'])
break
if (r['changelog_required']):
if (not (r['changelog_exists'])):
all_verified = False
sys.stdout.write('No changelog (%s): %s<br>\n' % (r['changelog'], r['version']))
break
if (not (r['signed_changelog_exists'])):
sys.stdout.write('No signed changelog (%s): %s<br>\n' % (r['signed_changelog'], r['version']))
all_verified = False
break
r['verified'] = True
return all_verified
class largest_num_href_parser(HTMLParser):
"Will take an index page and return the highest numbered link"
def parse(self, html):
"Parse the given string 's'."
self.feed(html)
self.close()
return self.number
def handle_decl(self, decl):
pass
def handle_starttag(self, tag, attributes):
"Process a tags and its 'attributes'."
if tag != 'a': pass
for name, value in attributes:
if name != 'href': pass
if (re.match(r'\d+', value)):
number = re.sub(r'\D', "", value)
if (number > self.number):
self.number = number
def handle_endtag(self, tag):
pass
def handle_data(self, data):
pass
def handle_comment(self, data):
pass
def __init__(self):
HTMLParser.__init__(self)
self.number = 0
# Example full html output parser, this can be used to
# simply read and output a full HTML file, you can modify
# this class to help you modify the contents. We do that
# later.
class html_base(HTMLParser):
"HTML 5 generator from parsed index parser content."
def parse(self, html):
"Parse the given string 's'."
self.feed(html)
self.close()
def handle_decl(self, decl):
sys.stdout.write('<!%s>' % decl)
def handle_starttag(self, tag, attributes):
sys.stdout.write('<%s' % tag)
for name, value in attributes:
sys.stdout.write(' %s="%s"' % (name, value))
sys.stdout.write('>')
def handle_endtag(self, tag):
sys.stdout.write('</%s>' % tag)
def handle_data(self, data):
sys.stdout.write('%s' % data)
def handle_comment(self, data):
sys.stdout.write('<!--%s-->' % data)
def __init__(self):
HTMLParser.__init__(self)
def license_url(license):
if (license == 'GPLv2'):
return "http://www.gnu.org/licenses/gpl-2.0.html"
elif (license == 'ISC'):
return "http://opensource.org/licenses/ISC"
elif (license == 'AGPL'):
return "http://www.gnu.org/licenses/agpl-3.0.html"
else:
return "http://opensource.org/licenses/alphabetical"
class rel_html_gen(HTMLParser):
"HTML 5 generator from parsed index parser content."
def __init__(self, parser):
HTMLParser.__init__(self)
self.parser = parser
self.skip_endtag = False
self.latest_stable = {}
self.next_rels = []
self.next_rel_count = 0
def handle_title(self, tag, attributes):
sys.stdout.write('<%s>%s' % (tag, self.parser.html_title))
def handle_def_start(self, tag, attributes):
sys.stdout.write('<%s' % tag)
for name, value in attributes:
sys.stdout.write(' %s="%s"' % (name, value))
sys.stdout.write('>')
def handle_h1_top(self, tag, attributes):
self.skip_endtag = True
sys.stdout.write('%s</h1>\n' % (self.parser.html_title))
sys.stdout.write('\t\t<nav>\n')
sys.stdout.write('\t\t\t<ul>\n')
for nav in self.parser.html_nav_dict:
sys.stdout.write('\t\t\t\t<li><a href="%s">%s</a></li>\n' % (nav['url'], nav['txt']))
sys.stdout.write('\t\t\t</ul>\n')
sys.stdout.write('\t\t</nav>\n')
def handle_h1_release(self, tag, attributes):
self.skip_endtag = True
sys.stdout.write('%s</h1>\n' % (self.parser.html_release_title))
sys.stdout.write('\t\t\t<table border="0">\n')
count = 0
for r in self.parser.rel_html_rels:
count+=1
if (not r.get('verified')):
continue
if (count == 2):
latest_stable = r
if (r.get('next_rel')):
self.next_rels.append(r)
self.next_rel_count = self.next_rel_count + 1
continue
sys.stdout.write('\t\t\t\t<tr>')
sys.stdout.write('\t\t\t\t<td><a href="%s">%s</a></td>\n' % (r.get('url'), r.get('rel')))
if (not r.get('ignore_signature')):
sys.stdout.write('\t\t\t\t<td><a href="%s">signed</a></td>\n' % (r.get('signed_tarball')))
else:
sys.stdout.write('\t\t\t\t<td></td>\n')
if (r.get('maintained')):
sys.stdout.write('\t\t\t\t<td></td>\n')
else:
sys.stdout.write('\t\t\t\t<td><font color="FF0000">EOL</font></td>\n')
if (not r.get('longterm')):
sys.stdout.write('\t\t\t\t<td></td>\n')
else:
sys.stdout.write('\t\t\t\t<td><font color="00FF00">Longterm</font></td>\n')
if (r.get('changelog_required')):
sys.stdout.write('\t\t\t\t<td><a href="%s">%s</a></td>\n' % (r['changelog'], "ChangeLog"))
else:
sys.stdout.write('\t\t\t\t<td></td>\n')
sys.stdout.write('\t\t\t\t</tr>')
sys.stdout.write('\t\t\t</table>\n')
def handle_h1_release_next(self, tag, attributes):
if (self.next_rel_count <= 0):
return
if (not len(self.next_rels)):
return
sys.stdout.write('%s</h1>\n' % (self.parser.html_release_title_next))
sys.stdout.write('\t\t\t<table border="0">\n')
for r in self.next_rels:
if (not r.get('verified')):
continue
sys.stdout.write('\t\t\t\t<tr>')
sys.stdout.write('\t\t\t\t<td><a href="%s">%s</a></td>\n' % (r.get('url'), r.get('rel')))
if (not r.get('ignore_signature')):
sys.stdout.write('\t\t\t\t<td><a href="%s">signed</a></td>\n' % (r.get('signed_tarball')))
else:
sys.stdout.write('\t\t\t\t<td></td>\n')
if (r.get('maintained')):
sys.stdout.write('\t\t\t\t<td></td>\n')
else:
sys.stdout.write('\t\t\t\t<td><font color="FF0000">EOL</font></td>\n')
if (not r.get('longterm')):
sys.stdout.write('\t\t\t\t<td></td>\n')
else:
sys.stdout.write('\t\t\t\t<td><font color="00FF00">Longterm</font></td>\n')
if (r.get('changelog_required')):
sys.stdout.write('\t\t\t\t<td><a href="%s">%s</a></td>\n' % (r['changelog'], "ChangeLog"))
else:
sys.stdout.write('\t\t\t\t<td></td>\n')
sys.stdout.write('\t\t\t\t</tr>')
sys.stdout.write('\t\t\t</table>\n')
def handle_h1_about(self, tag, attributes):
self.skip_endtag = True
sys.stdout.write('%s</h1>\n' % (self.parser.html_about_title))
sys.stdout.write('<p>%s</p>\n' % (self.parser.html_about))
def handle_h_license(self, tag, attributes):
self.skip_endtag = True
sys.stdout.write('License</h1>\n')
sys.stdout.write('\t\t<p>%s is licensed under the <a href="%s">%s</a>. \n' %
(self.parser.rel_html_proj,
license_url(self.parser.rel_license),
self.parser.rel_license))
sys.stdout.write('This page was generated by %s licensed under the <a href="%s">%s</a></p>\n' %
(rel_html_href(),
license_url(rel_html_license()),
rel_html_license()))
def handle_h1_pass(self, tag, attributes):
pass
def handle_h(self, tag, attributes):
def_run = self.handle_h1_pass
for name, value in attributes:
if (name == 'id'):
if (value == 'top_content'):
def_run = self.handle_h1_top
elif (value == 'release_title'):
def_run = self.handle_h1_release
elif (value == 'release_title_next'):
def_run = self.handle_h1_release_next
elif (value == 'about'):
def_run = self.handle_h1_about
elif (value == 'license'):
def_run = self.handle_h_license
sys.stdout.write('<%s' % tag)
for name, value in attributes:
sys.stdout.write(' %s="%s"' % (name, value))
sys.stdout.write('>')
def_run(tag, attributes)
def parse(self, html):
"Parse the given string 's'."
self.feed(html)
self.close()
def handle_decl(self, decl):
sys.stdout.write('<!%s>' % decl)
def handle_starttag(self, tag, attributes):
self.skip_endtag = False
if (tag == 'title'):
self.handle_title(tag, attributes)
elif (tag in {'h1', 'h2', 'h3'}):
self.handle_h(tag, attributes)
else:
self.handle_def_start(tag, attributes)
def handle_endtag(self, tag):
if (self.skip_endtag):
pass
sys.stdout.write('</%s>' % tag)
def handle_data(self, data):
sys.stdout.write('%s' % data)
def handle_comment(self, data):
sys.stdout.write('<!--%s-->' % data)
def check_file(file_input):
if not os.path.isfile(file_input):
print 'File not found: %(file)s' % { "file": file_input }
usage()
def __get_next_rel_page(url):
r = urllib.urlopen(url)
html = r.read()
num_parser = largest_num_href_parser()
return num_parser.parse(html)
def get_next_rel_url(parser, url):
parser.next_rel_month = __get_next_rel_page(url)
parser.next_rel_day = __get_next_rel_page(url + parser.next_rel_month)
parser.next_rel_url = url + parser.next_rel_month + '/' + parser.next_rel_day
parser.next_rel_date = '2013' + '-' + parser.next_rel_month + '-' + parser.next_rel_day
return parser.next_rel_url
def get_next_rel_html(parser, url):
url = get_next_rel_url(parser, url)
r = urllib.urlopen(url)
return r.read()
def read_rel_html(ver, url):
m = re.match(r"v*(?P<VERSION>\w+.)" \
"(?P<PATCHLEVEL>\w+.*)" \
"(?P<SUBLEVEL>\w*)" \
"(?P<EXTRAVERSION>[.-]\w*)" \
"(?P<RELMOD>[-]\w*)", \
ver)
rel_specifics = m.groupdict()
version = rel_specifics['VERSION'] + \
rel_specifics['PATCHLEVEL'] + \
rel_specifics['SUBLEVEL'] + \
rel_specifics['EXTRAVERSION']
url_rel = url + 'v' + version
f_rel = urllib.urlopen(url_rel)
return f_rel.read()
def main():
config_file = ''
try:
opts, args = getopt.getopt(sys.argv[1:],"hf:")
except getopt.GetoptError, err:
print str(err)
usage()
for o, a in opts:
if o in ("-f"):
check_file(a)
config_file = a
elif o in ("-h", "--help"):
usage()
if len(config_file) == 0:
config_file = 'rel-html.cfg'
parser = index_parser(config_file)
html = ""
for url in parser.rel_html_release_urls:
if url.endswith('stable/'):
for r in parser.rel_html_rels:
html = html + read_rel_html(r.get('version'), url)
elif url.endswith('2013/'):
html = html + get_next_rel_html(parser, url)
else:
f = urllib.urlopen(url)
html = html + f.read()
parser.parse(html)
if (not parser.releases_verified()):
sys.stdout.write("Releases not verified\n")
sys.exit(1)
gen = rel_html_gen(parser)
f = open('html/template.html', 'r')
html = f.read()
gen.parse(html)
def usage():
print ''
print '%(cmd)s' % { "cmd": sys.argv[0] }
print ''
print 'Provided an index URL and a few project hints page this'
print 'will spit out a shiny HTML 5 W3C compliant releases page.'
print ''
rel_html_license_verbose()
print ''
print 'This program can be run without arguments or with a project file passed'
print 'as an argument. If no arguments are given it will assume you have the'
print 'file rel-html.cfg present on your current directory.'
print ''
print 'Usage: %(cmd)s [ -f rel-html.cfg ]' % { "cmd": sys.argv[0] }
sys.exit(2)
if __name__ == "__main__":
main()