simple-icons/sdk.mjs

227 lines
6.5 KiB
JavaScript
Raw Normal View History

2023-04-19 16:23:13 +03:00
/**
* @fileoverview
* Simple Icons SDK.
*/
import fs from 'node:fs/promises';
2024-03-24 20:38:18 +03:00
import path from 'node:path';
import {fileURLToPath} from 'node:url';
2023-04-19 16:23:13 +03:00
/**
* @typedef {import("./sdk").ThirdPartyExtension} ThirdPartyExtension
* @typedef {import("./sdk").IconData} IconData
*/
const TITLE_TO_SLUG_REPLACEMENTS = {
'+': 'plus',
'.': 'dot',
'&': 'and',
đ: 'd',
ħ: 'h',
ı: 'i',
ĸ: 'k',
ŀ: 'l',
ł: 'l',
ß: 'ss',
ŧ: 't',
};
2024-03-24 20:38:18 +03:00
const TITLE_TO_SLUG_CHARS_REGEX = new RegExp(
2023-04-19 16:23:13 +03:00
`[${Object.keys(TITLE_TO_SLUG_REPLACEMENTS).join('')}]`,
'g',
);
2024-03-24 20:38:18 +03:00
const TITLE_TO_SLUG_RANGE_REGEX = /[^a-z\d]/g;
2023-04-19 16:23:13 +03:00
/**
* Regex to validate HTTPs URLs.
*/
export const URL_REGEX = /^https:\/\/[^\s"']+$/;
/**
* Regex to validate SVG paths.
*/
2024-03-24 20:38:18 +03:00
export const SVG_PATH_REGEX = /^m[-mzlhvcsqtae\d,. ]+$/i;
2023-04-19 16:23:13 +03:00
/**
* Get the directory name where this file is located from `import.meta.url`,
* equivalent to the `__dirname` global variable in CommonJS.
* @param {String} importMetaUrl import.meta.url
* @returns {String} Directory name in which this file is located
*/
export const getDirnameFromImportMeta = (importMetaUrl) =>
path.dirname(fileURLToPath(importMetaUrl));
/**
* Get the slug/filename for an icon.
* @param {IconData} icon The icon data as it appears in *_data/simple-icons.json*
* @returns {String} The slug/filename for the icon
*/
export const getIconSlug = (icon) => icon.slug || titleToSlug(icon.title);
/**
* Extract the path from an icon SVG content.
* @param {String} svg The icon SVG content
* @returns {String} The path from the icon SVG content
**/
export const svgToPath = (svg) => svg.split('"', 8)[7];
2023-04-19 16:23:13 +03:00
/**
* Converts a brand title into a slug/filename.
* @param {String} title The title to convert
* @returns {String} The slug/filename for the title
*/
export const titleToSlug = (title) =>
title
.toLowerCase()
2024-03-24 20:38:18 +03:00
.replaceAll(
2023-04-19 16:23:13 +03:00
TITLE_TO_SLUG_CHARS_REGEX,
(char) => TITLE_TO_SLUG_REPLACEMENTS[char],
)
.normalize('NFD')
2024-03-24 20:38:18 +03:00
.replaceAll(TITLE_TO_SLUG_RANGE_REGEX, '');
2023-04-19 16:23:13 +03:00
/**
* Converts a slug into a variable name that can be exported.
* @param {String} slug The slug to convert
* @returns {String} The variable name for the slug
*/
export const slugToVariableName = (slug) => {
const slugFirstLetter = slug[0].toUpperCase();
return `si${slugFirstLetter}${slug.slice(1)}`;
2023-04-19 16:23:13 +03:00
};
/**
* Converts a brand title as defined in *_data/simple-icons.json* into a brand
* title in HTML/SVG friendly format.
* @param {String} brandTitle The title to convert
* @returns {String} The brand title in HTML/SVG friendly format
*/
export const titleToHtmlFriendly = (brandTitle) =>
brandTitle
2024-03-24 20:38:18 +03:00
.replaceAll('&', '&')
.replaceAll('"', '"')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll(/./g, (char) => {
const charCode = char.codePointAt(0);
2023-04-19 16:23:13 +03:00
return charCode > 127 ? `&#${charCode};` : char;
});
/**
* Converts a brand title in HTML/SVG friendly format into a brand title (as
* it is seen in *_data/simple-icons.json*)
* @param {String} htmlFriendlyTitle The title to convert
* @returns {String} The brand title in HTML/SVG friendly format
*/
export const htmlFriendlyToTitle = (htmlFriendlyTitle) =>
htmlFriendlyTitle
2024-03-24 20:38:18 +03:00
.replaceAll(/&#(\d+);/g, (_, number_) =>
String.fromCodePoint(Number.parseInt(number_, 10)),
)
.replaceAll(
2023-04-19 16:23:13 +03:00
/&(quot|amp|lt|gt);/g,
2024-03-24 20:38:18 +03:00
(_, reference) => ({quot: '"', amp: '&', lt: '<', gt: '>'})[reference],
2023-04-19 16:23:13 +03:00
);
/**
* Get path of *_data/simpe-icons.json*.
2024-03-24 20:38:18 +03:00
* @param {String} rootDirectory Path to the root directory of the project
2023-04-19 16:23:13 +03:00
* @returns {String} Path of *_data/simple-icons.json*
*/
export const getIconDataPath = (
2024-03-24 20:38:18 +03:00
rootDirectory = getDirnameFromImportMeta(import.meta.url),
2023-04-19 16:23:13 +03:00
) => {
2024-03-24 20:38:18 +03:00
return path.resolve(rootDirectory, '_data', 'simple-icons.json');
2023-04-19 16:23:13 +03:00
};
/**
* Get contents of *_data/simple-icons.json*.
2024-03-24 20:38:18 +03:00
* @param {String} rootDirectory Path to the root directory of the project
2023-04-19 16:23:13 +03:00
* @returns {String} Content of *_data/simple-icons.json*
*/
export const getIconsDataString = (
2024-03-24 20:38:18 +03:00
rootDirectory = getDirnameFromImportMeta(import.meta.url),
2023-04-19 16:23:13 +03:00
) => {
2024-03-24 20:38:18 +03:00
return fs.readFile(getIconDataPath(rootDirectory), 'utf8');
2023-04-19 16:23:13 +03:00
};
/**
* Get icons data as object from *_data/simple-icons.json*.
2024-03-24 20:38:18 +03:00
* @param {String} rootDirectory Path to the root directory of the project
2023-04-19 16:23:13 +03:00
* @returns {IconData[]} Icons data as array from *_data/simple-icons.json*
*/
export const getIconsData = async (
2024-03-24 20:38:18 +03:00
rootDirectory = getDirnameFromImportMeta(import.meta.url),
2023-04-19 16:23:13 +03:00
) => {
2024-03-24 20:38:18 +03:00
const fileContents = await getIconsDataString(rootDirectory);
2023-04-19 16:23:13 +03:00
return JSON.parse(fileContents).icons;
};
/**
* Replace Windows newline characters by Unix ones.
* @param {String} text The text to replace
* @returns {String} The text with Windows newline characters replaced by Unix ones
*/
export const normalizeNewlines = (text) => {
2024-03-24 20:38:18 +03:00
return text.replaceAll('\r\n', '\n');
2023-04-19 16:23:13 +03:00
};
/**
* Convert non-6-digit hex color to 6-digit with the character `#` stripped.
* @param {String} text The color text
* @returns {String} The color text in 6-digit hex format
*/
export const normalizeColor = (text) => {
let color = text.replace('#', '').toUpperCase();
if (color.length < 6) {
2024-03-27 07:36:27 +03:00
// eslint-disable-next-line unicorn/no-useless-spread
color = [...color.slice(0, 3)].map((x) => x.repeat(2)).join('');
2023-04-19 16:23:13 +03:00
} else if (color.length > 6) {
color = color.slice(0, 6);
}
2024-03-24 20:38:18 +03:00
2023-04-19 16:23:13 +03:00
return color;
};
/**
* Get information about third party extensions from the README table.
* @param {String} readmePath Path to the README file
2023-04-19 16:23:13 +03:00
* @returns {Promise<ThirdPartyExtension[]>} Information about third party extensions
*/
export const getThirdPartyExtensions = async (
readmePath = path.join(
getDirnameFromImportMeta(import.meta.url),
'README.md',
),
) =>
normalizeNewlines(await fs.readFile(readmePath, 'utf8'))
.split('## Third-Party Extensions\n\n')[1]
.split('\n\n', 1)[0]
2023-04-19 16:23:13 +03:00
.split('\n')
.slice(2)
.map((line) => {
let [module, author] = line.split(' | ');
2023-07-28 16:50:15 +03:00
module = module.split('<img src="')[0];
2023-04-19 16:23:13 +03:00
return {
module: {
2024-03-24 20:38:18 +03:00
name: /\[(.+)]/.exec(module)[1],
2023-04-19 16:23:13 +03:00
url: /\((.+)\)/.exec(module)[1],
},
author: {
2024-03-24 20:38:18 +03:00
name: /\[(.+)]/.exec(author)[1],
2023-04-19 16:23:13 +03:00
url: /\((.+)\)/.exec(author)[1],
},
};
});
/**
* `Intl.Collator` object ready to be used for icon titles sorting.
* @type {Intl.Collator}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator Intl.Collator}
**/
export const collator = new Intl.Collator('en', {
usage: 'search',
caseFirst: 'upper',
});