governance/doc/source/_exts/badges.py
Doug Hellmann 1c98c456b5 add flake8 to the linter job
Turn on flake8 for code style checks, like in our other repos. Fix or
explicitly ignore various issues to allow the linter job to pass.

Change-Id: I7bfd8cf5e9c00dfa83c0393a4307fd54d2113047
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
2017-03-15 17:47:28 -04:00

203 lines
6.6 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Generate badges for the projects
"""
from itertools import chain
from itertools import zip_longest
import os
from PIL import ImageFont
import projects
PADDING = 8
BASE_TAGS_URL = 'https://governance.openstack.org/tc/reference/tags/'
COLOR_SCHEME = {
"brightgreen": "#4c1",
"green": "#97CA00",
"yellow": "#dfb317",
"yellowgreen": "#a4a61d",
"orange": "#fe7d37",
"red": "#e05d44",
"blue": "#007ec6",
"grey": "#555",
"lightgrey": "#9f9f9f",
}
SVG_ROOT = """<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg contentScriptType="text/ecmascript" zoomAndPan="magnify" contentStyleType="text/css"
height="{height}" width="{width}" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg"
version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<title id="os:gov:badges:title">
This is a container for a set of OpenStack badges indicating the status and
features of this project and its repository
</title>
{svg}
</svg>
"""
FLAT_BADGE_TEMPLATE = """<svg id="{left_text}:{right_text}" width="{width}" height="20" x="{svg_x}" y="{svg_y}">
<title>{left_text}:{right_text}</title>
<a target="_blank" xlink:href="{link}">
<linearGradient id="smooth:{left_text}:{right_text}" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="round:{left_text}:{right_text}">
<rect width="{width}" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round:{left_text}:{right_text})">
<rect width="{left_width}" height="20" fill="#555"/>
<rect x="{left_width}" width="{right_width}" height="20" fill="{color}"/>
<rect width="{width}" height="20" fill="url(#smooth:{left_text}:{right_text})"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="{left_x}" y="15" fill="#010101" fill-opacity=".3">{left_text}</text>
<text x="{left_x}" y="14">{left_text}</text>
<text x="{right_x}" y="15" fill="#010101" fill-opacity=".3">{right_text}</text>
<text x="{right_x}" y="14">{right_text}</text>
</g>
</a>
</svg>
"""
def _generate_badge(left_text, right_text, link=None, colorscheme='brightgreen'):
font = ImageFont.truetype('DejaVuSans.ttf', 11)
left_width = font.getsize(left_text)[0] + PADDING
right_width = font.getsize(right_text)[0] + PADDING
width = left_width + right_width
data = {
'link': link or '',
'svg_x': 0,
'svg_y': 0,
'color': COLOR_SCHEME[colorscheme],
'width': width,
'left_width': left_width,
'left_text': left_text,
'left_x': left_width / 2,
'right_width': right_width,
'right_text': right_text,
'right_x': left_width + right_width / 2 - 1,
}
return data
def _generate_tag_badges(tags):
badges = []
badges.append(_generate_badge('project', 'official'))
for tag in tags:
# NOTE(flaper87): will submit other patches to make these
# tags consistent with the rest.
if tag in ['starter-kit:compute', 'tc-approved-release']:
group, tname = 'tc', tag
else:
group, tname = tag.split(':')
link = BASE_TAGS_URL + '%s.html' % tag.replace(':', '_')
badges.append(_generate_badge(group, tname, link, colorscheme='blue'))
return badges
def _organize_badges(badges):
sbadges = sorted(badges, key=lambda badge: badge['width'])
# NOTE(flaper87): 4 is the number of columns
ziped = list(zip_longest(*(iter(sbadges),) * 4))
result = []
for y, group in enumerate(ziped):
result.append([])
col_x = 0
for x, badge in enumerate(group):
# NOTE(flaper87): zip_longest fills the
# empty slots with None. We don't care about
# those.
if badge is None:
break
width_badge = ziped[-1][x]
if width_badge is None:
if len(ziped) > 1:
width_badge = ziped[-2][x]
else:
width_badge = badge
badge['height'] = 20
badge['svg_y'] = (20 + 4) * y
# NOTE(flaper87): 3 is just an extra padding in case there are two badges
# with the same width in the same row
badge['svg_x'] = col_x
col_x += width_badge['width'] + 3
result[y].append(badge)
return result
def _to_svg(badges):
for badge in badges:
yield FLAT_BADGE_TEMPLATE.format(**badge)
def _generate_teams_badges(app):
all_teams = projects.get_project_data()
files = []
badges_dir = os.path.join(app.outdir, 'badges')
if not os.path.exists(badges_dir):
os.mkdir(badges_dir)
filename = os.path.join(badges_dir, 'project-unofficial.svg')
svg_data = _generate_badge('project', 'unofficial', colorscheme='red')
svg = FLAT_BADGE_TEMPLATE.format(**svg_data)
with open(filename, 'w', encoding='utf-8') as f:
f.write(SVG_ROOT.format(height=20, width=106, svg=svg))
files.append(filename)
for team, info in all_teams.items():
app.info('generating team badge for %s' % team)
for name, deliverable in info['deliverables'].items():
tags = info.get('tags', []) + deliverable.get('tags', [])
badges = _organize_badges(_generate_tag_badges(tags))
svg = '\n'.join(_to_svg(chain(*badges)))
root_width = badges[-1][-1]['svg_x'] + badges[-1][-1]['width']
root_height = badges[-1][0]['svg_y'] + badges[-1][0]['height']
for repo in deliverable.get('repos', []):
repo_name = repo.split('/')[1]
filename = os.path.join(badges_dir, '%s.svg' % projects.slugify(repo_name))
with open(filename, 'w') as f:
f.write(SVG_ROOT.format(height=root_height, width=root_width, svg=svg))
files.append(filename)
return files
def setup(app):
app.info('loading badges extension')
_generate_teams_badges(app)