color-name-list
Version:
long list of color names
385 lines (339 loc) • 11.4 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { parseCSVString, objArrToString } from './lib.js';
import { exec } from 'child_process';
// setting
const __dirname = path.dirname(new URL(import.meta.url).pathname);
const baseFolder = __dirname + '/../';
const folderSrc = 'src/';
const folderDist = 'dist/';
const fileNameSrc = 'colornames';
const fileNameBestOfPostfix = '.bestof';
const readmeFileName = 'README.md';
const fileNameShortPostfix = '.short';
const maxShortNameLength = 12;
const csvKeys = ['name', 'hex'];
const bestOfKey = 'good name';
// reads the CSV file contents
const src = fs
.readFileSync(path.normalize(`${baseFolder}${folderSrc}${fileNameSrc}.csv`), 'utf8')
.toString();
const colorsSrc = parseCSVString(src);
// creates JS related files
const JSONExportString = JSON.stringify(
[...colorsSrc.entries].map(
// removes good name attributes
(val) => ({
name: val.name,
hex: val.hex,
})
)
);
const JSONExportStringBestOf = JSON.stringify(
[...colorsSrc.entries]
.filter((val) => val[bestOfKey])
.map(
// removes good name attributes
(val) => ({
name: val.name,
hex: val.hex,
})
)
);
const JSONExportStringShort = JSON.stringify(
[...colorsSrc.entries]
.filter(
// make sure its only one word long
(val) =>
val[bestOfKey] &&
// val.name.split(" ").length === 1 &&
val.name.length < maxShortNameLength + 1
)
.map(
// removes good name attributes
(val) => ({
name: val.name,
hex: val.hex,
})
)
);
// make sure dist folder exists
const distFolder = path.normalize(`${baseFolder}${folderDist}`);
if (!fs.existsSync(distFolder)) {
fs.mkdirSync(distFolder);
}
fs.writeFileSync(path.normalize(`${baseFolder}${folderDist}${fileNameSrc}.json`), JSONExportString);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}${fileNameBestOfPostfix}.json`),
JSONExportStringBestOf
);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}${fileNameShortPostfix}.json`),
JSONExportStringShort
);
// creates a more compact JSON file, where the HEX color serves as an id
const miniJSONExportObj = colorsSrc.entries.reduce((obj, entry) => {
obj[entry.hex.replace('#', '')] = entry.name;
return obj;
}, {});
const miniJSONExportObjBestOf = colorsSrc.entries.reduce((obj, entry) => {
if (entry[bestOfKey]) {
obj[entry.hex.replace('#', '')] = entry.name;
}
return obj;
}, {});
const miniJSONExportObjShort = colorsSrc.entries.reduce((obj, entry) => {
if (
entry[bestOfKey] &&
// entry.name.split(" ").length === 1 &&
entry.name.length < maxShortNameLength + 1
) {
obj[entry.hex.replace('#', '')] = entry.name;
}
return obj;
}, {});
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}.min.json`),
JSON.stringify(miniJSONExportObj)
);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}${fileNameBestOfPostfix}.min.json`),
JSON.stringify(miniJSONExportObjBestOf)
);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}${fileNameShortPostfix}.min.json`),
JSON.stringify(miniJSONExportObjShort)
);
// gets UMD template
const umdTpl = fs.readFileSync(path.normalize(__dirname + '/umd.js.tpl'), 'utf8').toString();
// create UMD
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}.umd.js`),
umdTpl.replace('"{{COLORS}}"', JSONExportString)
);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}${fileNameBestOfPostfix}.umd.js`),
umdTpl.replace('"{{COLORS}}"', JSONExportStringBestOf)
);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}${fileNameShortPostfix}.umd.js`),
umdTpl.replace('"{{COLORS}}"', JSONExportStringShort)
);
// gets ESM template
const esmTpl = fs.readFileSync(path.normalize(__dirname + '/esm.js.tpl'), 'utf8').toString();
// create ESM
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}.esm.js`),
esmTpl.replace('"{{COLORS}}"', JSONExportString)
);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}.esm.mjs`),
esmTpl.replace('"{{COLORS}}"', JSONExportString)
);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}${fileNameBestOfPostfix}.esm.js`),
esmTpl.replace('"{{COLORS}}"', JSONExportStringBestOf)
);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}${fileNameBestOfPostfix}.esm.mjs`),
esmTpl.replace('"{{COLORS}}"', JSONExportStringBestOf)
);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}${fileNameShortPostfix}.esm.js`),
esmTpl.replace('"{{COLORS}}"', JSONExportStringShort)
);
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}${fileNameShortPostfix}.esm.mjs`),
esmTpl.replace('"{{COLORS}}"', JSONExportStringShort)
);
// create foreign formats
// configuration for the file outputs
const outputFormats = {
csv: {
insertBefore: csvKeys.join(',') + '\n',
rowDelimitor: '\n',
insertAfter: '\n',
},
toon: {
// TOON tabular array header for flat objects: [N]{name,hex}:
insertBefore: `[${colorsSrc.entries.length}]{${csvKeys.join(',')}}:\n `,
itemDelimitor: ',',
rowDelimitor: '\n ',
insertAfter: '\n',
},
yaml: {
insertBefore: '-\n ',
beforeValue: '"',
afterValue: '"',
includeKeyPerItem: true,
rowDelimitor: '\n-\n ',
itemDelimitor: '\n ',
insertAfter: '\n',
},
scss: {
insertBefore: '$color-name-list: (',
beforeValue: '"',
afterValue: '"',
insertAfter: ');\n',
itemDelimitor: ':',
rowDelimitor: ',',
},
html: {
insertBefore: `<table><thead><tr><th>${csvKeys.join('</th><th>')}</th></tr><thead><tbody><tr><td>`,
itemDelimitor: '</td><td>',
rowDelimitor: '</td></tr><tr><td>',
insertAfter: `</td></tr></tbody></table>\n`,
},
xml: {
insertBefore: `<?xml version='1.0'?>\n<colors>\n<color>\n<${csvKeys[0]}>`,
itemDelimitor: `</${csvKeys[0]}>\n<${csvKeys[1]}>`,
rowDelimitor: `</${csvKeys[1]}>\n</color>\n<color>\n<${csvKeys[0]}>`,
insertAfter: `</${csvKeys[1]}>\n</color>\n</colors>\n`,
},
};
for (const outputFormat in outputFormats) {
if (outputFormats[outputFormat]) {
let outputString = objArrToString(colorsSrc.entries, csvKeys, outputFormats[outputFormat]);
if (outputFormat === 'html' || outputFormat === 'xml') {
outputString = outputString.replace(/&/g, '&');
}
fs.writeFileSync(
path.normalize(`${baseFolder}${folderDist}${fileNameSrc}.${outputFormat}`),
outputString
);
}
}
// bestOf files
for (const outputFormat in outputFormats) {
if (outputFormats[outputFormat]) {
let outputString = objArrToString(
colorsSrc.entries.filter((val) => val[bestOfKey]),
csvKeys,
outputFormats[outputFormat]
);
if (outputFormat === 'html' || outputFormat === 'xml') {
outputString = outputString.replace(/&/g, '&');
}
fs.writeFileSync(
path.normalize(
`${baseFolder}${folderDist}${fileNameSrc}${fileNameBestOfPostfix}.${outputFormat}`
),
outputString
);
}
}
// short files
for (const outputFormat in outputFormats) {
if (outputFormats[outputFormat]) {
let outputString = objArrToString(
colorsSrc.entries.filter(
(val) =>
val[bestOfKey] &&
// val.name.split(" ").length === 1 &&
val.name.length < maxShortNameLength + 1
),
csvKeys,
outputFormats[outputFormat]
);
if (outputFormat === 'html' || outputFormat === 'xml') {
outputString = outputString.replace(/&/g, '&');
}
fs.writeFileSync(
path.normalize(
`${baseFolder}${folderDist}${fileNameSrc}${fileNameShortPostfix}.${outputFormat}`
),
outputString
);
}
}
// updates the color count in readme file
const readme = fs.readFileSync(path.normalize(`${baseFolder}${readmeFileName}`), 'utf8').toString();
fs.writeFileSync(
path.normalize(`${baseFolder}${readmeFileName}`),
readme
.replace(
// update color count in text
/__\d+__/g,
`__${colorsSrc.entries.length}__`
)
.replace(
// update color count in badge
/\d+-colors-orange/,
`${colorsSrc.entries.length}-colors-orange`
)
.replace(
// update color count in percentage
/__\d+(\.\d+)?%__/,
`__${((colorsSrc.entries.length / (256 * 256 * 256)) * 100).toFixed(2)}%__`
)
.replace(
// update file size (update all occurrences)
/Bundle size note \(≈?\d+(\.\d+)? MB\)__/g,
`Bundle size note (${(
fs.statSync(path.normalize(`${baseFolder}${folderDist}${fileNameSrc}.json`)).size /
1024 /
1024
).toFixed(2)} MB)__`
),
'utf8'
);
// gets SVG template
const svgTpl = fs.readFileSync(path.normalize(__dirname + '/changes.svg.tpl'), 'utf8').toString();
// generates an SVG image with the new colors based on the diff between the last two commits that changed the file
function diffSVG() {
// Get the last two commits that modified the CSV file
exec(
`git log -n 2 --pretty=format:"%H" -- ${baseFolder}${folderSrc}${fileNameSrc}.csv`,
function (err, stdout, stderr) {
if (err) {
console.error('Error getting commit history:', err);
return;
}
const commits = stdout.trim().split('\n');
if (commits.length < 2) {
console.log('Not enough commit history to generate diff');
return;
}
const newerCommit = commits[0];
const olderCommit = commits[1];
// Compare the two commits
exec(
`git diff -w -U0 ${olderCommit} ${newerCommit} -- ${baseFolder}${folderSrc}${fileNameSrc}.csv`,
function (err, stdout, stderr) {
if (err) {
console.error('Error generating diff:', err);
return;
}
const diffTxt = stdout;
if (!/(?<=^[+])[^+].*/gm.test(diffTxt)) {
console.log('No changes detected in the color file');
return;
}
const changes = diffTxt.match(/(?<=^[+])[^+].*/gm).filter((i) => i);
// Filter out the header line if it was included in the diff
const filteredChanges = changes.filter((line) => !line.startsWith('name,hex'));
if (filteredChanges.length === 0) {
console.log('No color changes detected');
return;
}
const svgTxtStr = filteredChanges.reduce((str, change, i) => {
const changeParts = change.split(',');
// Make sure we have both the name and hex color
if (changeParts.length < 2) return str;
return `${str}<text x="40" y="${20 + (i + 1) * 70}" fill="${
changeParts[1]
}">${changeParts[0].replace(/&/g, '&')}</text>`;
}, '');
fs.writeFileSync(
path.normalize(`${baseFolder}changes.svg`),
svgTpl
.replace(/{height}/g, filteredChanges.length * 70 + 80)
.replace(/{items}/g, svgTxtStr)
);
console.log(`Generated SVG showing ${filteredChanges.length} new color(s)`);
}
);
}
);
}
diffSVG();