We use arrow functions in most places; this enforces it. Passing arrow functions to Mocha is discouraged: https://mochajs.org/#arrow-functions This was a mix of autofixes and hand adjustments.
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,
|
|
}
|