This is consistent with what we're pretty much already doing, and saves us from making the request during code review. These were all autofixed and most of them seem easier to read. Some in the legacy services should be rewritten in more legible forms during refactor (ie using intermediate variables, or using request’s qs option). There are some in helper functions and elsewhere that should get rewritten separately. I don't want to change them in this PR because the changes will get lost in this diff, though we could identify them here and fix them before or just after.
209 lines
5.4 KiB
JavaScript
209 lines
5.4 KiB
JavaScript
'use strict'
|
|
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const SVGO = require('svgo')
|
|
const dot = require('dot')
|
|
const LruCache = require('./lru-cache')
|
|
const isCSSColor = require('is-css-color')
|
|
|
|
// Holds widths of badge keys (left hand side of badge).
|
|
const badgeKeyWidthCache = new LruCache(1000)
|
|
|
|
// 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'))
|
|
|
|
// Inject the measurer to avoid placing any persistent state in this module.
|
|
function makeBadge(
|
|
measurer,
|
|
{
|
|
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 = badgeKeyWidthCache.get(left)
|
|
if (leftWidth === undefined) {
|
|
leftWidth = measurer.widthOf(left) | 0
|
|
// Increase chances of pixel grid alignment.
|
|
if (leftWidth % 2 === 0) {
|
|
leftWidth++
|
|
}
|
|
badgeKeyWidthCache.set(left, leftWidth)
|
|
}
|
|
let rightWidth = measurer.widthOf(right) | 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,
|
|
makeMakeBadgeFn: measurer => data => makeBadge(measurer, data),
|
|
// Expose for testing.
|
|
_badgeKeyWidthCache: badgeKeyWidthCache,
|
|
}
|