color-name-list 12.1.0 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/tasks.json +10 -0
- package/README.md +2 -2
- package/changes.svg +3 -3
- package/dist/colornames.bestof.csv +22 -20
- package/dist/colornames.bestof.esm.js +1 -1
- package/dist/colornames.bestof.esm.mjs +1 -1
- package/dist/colornames.bestof.html +1 -1
- package/dist/colornames.bestof.json +1 -1
- package/dist/colornames.bestof.min.json +1 -1
- package/dist/colornames.bestof.scss +1 -1
- package/dist/colornames.bestof.umd.js +1 -1
- package/dist/colornames.bestof.xml +59 -51
- package/dist/colornames.bestof.yaml +47 -41
- package/dist/colornames.csv +38 -135
- package/dist/colornames.esm.js +1 -1
- package/dist/colornames.esm.mjs +1 -1
- package/dist/colornames.html +1 -1
- package/dist/colornames.json +1 -1
- package/dist/colornames.min.json +1 -1
- package/dist/colornames.scss +1 -1
- package/dist/colornames.short.csv +12 -17
- package/dist/colornames.short.esm.js +1 -1
- package/dist/colornames.short.esm.mjs +1 -1
- package/dist/colornames.short.html +1 -1
- package/dist/colornames.short.json +1 -1
- package/dist/colornames.short.min.json +1 -1
- package/dist/colornames.short.scss +1 -1
- package/dist/colornames.short.umd.js +1 -1
- package/dist/colornames.short.xml +25 -45
- package/dist/colornames.short.yaml +21 -36
- package/dist/colornames.umd.js +1 -1
- package/dist/colornames.xml +69 -457
- package/dist/colornames.yaml +56 -347
- package/dist/history.json +1 -1
- package/package.json +1 -1
- package/scripts/build.js +33 -161
- package/scripts/lib.js +60 -5
- package/scripts/sortSrc.js +4 -5
- package/src/colornames.csv +42 -139
- package/tests/_utils/report.js +76 -0
- package/tests/csv-test-data.js +108 -0
- package/tests/duplicate-allowlist.json +28 -1
- package/tests/duplicate-plurals-allowlist.json +10 -0
- package/tests/duplicates.test.js +135 -40
- package/tests/formats.test.js +9 -8
- package/tests/imports.test.js +12 -14
- package/tests/sorting.test.js +10 -12
- package/tests/validations.test.js +219 -0
package/package.json
CHANGED
package/scripts/build.js
CHANGED
|
@@ -1,22 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { parseCSVString,
|
|
3
|
+
import { parseCSVString, objArrToString } from './lib.js';
|
|
4
4
|
import { exec } from 'child_process';
|
|
5
5
|
|
|
6
|
-
const args = process.argv;
|
|
7
|
-
// treat --testonly / --testOnly the same
|
|
8
|
-
const isTestRun = args.some((arg) => arg.toLowerCase() === '--testonly');
|
|
9
|
-
|
|
10
|
-
// only hex colors with 6 values
|
|
11
|
-
const hexColorValidation = /^#[0-9a-f]{6}$/;
|
|
12
|
-
const errors = [];
|
|
13
|
-
|
|
14
|
-
// spaces regex
|
|
15
|
-
const spacesValidation = /^\s+|\s{2,}|\s$/;
|
|
16
|
-
|
|
17
|
-
// quote regex
|
|
18
|
-
const quoteValidation = /"|'|`/;
|
|
19
|
-
|
|
20
6
|
// setting
|
|
21
7
|
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
22
8
|
const baseFolder = __dirname + '/../';
|
|
@@ -29,7 +15,6 @@ const readmeFileName = 'README.md';
|
|
|
29
15
|
const fileNameShortPostfix = '.short';
|
|
30
16
|
const maxShortNameLength = 12;
|
|
31
17
|
|
|
32
|
-
const sortBy = 'name';
|
|
33
18
|
const csvKeys = ['name', 'hex'];
|
|
34
19
|
const bestOfKey = 'good name';
|
|
35
20
|
|
|
@@ -40,63 +25,6 @@ const src = fs
|
|
|
40
25
|
|
|
41
26
|
const colorsSrc = parseCSVString(src);
|
|
42
27
|
|
|
43
|
-
// sort by sorting criteria
|
|
44
|
-
colorsSrc.entries.sort((a, b) => {
|
|
45
|
-
return a[sortBy].localeCompare(b[sortBy]);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
csvKeys.forEach((key) => {
|
|
49
|
-
// find duplicates
|
|
50
|
-
const dupes = findDuplicates(colorsSrc.values[key]);
|
|
51
|
-
dupes.forEach((dupe) => {
|
|
52
|
-
log(key, dupe, `found a double ${key}`);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// loop hex values for validations
|
|
57
|
-
colorsSrc.values['hex'].forEach((hex) => {
|
|
58
|
-
// validate HEX values
|
|
59
|
-
if (!hexColorValidation.test(hex)) {
|
|
60
|
-
log(
|
|
61
|
-
'hex',
|
|
62
|
-
hex,
|
|
63
|
-
`${hex} is not a valid hex value. (Or to short, we avoid using the hex shorthands, no capital letters)`
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// loop names
|
|
69
|
-
colorsSrc.values['name'].forEach((name) => {
|
|
70
|
-
// check for spaces
|
|
71
|
-
if (spacesValidation.test(name)) {
|
|
72
|
-
log('name', name, `${name} found either a leading or trailing space (or both)`);
|
|
73
|
-
}
|
|
74
|
-
if (quoteValidation.test(name)) {
|
|
75
|
-
log('name', name, `${name} found a quote character, should be an apostrophe ’`);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// loop good name markers
|
|
80
|
-
colorsSrc.values[bestOfKey].forEach((str) => {
|
|
81
|
-
// check for spaces
|
|
82
|
-
if (spacesValidation.test(str)) {
|
|
83
|
-
// Use the actual CSV key so we can resolve the offending entries (names)
|
|
84
|
-
log(bestOfKey, str, `${str} found either a leading or trailing space (or both)`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (!(str == 'x' || str == '')) {
|
|
88
|
-
// Use the actual CSV key so we can resolve the offending entries (names)
|
|
89
|
-
log(bestOfKey, str, `${str} must be a lowercase "x" character or empty`);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
showLog();
|
|
94
|
-
// In test mode we still perform the build so tests can import dist artifacts,
|
|
95
|
-
// but we avoid mutating repository files like README.md or generating the SVG.
|
|
96
|
-
if (isTestRun) {
|
|
97
|
-
console.log('Test mode: skipping README & SVG generation.');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
28
|
// creates JS related files
|
|
101
29
|
const JSONExportString = JSON.stringify(
|
|
102
30
|
[...colorsSrc.entries].map(
|
|
@@ -339,91 +267,37 @@ for (const outputFormat in outputFormats) {
|
|
|
339
267
|
}
|
|
340
268
|
}
|
|
341
269
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
)
|
|
360
|
-
.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
),
|
|
374
|
-
'utf8'
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* outputs the collected logs
|
|
380
|
-
*/
|
|
381
|
-
function showLog() {
|
|
382
|
-
let errorLevel = 0;
|
|
383
|
-
let totalErrors = 0;
|
|
384
|
-
errors.forEach((error, i) => {
|
|
385
|
-
totalErrors = i + 1;
|
|
386
|
-
errorLevel = error.errorLevel || errorLevel;
|
|
387
|
-
console.log(`${error.errorLevel ? '⛔' : '⚠'} ${error.message}`);
|
|
388
|
-
if (Array.isArray(error.entries) && error.entries.length) {
|
|
389
|
-
// Print a concise list of offending names for quick scanning
|
|
390
|
-
const nameList = error.entries
|
|
391
|
-
.map((e) => (e && e.name ? `${e.name}${e.hex ? ` (${e.hex})` : ''}` : ''))
|
|
392
|
-
.filter(Boolean)
|
|
393
|
-
.join(', ');
|
|
394
|
-
if (nameList) {
|
|
395
|
-
console.log(`Offending name(s): ${nameList}`);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
// Keep the JSON dump for full context
|
|
399
|
-
console.log(JSON.stringify(error.entries));
|
|
400
|
-
console.log('*-------------------------*');
|
|
401
|
-
});
|
|
402
|
-
if (errorLevel) {
|
|
403
|
-
throw new Error(`⚠ failed because of the ${totalErrors} error${totalErrors > 1 ? 's' : ''} above ⚠`);
|
|
404
|
-
}
|
|
405
|
-
return totalErrors;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* logs errors and warning
|
|
410
|
-
* @param {string} key key to look for in input
|
|
411
|
-
* @param {string} value value to look for
|
|
412
|
-
* @param {string} message error message
|
|
413
|
-
* @param {Number} errorLevel if any error is set to 1, the program will exit
|
|
414
|
-
*/
|
|
415
|
-
function log(key, value, message, errorLevel = 1) {
|
|
416
|
-
const error = {};
|
|
417
|
-
// looks for the original item that caused the error
|
|
418
|
-
error.entries = colorsSrc.entries.filter((entry) => {
|
|
419
|
-
return entry[key] === value;
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
error.message = message;
|
|
423
|
-
error.errorLevel = errorLevel;
|
|
424
|
-
|
|
425
|
-
errors.push(error);
|
|
426
|
-
}
|
|
270
|
+
// updates the color count in readme file
|
|
271
|
+
const readme = fs.readFileSync(path.normalize(`${baseFolder}${readmeFileName}`), 'utf8').toString();
|
|
272
|
+
fs.writeFileSync(
|
|
273
|
+
path.normalize(`${baseFolder}${readmeFileName}`),
|
|
274
|
+
readme
|
|
275
|
+
.replace(
|
|
276
|
+
// update color count in text
|
|
277
|
+
/__\d+__/g,
|
|
278
|
+
`__${colorsSrc.entries.length}__`
|
|
279
|
+
)
|
|
280
|
+
.replace(
|
|
281
|
+
// update color count in badge
|
|
282
|
+
/\d+-colors-orange/,
|
|
283
|
+
`${colorsSrc.entries.length}-colors-orange`
|
|
284
|
+
)
|
|
285
|
+
.replace(
|
|
286
|
+
// update color count in percentage
|
|
287
|
+
/__\d+(\.\d+)?%__/,
|
|
288
|
+
`__${((colorsSrc.entries.length / (256 * 256 * 256)) * 100).toFixed(2)}%__`
|
|
289
|
+
)
|
|
290
|
+
.replace(
|
|
291
|
+
// update file size
|
|
292
|
+
/\d+(\.\d+)? MB\)__/, // no global to only hit first occurrence
|
|
293
|
+
`${(
|
|
294
|
+
fs.statSync(path.normalize(`${baseFolder}${folderDist}${fileNameSrc}.json`)).size /
|
|
295
|
+
1024 /
|
|
296
|
+
1024
|
|
297
|
+
).toFixed(2)} MB)__`
|
|
298
|
+
),
|
|
299
|
+
'utf8'
|
|
300
|
+
);
|
|
427
301
|
|
|
428
302
|
// gets SVG template
|
|
429
303
|
const svgTpl = fs.readFileSync(path.normalize(__dirname + '/changes.svg.tpl'), 'utf8').toString();
|
|
@@ -497,6 +371,4 @@ function diffSVG() {
|
|
|
497
371
|
);
|
|
498
372
|
}
|
|
499
373
|
|
|
500
|
-
|
|
501
|
-
diffSVG();
|
|
502
|
-
}
|
|
374
|
+
diffSVG();
|
package/scripts/lib.js
CHANGED
|
@@ -131,23 +131,78 @@ export const normalizeNameForDuplicates = (name) => {
|
|
|
131
131
|
* @returns {Array<{norm:string, entries:Array<{name:string, lineNumber?:number}>}>}
|
|
132
132
|
*/
|
|
133
133
|
export const findNearDuplicateNameConflicts = (items, options = {}) => {
|
|
134
|
-
const {
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
const {
|
|
135
|
+
allowlist = [],
|
|
136
|
+
foldPlurals = false,
|
|
137
|
+
pluralAllowlist = [],
|
|
138
|
+
foldStopwords = false,
|
|
139
|
+
stopwords = [],
|
|
140
|
+
} = options;
|
|
141
|
+
|
|
142
|
+
// Precompute stopword set (normalized to lowercase ASCII) when folding is enabled
|
|
143
|
+
const stopSet = foldStopwords
|
|
144
|
+
? new Set(
|
|
145
|
+
(Array.isArray(stopwords) ? stopwords : [])
|
|
146
|
+
.filter((w) => typeof w === 'string' && w.trim().length)
|
|
147
|
+
.map((w) =>
|
|
148
|
+
String(w)
|
|
149
|
+
.toLowerCase()
|
|
150
|
+
.normalize('NFD')
|
|
151
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
: null;
|
|
155
|
+
|
|
156
|
+
// Helper: normalize name using current options (stopword folding if enabled)
|
|
157
|
+
const normalizeForOptions = (name) => {
|
|
158
|
+
const base = String(name)
|
|
159
|
+
.toLowerCase()
|
|
160
|
+
.normalize('NFD')
|
|
161
|
+
.replace(/[\u0300-\u036f]/g, '');
|
|
162
|
+
const tokens = base.match(/[a-z0-9]+/g) || [];
|
|
163
|
+
const filtered = foldStopwords && stopSet && stopSet.size ? tokens.filter((t) => !stopSet.has(t)) : tokens;
|
|
164
|
+
return filtered.length ? filtered.join('') : normalizeNameForDuplicates(name);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Normalize allowlist entries using the same normalization function.
|
|
137
168
|
const allowSet = new Set(
|
|
138
169
|
(Array.isArray(allowlist) ? allowlist : [])
|
|
139
170
|
.filter((v) => typeof v === 'string' && v.trim().length)
|
|
140
|
-
.map((v) =>
|
|
171
|
+
.map((v) => normalizeForOptions(String(v)))
|
|
141
172
|
);
|
|
142
173
|
|
|
143
174
|
const byNorm = new Map();
|
|
144
175
|
for (const item of items) {
|
|
145
176
|
if (!item || typeof item.name !== 'string') continue;
|
|
146
|
-
const norm =
|
|
177
|
+
const norm = normalizeForOptions(item.name);
|
|
147
178
|
if (!byNorm.has(norm)) byNorm.set(norm, []);
|
|
148
179
|
byNorm.get(norm).push({ name: item.name, lineNumber: item.lineNumber });
|
|
149
180
|
}
|
|
150
181
|
|
|
182
|
+
// Optional plural folding: merge keys ending in 's' with their singular if present.
|
|
183
|
+
if (foldPlurals) {
|
|
184
|
+
const pluralAllowSet = new Set(
|
|
185
|
+
(Array.isArray(pluralAllowlist) ? pluralAllowlist : [])
|
|
186
|
+
.filter((v) => typeof v === 'string' && v.trim().length)
|
|
187
|
+
// Use the same normalization as used for keys (respects stopword folding)
|
|
188
|
+
.map((v) => normalizeForOptions(String(v)))
|
|
189
|
+
);
|
|
190
|
+
// We iterate over a snapshot of keys because we'll mutate the map.
|
|
191
|
+
for (const key of Array.from(byNorm.keys())) {
|
|
192
|
+
if (key.length < 4) continue; // avoid over-aggressive folding for tiny words
|
|
193
|
+
if (!key.endsWith('s')) continue;
|
|
194
|
+
if (key.endsWith('ss')) continue; // glass vs glas, moss vs mos, keep
|
|
195
|
+
if (pluralAllowSet.has(key)) continue; // explicit allow: don't fold
|
|
196
|
+
const singular = key.slice(0, -1);
|
|
197
|
+
if (byNorm.has(singular)) {
|
|
198
|
+
// merge arrays
|
|
199
|
+
const merged = [...byNorm.get(singular), ...byNorm.get(key)];
|
|
200
|
+
byNorm.set(singular, merged);
|
|
201
|
+
byNorm.delete(key);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
151
206
|
const conflicts = [];
|
|
152
207
|
for (const [norm, arr] of byNorm.entries()) {
|
|
153
208
|
if (allowSet.has(norm)) continue; // explicitly allowed
|
package/scripts/sortSrc.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Script to sort the colornames.csv file alphabetically by name
|
|
5
|
-
* This helps maintain order when new colors are added to the list
|
|
6
|
-
*/
|
|
4
|
+
* Script to sort the colornames.csv file alphabetically by name
|
|
5
|
+
* This helps maintain order when new colors are added to the list
|
|
6
|
+
*/
|
|
7
7
|
|
|
8
8
|
import fs from 'fs';
|
|
9
9
|
import path from 'path';
|
|
@@ -28,7 +28,7 @@ const readAndSortCSV = () => {
|
|
|
28
28
|
while (lines.length && !lines[lines.length - 1].trim()) lines.pop();
|
|
29
29
|
|
|
30
30
|
// Trim trailing whitespace on each line
|
|
31
|
-
lines = lines.map(l => l.replace(/\s+$/,''));
|
|
31
|
+
lines = lines.map((l) => l.replace(/\s+$/, ''));
|
|
32
32
|
|
|
33
33
|
// The header should be kept as the first line
|
|
34
34
|
const header = lines[0];
|
|
@@ -52,7 +52,6 @@ const readAndSortCSV = () => {
|
|
|
52
52
|
|
|
53
53
|
console.log(`✅ Successfully sorted ${sortedColorLines.length} colors alphabetically by name`);
|
|
54
54
|
console.log(`📝 File saved: ${csvPath}`);
|
|
55
|
-
|
|
56
55
|
} catch (error) {
|
|
57
56
|
console.error('❌ Error sorting the CSV file:', error);
|
|
58
57
|
process.exit(1);
|