This picks up @RedSparr0w's work in #1802. 1. The handler for the static badge is moved into its own service with a synchronous handler. Avoiding an async call may make the static badges slightly faster, though it may be worth profiling this if it turns out we want asynchronous static badges in the future. If it doesn't make a performance difference we could make this handler `async` like the others. 2. Most of the custom static-badge logic is in a BaseStaticBadge base class. 3. Rewrite the static Gitter badge to use BaseStaticBadge. 4. A bit of minor cleanup in related functions.
193 lines
4.9 KiB
JavaScript
193 lines
4.9 KiB
JavaScript
'use strict'
|
|
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const SVGO = require('svgo')
|
|
const dot = require('dot')
|
|
const anafanafo = require('anafanafo')
|
|
const isCSSColor = require('is-css-color')
|
|
|
|
// cache templates.
|
|
const templates = {}
|
|
const templateFiles = fs.readdirSync(path.join(__dirname, '..', 'templates'))
|
|
dot.templateSettings.strip = false // Do not strip whitespace.
|
|
templateFiles.forEach(async filename => {
|
|
if (filename[0] === '.') {
|
|
return
|
|
}
|
|
const templateData = fs
|
|
.readFileSync(path.join(__dirname, '..', 'templates', filename))
|
|
.toString()
|
|
const extension = path.extname(filename).slice(1)
|
|
const style = filename.slice(0, -`-template.${extension}`.length)
|
|
// Compile the template. Necessary to always have a working template.
|
|
templates[`${style}-${extension}`] = dot.template(templateData)
|
|
if (extension === 'svg') {
|
|
// Substitute dot code.
|
|
const mapping = new Map()
|
|
let mappingIndex = 1
|
|
const untemplatedSvg = templateData.replace(/{{.*?}}/g, match => {
|
|
// Weird substitution that currently works for all templates.
|
|
const mapKey = `99999990${mappingIndex}.1`
|
|
mappingIndex++
|
|
mapping.set(mapKey, match)
|
|
return mapKey
|
|
})
|
|
|
|
const svgo = new SVGO()
|
|
const { data, error } = await svgo.optimize(untemplatedSvg)
|
|
|
|
if (error !== undefined) {
|
|
console.error(
|
|
`Template ${filename}: ${error}\n` +
|
|
' Generated untemplated SVG:\n' +
|
|
`---\n${untemplatedSvg}---\n`
|
|
)
|
|
return
|
|
}
|
|
|
|
// Substitute dot code back.
|
|
let svg = data
|
|
const unmappedKeys = []
|
|
mapping.forEach((value, key) => {
|
|
let keySubstituted = false
|
|
svg = svg.replace(RegExp(key, 'g'), () => {
|
|
keySubstituted = true
|
|
return value
|
|
})
|
|
if (!keySubstituted) {
|
|
unmappedKeys.push(key)
|
|
}
|
|
})
|
|
if (unmappedKeys.length > 0) {
|
|
console.error(
|
|
`Template ${filename} has unmapped keys ` +
|
|
`${unmappedKeys.join(', ')}.\n` +
|
|
' Generated untemplated SVG:\n' +
|
|
`---\n${untemplatedSvg}\n---\n` +
|
|
' Generated template:\n' +
|
|
`---\n${svg}\n---\n`
|
|
)
|
|
return
|
|
}
|
|
|
|
templates[`${style}-${extension}`] = dot.template(svg)
|
|
}
|
|
})
|
|
|
|
function escapeXml(s) {
|
|
if (s === undefined || typeof s !== 'string') {
|
|
return undefined
|
|
} else {
|
|
return s
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''')
|
|
}
|
|
}
|
|
|
|
function capitalize(s) {
|
|
return s.charAt(0).toUpperCase() + s.slice(1)
|
|
}
|
|
|
|
// check if colorA/B is a colorscheme else check if it's a valid css3 color else return undefined and let the badge assign the default color
|
|
function assignColor(color = '', colorschemeType = 'colorB') {
|
|
if (definedColorschemes[color] !== undefined) {
|
|
return definedColorschemes[color][colorschemeType] || undefined
|
|
} else if (isCSSColor(color)) {
|
|
return color
|
|
} else {
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
const definedColorschemes = require(path.join(__dirname, 'colorscheme.json'))
|
|
|
|
function makeBadge({
|
|
format,
|
|
template,
|
|
text,
|
|
colorscheme,
|
|
colorA,
|
|
colorB,
|
|
logo,
|
|
logoPosition,
|
|
logoWidth,
|
|
links = ['', ''],
|
|
}) {
|
|
// String coercion.
|
|
text = text.map(value => `${value}`)
|
|
|
|
if (format !== 'json') {
|
|
format = 'svg'
|
|
}
|
|
|
|
if (!(`${template}-${format}` in templates)) {
|
|
template = format === 'svg' ? 'flat' : 'default'
|
|
}
|
|
if (template.startsWith('popout')) {
|
|
if (logo) {
|
|
logoPosition =
|
|
logoPosition <= 10 && logoPosition >= -10 ? logoPosition : 0
|
|
logoWidth = +logoWidth || 32
|
|
} else {
|
|
template = template.replace('popout', 'flat')
|
|
}
|
|
}
|
|
if (template === 'social') {
|
|
text[0] = capitalize(text[0])
|
|
} else if (template === 'for-the-badge') {
|
|
text = text.map(value => value.toUpperCase())
|
|
}
|
|
|
|
// colorA/B have a higher priority than colorscheme
|
|
colorA = colorA || colorscheme || undefined
|
|
colorB = colorB || colorscheme || undefined
|
|
colorA = assignColor(colorA, 'colorA')
|
|
colorB = assignColor(colorB, 'colorB')
|
|
|
|
const [left, right] = text
|
|
let leftWidth = (anafanafo(left) / 10) | 0
|
|
// Increase chances of pixel grid alignment.
|
|
if (leftWidth % 2 === 0) {
|
|
leftWidth++
|
|
}
|
|
let rightWidth = (anafanafo(right) / 10) | 0
|
|
// Increase chances of pixel grid alignment.
|
|
if (rightWidth % 2 === 0) {
|
|
rightWidth++
|
|
}
|
|
|
|
logoWidth = +logoWidth || (logo ? 14 : 0)
|
|
|
|
let logoPadding
|
|
if (left.length === 0) {
|
|
logoPadding = 0
|
|
} else {
|
|
logoPadding = logo ? 3 : 0
|
|
}
|
|
|
|
const context = {
|
|
text: [left, right],
|
|
escapedText: text.map(escapeXml),
|
|
widths: [leftWidth + 10 + logoWidth + logoPadding, rightWidth + 10],
|
|
links: links.map(escapeXml),
|
|
logo,
|
|
logoPosition,
|
|
logoWidth,
|
|
logoPadding,
|
|
colorA,
|
|
colorB,
|
|
escapeXml,
|
|
}
|
|
|
|
const templateFn = templates[`${template}-${format}`]
|
|
|
|
// The call to template() can raise an exception.
|
|
return templateFn(context)
|
|
}
|
|
|
|
module.exports = makeBadge
|